PostgreSQL Crosstab Query


196

มีใครรู้วิธีสร้างแบบสอบถามแบบแท็บไขว้ใน PostgreSQL หรือไม่
ตัวอย่างเช่นฉันมีตารางต่อไปนี้:

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

ฉันต้องการให้แบบสอบถามส่งคืนอ้างอิงไขว้ต่อไปนี้:

Section    Active    Inactive
A          1         2
B          4         5

เป็นไปได้ไหม


1
ผมมีโครงสร้างแตกต่างกันเล็กน้อยและพบว่าตัวอย่างนี้ยากเล็ก ๆ น้อย ๆ ที่จะเข้าใจดังนั้นฉันเอกสารวิธีการของฉันคิดนี้stackoverflow.com/q/49051959/808723 อาจเป็นประโยชน์สำหรับทุกคน
GameScripting

คำตอบ:


317

ติดตั้งโมดูลเพิ่มเติมtablefunc ครั้งเดียวcrosstab()ต่อฐานข้อมูลซึ่งมีฟังก์ชั่น ตั้งแต่ Postgres 9.1 คุณสามารถใช้CREATE EXTENSIONเพื่อ:

CREATE EXTENSION IF NOT EXISTS tablefunc;

ปรับปรุงกรณีทดสอบ

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

รูปแบบเรียบง่าย - ไม่เหมาะกับแอตทริบิวต์ที่ขาดหายไป

crosstab(text)ด้วย1อินพุตพารามิเตอร์:

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);

ผลตอบแทน:

มาตรา | ใช้งานอยู่ | เฉื่อยชา
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C |      7 | - !!
  • ไม่จำเป็นต้องหล่อและเปลี่ยนชื่อ
  • สังเกตผลลัพธ์ที่ไม่ถูกต้องสำหรับC: ค่า7ถูกป้อนสำหรับคอลัมน์แรก บางครั้งพฤติกรรมนี้เป็นที่ต้องการ แต่ไม่ใช่สำหรับกรณีการใช้งานนี้
  • รูปแบบที่เรียบง่ายนอกจากนี้ยังถูก จำกัด ไว้ที่ตรงสามคอลัมน์ในแบบสอบถามสำหรับการป้อนให้: ROW_NAME , หมวดหมู่ , ค่า ไม่มีที่ว่างสำหรับคอลัมน์เพิ่มเติมเช่นในทางเลือกที่ 2 พารามิเตอร์ด้านล่าง

ฟอร์มปลอดภัย

crosstab(text, text)ด้วย2พารามิเตอร์อินพุต:

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);

ผลตอบแทน:

มาตรา | ใช้งานอยู่ | เฉื่อยชา
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C | |        7   - !!
  • Cหมายเหตุผลที่ถูกต้องสำหรับ

  • พารามิเตอร์ที่สองสามารถสอบถามว่าผลตอบแทนที่หนึ่งแถวต่อแอตทริบิวต์ที่ตรงกับคำสั่งของนิยามคอลัมน์ที่สิ้นสุด บ่อยครั้งที่คุณต้องการสืบค้นแอตทริบิวต์ที่แตกต่างจากตารางที่มีลักษณะดังนี้:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    ที่อยู่ในคู่มือ

    เนื่องจากคุณต้องสะกดคอลัมน์ทั้งหมดในรายการนิยามคอลัมน์ต่อไป (ยกเว้นสำหรับตัวแปรที่กำหนดไว้ล่วงหน้า) ดังนั้นโดยทั่วไปจะมีประสิทธิภาพมากกว่าในการแสดงรายการสั้น ๆ ในนิพจน์ที่แสดง:crosstabN()VALUES

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

    หรือ (ไม่ใช่ในคู่มือ):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • ฉันใช้การอ้างอิงดอลลาร์เพื่อให้การอ้างง่ายขึ้น

  • คุณสามารถเอาท์พุทคอลัมน์ที่มีชนิดข้อมูลต่างด้วยcrosstab(text, text)- ตราบใดที่การแสดงข้อความของคอลัมน์ค่าเป็นอินพุตที่ถูกต้องสำหรับประเภทเป้าหมาย วิธีนี้คุณอาจมีแอตทริบิวต์ของชนิดที่แตกต่างกันและเอาท์พุทtext, date, numericฯลฯ สำหรับแอตทริบิวต์ที่เกี่ยวข้อง มีตัวอย่างโค้ดที่ปลายเป็นบทcrosstab(text, text)ในคู่มือ

db <> ซอที่นี่

ตัวอย่างขั้นสูง


\crosstabview ใน psql

