Nhóm trên bất kỳ một trong nhiều cột trong Postgres


7

Có thể tạo ra một số loại nhóm trong Postgres? Hãy nói rằng tôi có biểu đồ sau:

CREATE TABLE foo AS
SELECT row_number() OVER () AS id, *
FROM ( VALUES
  ( 'X', 'D', 'G', 'P' ),
  ( 'F', 'D', 'L', 'M' ),
  ( 'X', 'N', 'R', 'S' ),
  ( 'Y', 'I', 'W', NULL ),
  ( 'U', 'Z', 'E', NULL )
) AS f(a,b,c,d);

id | a | b | c | d
------------------
 1 | X | D | G | P
 2 | F | D | L | M
 3 | X | N | R | S
 4 | Y | I | W | 
 5 | U | Z | E | 

Tôi muốn bằng cách nào đó tạo ra một GROUP BYnhóm mang lại ba nhóm:

  1. 1, 23cùng nhau
    • 12vì một điểm chung Dtrong bcột
    • 13vì một điểm chung Xtrong acột
  2. 4 một mình (không có giá trị chung trong bất kỳ cột nào; null không nên khớp)
  3. 5 một mình (không có giá trị chung trong bất kỳ cột nào; null không nên khớp)

Tôi hiện đang sử dụng Postgres 9.5, nhưng cuối cùng chúng tôi sẽ nâng cấp lên 9.6, vì vậy nếu có bất cứ điều gì trong đó sẽ giúp tôi, tôi sẵn sàng nghe nó.

Nói cách khác, tôi đang tìm kiếm một cái gì đó như (giả sử tôi đã sử dụng array_agg(DISTINCT a), v.v. để giữ cho màn hình đơn giản hơn):

   ids    |     as     |     bs     |       cs        |      ds
-----------------------------------------------------------------------
{1, 2, 3} | {'X', 'F'} | {'D', 'N'} | {'G', 'L', 'R'} | {'P', 'M', 'S'}
{4}       | {'Y'}      | {'I'}      | {'W'}           | {NULL}
{5}       | {'U'}      | {'Z'}      | {'E'}           | {NULL}

(Tôi không chắc chắn chính xác làm thế nào các null sẽ hiển thị vì vậy đừng quá bận tâm về điều đó; điểm quan trọng là chúng không nên khớp với nhau.)

Khi tôi sử dụng GROUP BY CUBE (a, b, c, d), tôi nhận được nhiều hơn ba kết quả ... ditto GROUP BY ROLLUPGROUP BY GROUPING SETS.

Có một cách thanh lịch trong Postgres? Tôi có thể tưởng tượng bạn sẽ làm điều đó như thế nào trong Ruby thông qua Active Record (lặp qua từng bản ghi, nhóm nó với các nhóm được nhóm trước đó phù hợp), nhưng tôi muốn giữ điều này trong Postgres nếu có thể.


1
Vui lòng chỉnh sửa câu hỏi để bao gồm kết quả của cuộc thảo luận bạn đã có trong các nhận xét với Evan về SO (Tôi không thể thấy câu hỏi đã bị xóa trên SO nữa). Họ đã làm rõ hữu ích.
Jack nói hãy thử topanswers.xyz

1
@Stephen vui lòng đọc các bình luận dưới câu trả lời của Erwn và làm rõ câu hỏi của bạn với kết quả chính xác mà bạn muốn trong trường hợp được thảo luận.
ypercubeᵀᴹ

Câu trả lời:


5

Giả sử bạn theo đuổi một giải pháp chung, tôi không nghĩ có bất kỳ phương pháp không đệ quy nào để giải quyết vấn đề của bạn. Nếu vấn đề trong thế giới thực của bạn cần phải làm việc với số lượng lớn các hàng, bạn có thể yêu cầu công việc của mình cắt ra để có được một giải pháp có quy mô đủ tốt.

lược đồ kiểm tra và dữ liệu:

begin;
create schema stack;
set search_path=stack;

