Truy vấn Crosstab PostgreSQL


196

Có ai biết làm thế nào để tạo truy vấn chéo bảng trong PostgreSQL không?
Ví dụ tôi có bảng sau:

Section    Status    Count
A          Active    1
A          Inactive  2
B          Active    4
B          Inactive  5

Tôi muốn truy vấn trả về dấu chéo sau:

Section    Active    Inactive
A          1         2
B          4         5

Điều này có thể không?


1
Tôi có một cấu trúc hơi khác và thấy ví dụ này hơi khó hiểu nên tôi đã ghi lại cách nghĩ của mình về stackoverflow.com/q/49051959/808723 này . Có lẽ nó hữu ích cho bất cứ ai.
GameScripting

Câu trả lời:


317

Cài đặt mô-đun bổ sung tablefunc một lần cho mỗi cơ sở dữ liệu, cung cấp chức năng crosstab(). Vì Postgres 9.1, bạn có thể sử dụng CREATE EXTENSIONcho điều đó:

CREATE EXTENSION IF NOT EXISTS tablefunc;

Trường hợp kiểm tra được cải thiện

CREATE TABLE tbl (
   section   text
 , status    text
 , ct        integer  -- "count" is a reserved word in standard SQL
);

INSERT INTO tbl VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                    , ('C', 'Inactive', 7);  -- ('C', 'Active') is missing

Hình thức đơn giản - không phù hợp với các thuộc tính bị thiếu

crosstab(text)với 1 tham số đầu vào:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- needs to be "ORDER BY 1,2" here
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Trả về:

Mục | Hoạt động | Không hoạt động
--------- + -------- + ----------
 Một | 1 | 2
 B | 4 | 5
 C |      7 | - !!
  • Không cần đúc và đổi tên.
  • Lưu ý kết quả không chính xác cho C: giá trị 7được điền cho cột đầu tiên. Đôi khi, hành vi này là mong muốn, nhưng không phải cho trường hợp sử dụng này.
  • Biểu mẫu đơn giản cũng được giới hạn ở chính xác ba cột trong truy vấn đầu vào được cung cấp: row_name , category , value . Không có chỗ cho các cột thêm như trong thay thế 2 tham số bên dưới.

Hình thức an toàn

crosstab(text, text)với 2 tham số đầu vào:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- could also just be "ORDER BY 1" here

  , $$VALUES ('Active'::text), ('Inactive')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Trả về:

Mục | Hoạt động | Không hoạt động
--------- + -------- + ----------
 Một | 1 | 2
 B | 4 | 5
 C | |        7   - !!
  • Lưu ý kết quả chính xác cho C.

  • Các tham số thứ hai có thể là bất kỳ truy vấn mà trở về một hàng cho mỗi thuộc tính phù hợp với thứ tự của các định nghĩa cột ở cuối. Thường thì bạn sẽ muốn truy vấn các thuộc tính riêng biệt từ bảng bên dưới như thế này:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    Đó là trong hướng dẫn.

    Vì dù sao bạn cũng phải đánh vần tất cả các cột trong danh sách định nghĩa cột (ngoại trừ các biến thể được xác định trước ), nên việc cung cấp một danh sách ngắn trong một biểu thức như thể hiện là hiệu quả hơn :crosstabN()VALUES

    $$VALUES ('Active'::text), ('Inactive')$$)

    Hoặc (không có trong hướng dẫn):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • Tôi đã sử dụng báo giá đô la để làm cho trích dẫn dễ dàng hơn.

  • Bạn thậm chí có thể xuất các cột với các loại dữ liệu khác nhau với crosstab(text, text)- miễn là biểu diễn văn bản của cột giá trị là đầu vào hợp lệ cho loại mục tiêu. Bằng cách này bạn có thể có các thuộc tính của loại hình khác nhau và đầu ra text, date, numericvv cho các thuộc tính tương ứng. Có một ví dụ mã ở cuối chương crosstab(text, text)trong hướng dẫn .

db <> fiddle ở đây

Ví dụ nâng cao


\crosstabview trong psql

Postgres 9.6 bổ sung này meta-lệnh để thiết bị đầu cuối tương tác mặc định của nó psql . Bạn có thể chạy truy vấn bạn sẽ sử dụng làm crosstab()tham số đầu tiên và đưa nó đến \crosstabview(ngay lập tức hoặc trong bước tiếp theo). Giống:

db=> SELECT section, status, ct FROM tbl \crosstabview