Postgres 9.6เพิ่มเมตานี้สั่งไปยังสถานีของมันเริ่มต้นโต้ตอบpsql คุณสามารถเรียกใช้แบบสอบถามที่คุณจะใช้เป็นcrosstab()พารามิเตอร์แรกและป้อนไปยัง\crosstabview(ทันทีหรือในขั้นตอนถัดไป) ชอบ:

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

ผลลัพธ์ที่คล้ายกันข้างต้น แต่เป็นคุณลักษณะการแสดงในฝั่งไคลเอ็นต์โดยเฉพาะ แถวอินพุตจะถือว่าแตกต่างกันเล็กน้อยดังนั้นจึงORDER BYไม่จำเป็น รายละเอียดสำหรับ\crosstabviewในคู่มือ มีตัวอย่างโค้ดเพิ่มเติมที่ด้านล่างของหน้านั้น

คำตอบที่เกี่ยวข้องกับ dba.SE โดย Daniel Vérité (ผู้เขียนคุณสมบัติ psql):



ตอบรับก่อนหน้านี้ล้าสมัย

  • ตัวแปรของฟังก์ชันcrosstab(text, integer)นั้นล้าสมัยแล้ว integerพารามิเตอร์ที่สองจะถูกละเว้น ฉันพูดคู่มือปัจจุบัน :

    crosstab(text sql, int N) ...

    crosstab(text)รุ่นล้าสมัยของ Nตอนนี้พารามิเตอร์จะถูกละเว้นเนื่องจากจำนวนคอลัมน์ค่าจะถูกกำหนดโดยคิวรีการโทรเสมอ

  • หล่อและเปลี่ยนชื่อโดยไม่จำเป็น

  • มันล้มเหลวหากแถวไม่มีคุณสมบัติทั้งหมด ดูตัวแปรที่ปลอดภัยที่มีพารามิเตอร์อินพุตสองตัวด้านบนเพื่อจัดการแอตทริบิวต์ที่ขาดหายไปอย่างเหมาะสม

  • ORDER BYcrosstab()จะต้องอยู่ในรูปแบบหนึ่งของพารามิเตอร์ คู่มือ:

    ในทางปฏิบัติแบบสอบถาม SQL ควรระบุเสมอORDER BY 1,2เพื่อให้แน่ใจว่ามีการเรียงลำดับแถวเข้าอย่างถูกต้อง


3
+1 เขียนดีขอบคุณที่สังเกตIn practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
ChristopheD

ฉันมีปัญหาในการใช้ $$ VALUES .. $$ ฉันใช้ 'VALUES (' '<attr>' ':: <type>), .. '
Marco Fantasia

เราสามารถระบุการเชื่อมพารามิเตอร์ในแบบสอบถามแท็บไขว้ได้หรือไม่? ฉันได้รับข้อผิดพลาดนี้ => ไม่สามารถระบุชนิดข้อมูลของพารามิเตอร์ $ 2
Ashish

1
เป็นไปได้หรือไม่ที่จะตั้งค่าเริ่มต้นสำหรับคอลัมน์ในแท็บไขว้แท็บไขว้?
Ashish

2
@Aishish: โปรดเริ่มคำถามใหม่ ความเห็นไม่ใช่สถานที่ คุณสามารถเชื่อมโยงกับบริบทนี้ได้ตลอดเวลา
Erwin Brandstetter

30

คุณสามารถใช้crosstab()ฟังก์ชั่นของโมดูลเสริม tablefuncซึ่งคุณต้องติดตั้งหนึ่งครั้งต่อฐานข้อมูล ตั้งแต่ PostgreSQL 9.1 คุณสามารถใช้สิ่งCREATE EXTENSIONต่อไปนี้:

CREATE EXTENSION tablefunc;

ในกรณีของคุณฉันเชื่อว่ามันจะมีลักษณะเช่นนี้:

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);

ในกรณีที่คุณใช้พารามิเตอร์ในแบบสอบถามแบบแท็บไขว้คุณต้องหลีกเลี่ยงอย่างถูกต้อง ตัวอย่าง: (จากด้านบน) บอกว่าคุณต้องการเฉพาะรายการที่ใช้งานอยู่: SELECT ... จากอ้างอิงไขว้ ('เลือกส่วน :: ข้อความ, สถานะ, นับ :: ข้อความจาก t โดยที่ status =' 'active' '', 2) AS .. (สังเกตเครื่องหมายคำพูดคู่) ในกรณีที่พารามิเตอร์ถูกส่งไปที่รันไทม์โดยผู้ใช้ (เช่นพารามิเตอร์ฟังก์ชั่นตัวอย่าง) คุณสามารถพูดได้: SELECT ... จากแท็บไขว้ ('เลือกส่วน :: ข้อความสถานะนับ :: ข้อความจาก t โดยที่สถานะ =' ' '|| par_active ||' '' ', 2) AS ... (เครื่องหมายคำพูดสามคำที่นี่!) ใน BIRT สิ่งนี้สามารถใช้ได้กับ ตัวยึด
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
บางคนสามารถอธิบายได้ว่าฟังก์ชันแท็บไขว้ในโมดูล tablefunc เพิ่มคำตอบนี้ซึ่งทั้งสองทำงานที่อยู่ในมือและใจของฉันเข้าใจง่ายกว่ากัน
John Powell