create table foo as
select *
from (values (1,'X','D','G','P')
           , (2,'F','D','L','M')
           , (3,'X','N','R','S')
           , (4,'Y','I','W',null)
           , (5,'U','Z','E',null) ) AS f(id,a,b,c,d);

giải pháp:

with recursive t(id,a,b,c,d,start,path,cycle) as (
  select *, id, array[id], false from foo
  union all
  select f.*, start, path||f.id, f.id=any(path)
  from foo f join t 
    on f.id<>t.id and
       (f.a=t.a or f.b=t.b or f.c=t.c or f.d=t.d) where not cycle )
select array_agg(f.id order by f.id) ids
     , array_agg(distinct a order by a) a
     , array_agg(distinct b order by b) b
     , array_agg(distinct c order by c) c
     , array_agg(distinct d order by d) d
from foo f join ( select start id, array_agg(id order by id) ids
                  from t
                  where not cycle group by start) z on z.id=f.id
group by ids::text;
┌─────────┬───────┬───────┬─────────┬─────────┐
│   ids   │   a   │   b   │    c    │    d    │
├─────────┼───────┼───────┼─────────┼─────────┤
│ {1,2,3} │ {F,X} │ {D,N} │ {G,L,R} │ {M,P,S} │
│ {4}     │ {Y}   │ {I}   │ {W}     │ {NULL}  │
│ {5}     │ {U}   │ {Z}   │ {E}     │ {NULL}  │
└─────────┴───────┴───────┴─────────┴─────────┘

dọn dẹp:

rollback;

TIL tôi đã học được arr_aggmột DISTINCTđiều đó thật tuyệt vời.
Evan Carroll

tín dụng cho OP cho điều đó (và tôi nghĩ đó là trong câu hỏi ban đầu về SO)
Jack nói hãy thử topanswers.xyz

5

Một giải pháp đệ quy khác:

  • đầu tiên tạo danh sách kề của biểu đồ được kết nối của id,
  • sau đó tìm thấy đóng cửa bắc cầu của nó (đây là phần đệ quy)
  • và sau đó nhóm theo (một lần) để tìm các thành phần được kết nối mà mỗi nút thuộc về
  • và tham gia vào bảng một lần nữa và nhóm theo (một lần nữa) để thu thập các giá trị từ tất cả các nút của mỗi thành phần được kết nối.

Dữ liệu ban đầu (được sao chép từ giải pháp của Jack Douglas ):

begin;
create schema stack;
set search_path=stack;

create table foo as
select *
from (values (1,'X','D','G','P')
           , (2,'F','D','L','M')
           , (3,'X','N','R','S')
           , (4,'Y','I','W',null)
           , (5,'U','Z','E',null) ) AS f(id,a,b,c,d);

Truy vấn:

with recursive 
  al (tail, head) as                     -- adjacency list 
  ( select f.id, g.id 
    from foo as f join foo as g
      on (f.a = g.a or f.b = g.b or f.c = g.c or f.d = g.d) 
  ),
  tc (tail, head) as                     -- transitive closure
  ( select * from al
    union distinct
    select f.tail, g.head 
    from al as f join tc as g on f.head = g.tail
  ) ,
  cc (head, ids) as                      -- group once
  ( select head, array_agg(distinct tail order by tail) as ids
    from tc
    group by head
  ) 
select                                   -- group twice
    ids,
    array_agg(distinct a order by a) as a,
    array_agg(distinct b order by b) as b,
    array_agg(distinct c order by c) as c,
    array_agg(distinct d order by d) as d
from
  cc join foo on cc.head = foo.id
group by ids ;
┌─────────┬───────┬───────┬─────────┬─────────┐
│   ids   │   a   │   b   │    c    │    d    │
├─────────┼───────┼───────┼─────────┼─────────┤
│ {1,2,3} │ {F,X} │ {D,N} │ {G,L,R} │ {M,P,S} │
│ {4}     │ {Y}   │ {I}   │ {W}     │ {NULL}  │
│ {5}     │ {U}   │ {Z}   │ {E}     │ {NULL}  │
└─────────┴───────┴───────┴─────────┴─────────┘

Dọn dẹp:

rollback;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.