Kết quả tương tự như trên, nhưng đó là một tính năng đại diện riêng cho phía khách hàng . Các hàng đầu vào được xử lý hơi khác nhau, do đó ORDER BYkhông bắt buộc. Chi tiết \crosstabviewtrong hướng dẫn. Có nhiều ví dụ mã ở dưới cùng của trang đó.

Câu trả lời liên quan trên dba.SE của Daniel Vérité (tác giả của tính năng psql):



Các câu trả lời được chấp nhận trước đây là lỗi thời.

  • Các biến thể của chức năng crosstab(text, integer)đã lỗi thời. integerTham số thứ hai được bỏ qua. Tôi trích dẫn hướng dẫn hiện tại :

    crosstab(text sql, int N) ...

    Phiên bản lỗi thời của crosstab(text). Tham số Nhiện được bỏ qua, vì số lượng cột giá trị luôn được xác định bởi truy vấn gọi

  • Không cần đúc và đổi tên.

  • Nó thất bại nếu một hàng không có tất cả các thuộc tính. Xem biến thể an toàn với hai tham số đầu vào ở trên để xử lý các thuộc tính bị thiếu đúng cách.

  • ORDER BYđược yêu cầu ở dạng một tham số của crosstab(). Hướng dẫn sử dụng:

    Trong thực tế, truy vấn SQL phải luôn luôn chỉ định ORDER BY 1,2để đảm bảo rằng các hàng đầu vào được sắp xếp đúng


3
+1, viết tốt, cảm ơn vì đã chú ýIn practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
BarsheD

Tôi có một số vấn đề khi sử dụng $$ GIÁ TRỊ .. $$. Tôi đã sử dụng thay thế 'GIÁ TRỊ (' '<attr>' ':: <type>), ..'
Marco Fantasia

Chúng tôi có thể chỉ định ràng buộc tham số trong truy vấn chéo bảng không? Tôi đang gặp lỗi này => không thể xác định loại dữ liệu của tham số $ 2
Ashish

1
Có thể đặt giá trị mặc định cho cột trong truy vấn chéo bảng không?
Ashish

2
@Ashish: Hãy bắt đầu một câu hỏi mới. Bình luận không phải là nơi. Bạn luôn có thể liên kết đến cái này cho bối cảnh.
Erwin Brandstetter

30

Bạn có thể sử dụng crosstab()chức năng của bảng mô-đun bổ sung - mà bạn phải cài đặt một lần cho mỗi cơ sở dữ liệu. Kể từ PostgreSQL 9.1, bạn có thể sử dụng CREATE EXTENSIONcho điều đó:

CREATE EXTENSION tablefunc;

Trong trường hợp của bạn, tôi tin rằng nó sẽ trông giống như thế này:

CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);

INSERT INTO t VALUES ('A', 'Active',   1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active',   4);
INSERT INTO t VALUES ('B', 'Inactive', 5);

SELECT row_name AS Section,
       category_1::integer AS Active,
       category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
            AS ct (row_name text, category_1 text, category_2 text);

Trong trường hợp bạn sử dụng một tham số trong truy vấn chéo bảng, bạn phải thoát nó đúng cách. Ví dụ: (từ phía trên) nói rằng bạn chỉ muốn những người hoạt động: CHỌN ... TỪ crosstab ('select mục :: text, status, Count :: text từ t where status =' 'active' '', 2) AS. .. (chú ý dấu ngoặc kép). Trong trường hợp tham số được người dùng truyền vào thời gian chạy (ví dụ như tham số hàm), bạn có thể nói: CHỌN ... TỪ crosstab ('select mục :: text, status, Count :: text từ t where status =' ' '| | par_active ||' '' ', 2) NHƯ ... (ba trích dẫn ở đây!). Trong BIRT điều này cũng hoạt động với? giữ chỗ.
Wim Verhavert

26
SELECT section,
       SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
       SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status  value as a separate column explicitly

FROM t
GROUP BY section

1
Ai đó có thể giải thích chức năng crosstab trong mô-đun bảngfunc thêm vào câu trả lời này không, cả hai đều thực hiện công việc trong tay, và theo tôi thì dễ hiểu hơn?
John Powell

4
@ JohnBarça: Một trường hợp đơn giản như thế này có thể dễ dàng được giải quyết bằng các câu lệnh CASE. Tuy nhiên, điều này trở nên khó sử dụng rất nhanh với nhiều thuộc tính và / hoặc các loại dữ liệu khác hơn là chỉ các số nguyên. Bên cạnh đó: hình thức này sử dụng hàm tổng hợp sum(), sẽ tốt hơn nếu sử dụng min()hoặc max()không có chức năng ELSEnào hoạt động text. Nhưng điều này có hiệu ứng khác biệt tinh vi hơn corosstab(), chỉ sử dụng giá trị "đầu tiên" cho mỗi thuộc tính. Không quan trọng miễn là chỉ có một. Cuối cùng, hiệu suất cũng có liên quan. crosstab()được viết bằng C và tối ưu hóa cho nhiệm vụ.
Erwin Brandstetter

Điều này không làm việc cho tôi, cho postgresql. Tôi nhận được lỗiERROR: 42803: aggregate function calls may not be nested
Audrey

1
@Audrey bạn không chạy cùng một SQL?

2
Xem xét thêm giải thích so với chỉ một khối mã
Daniel L. VanDenBosch

10

Giải pháp với tổng hợp JSON:

CREATE TEMP TABLE t (
  section   text
, status    text
, ct        integer  -- don't use "count" as column name.
);

INSERT INTO t VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7); 