4
@ JohnBarça: กรณีง่าย ๆ แบบนี้สามารถแก้ไขได้อย่างง่ายดายด้วยคำสั่ง CASE อย่างไรก็ตามสิ่งนี้ได้รับความรวดเร็วอย่างมากด้วยแอตทริบิวต์และ / หรือประเภทข้อมูลอื่น ๆ มากกว่าจำนวนเต็ม เช่นกัน: แบบฟอร์มนี้ใช้ฟังก์ชันการรวมsum()ก็จะดีกว่าการใช้min()หรือmax()ไม่มีELSEที่ทำงานสำหรับtextยัง แต่สิ่งนี้มีเอฟเฟกต์ที่แตกต่างอย่างละเอียดกว่าcorosstab()ซึ่งจะใช้ค่า "แรก" ต่อแอตทริบิวต์ ไม่สำคัญตราบใดที่มีได้เพียงอย่างเดียว ในที่สุดประสิทธิภาพก็มีความเกี่ยวข้องเช่นกัน crosstab()ถูกเขียนใน C และปรับให้เหมาะสมสำหรับงาน
Erwin Brandstetter

สิ่งนี้ใช้ไม่ได้สำหรับฉันสำหรับ postgresql ฉันพบข้อผิดพลาดERROR: 42803: aggregate function calls may not be nested
Audrey

1
@Audrey คุณไม่ได้ใช้ SQL ตัวเดียวกันใช่ไหม?

2
ลองเพิ่มคำอธิบายเทียบกับบล็อกโค้ด
Daniel L. VanDenBosch

10

โซลูชันที่มีการรวม 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

ขอบคุณสิ่งนี้ช่วยฉันด้วยปัญหาที่เกี่ยวข้อง
JeffCharter

1

ขออภัยนี่ไม่สมบูรณ์เพราะฉันไม่สามารถทดสอบได้ที่นี่ แต่อาจทำให้คุณไปในทิศทางที่ถูกต้อง ฉันแปลจากสิ่งที่ฉันใช้ซึ่งทำให้ข้อความค้นหาที่คล้ายกัน:

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;

รหัสที่ฉันใช้งานคือ:

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;

ซึ่งจะส่งคืน typeID การเสนอราคาสูงสุดและราคาต่ำสุดที่ถามและความแตกต่างระหว่างทั้งสอง


1
คุณไม่มีประโยคจากมิฉะนั้นจะถูกต้อง แผนการอธิบายแตกต่างกันอย่างมากในระบบของฉัน - ฟังก์ชั่นอ้างอิงไขว้มีราคา 22.5 ในขณะที่วิธีเข้าร่วมซ้ายมีราคาแพงกว่าประมาณ 4 เท่าด้วยราคา 91.38 นอกจากนี้ยังสร้างการอ่านทางกายภาพจำนวนมากเป็นสองเท่าและทำการแฮชการรวมซึ่งอาจมีราคาค่อนข้างแพงเมื่อเทียบกับการรวมประเภทอื่น ๆ
ยิระมะยา Peschka

ขอบคุณเยเรมีย์ที่ดีที่รู้ ฉันได้เพิ่มคำตอบอื่น ๆ แล้ว แต่ความคิดเห็นของคุณก็คุ้มค่าดังนั้นฉันจะไม่ลบคำตอบนี้
LanceH

-1

Crosstabฟังก์ชั่นสามารถใช้ได้ภายใต้tablefuncนามสกุล คุณจะต้องสร้างส่วนขยายนี้หนึ่งครั้งสำหรับฐานข้อมูล

สร้างส่วนขยายtablefunc;

คุณสามารถใช้รหัสด้านล่างเพื่อสร้างตารางเดือยโดยใช้ cross tab:

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
คำตอบนี้ไม่ได้เพิ่มคำตอบที่มีอยู่ก่อนหน้า
Erwin Brandstetter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.