SELECT section,
       (obj ->> 'Active')::int AS active,
       (obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
      FROM t
      GROUP BY section
     )X

Cảm ơn bạn, điều này đã giúp tôi với một vấn đề liên quan.
JeffCharter

1

Xin lỗi, điều này không hoàn thành vì tôi không thể kiểm tra nó ở đây, nhưng nó có thể giúp bạn đi đúng hướng. Tôi đang dịch từ thứ tôi sử dụng tạo ra một truy vấn tương tự:

select mt.section, mt1.count as Active, mt2.count as Inactive
from mytable mt
left join (select section, count from mytable where status='Active')mt1
on mt.section = mt1.section
left join (select section, count from mytable where status='Inactive')mt2
on mt.section = mt2.section
group by mt.section,
         mt1.count,
         mt2.count
order by mt.section asc;

Mã tôi đang làm việc là:

select m.typeID, m1.highBid, m2.lowAsk, m1.highBid - m2.lowAsk as diff, 100*(m1.highBid - m2.lowAsk)/m2.lowAsk as diffPercent
from mktTrades m
   left join (select typeID,MAX(price) as highBid from mktTrades where bid=1 group by typeID)m1
   on m.typeID = m1.typeID
   left join (select typeID,MIN(price) as lowAsk  from mktTrades where bid=0 group by typeID)m2
   on m1.typeID = m2.typeID
group by m.typeID, 
         m1.highBid, 
         m2.lowAsk
order by diffPercent desc;

sẽ trả về một loại ID, giá thầu cao nhất và giá thấp nhất được hỏi và chênh lệch giữa hai loại (một sự khác biệt tích cực có nghĩa là một cái gì đó có thể được mua với giá thấp hơn nó có thể được bán).


1
Bạn đang thiếu một mệnh đề từ, nếu không thì điều này là chính xác. Các kế hoạch giải thích rất khác nhau trên hệ thống của tôi - chức năng chéo bảng có chi phí 22,5 trong khi phương pháp LEFT THAM GIA đắt gấp khoảng 4 lần với chi phí 91,38. Nó cũng tạo ra khoảng gấp đôi số lần đọc vật lý và thực hiện các phép nối băm - có thể khá tốn kém so với các loại tham gia khác.
Jeremiah Peschka

Cảm ơn Jeremiah, đó là điều tốt để biết. Tôi đã đưa ra câu trả lời khác, nhưng nhận xét của bạn đáng để lưu giữ vì vậy tôi sẽ không xóa câu trả lời này.
LanceH

-1

Crosstabchức năng có sẵn dưới tablefuncphần mở rộng. Bạn sẽ phải tạo tiện ích mở rộng này một lần cho cơ sở dữ liệu.

TẠO KIỂM TRA tablefunc;

Bạn có thể sử dụng mã dưới đây để tạo bảng xoay vòng bằng cách sử dụng tab chéo:

create table test_Crosstab( section text,
<br/>status text,
<br/>count numeric)

<br/>insert into test_Crosstab values ( 'A','Active',1)
                <br/>,( 'A','Inactive',2)
                <br/>,( 'B','Active',4)
                <br/>,( 'B','Inactive',5)

select * from crosstab(
<br/>'select section
    <br/>,status
    <br/>,count
    <br/>from test_crosstab'
    <br/>)as ctab ("Section" text,"Active" numeric,"Inactive" numeric)

1
Câu trả lời này không thêm gì vào câu trả lời đã có từ trước.
Erwin Brandstetter
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.