PostgreSQL Crosstab Query

196

Apakah ada yang tahu cara membuat kueri tab silang di PostgreSQL?
Misalnya saya punya tabel berikut:

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

Saya ingin kueri mengembalikan tab silang berikut:

Section    Active    Inactive
A          1         2
B          4         5

Apakah ini mungkin?

schone
sumber
1
Saya memiliki struktur yang sedikit berbeda dan menemukan contoh ini agak sulit untuk dipahami, jadi saya mendokumentasikan cara berpikir saya tentang stackoverflow.com/q/49051959/808723 ini . Mungkin ini membantu bagi siapa pun.
GameScripting

Jawaban:

317

Instal modul tambahan tablefunc satu kali per basis data, yang menyediakan fungsi crosstab(). Sejak Postgres 9.1 Anda dapat menggunakannya CREATE EXTENSIONuntuk itu:

CREATE EXTENSION IF NOT EXISTS tablefunc;

Kasing uji yang ditingkatkan

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

Bentuk sederhana - tidak cocok untuk atribut yang hilang

crosstab(text)dengan 1 parameter input:

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

Pengembalian:

Bagian | Aktif | Tidak aktif
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C |      7 | - !!
  • Tidak perlu casting dan penggantian nama.
  • Perhatikan hasil yang salah untuk C: nilai 7diisi untuk kolom pertama. Kadang-kadang, perilaku ini diinginkan, tetapi tidak untuk kasus penggunaan ini.
  • Bentuk sederhana juga dibatasi tepat pada tiga kolom dalam kueri input yang disediakan: row_name , kategori , nilai . Tidak ada ruang untuk kolom tambahan seperti pada alternatif 2-parameter di bawah ini.

Bentuk yang aman

crosstab(text, text)dengan 2 parameter input:

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

Pengembalian:

Bagian | Aktif | Tidak aktif
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C | |        7   - !!
  • Catat hasil yang benar untuk C.

  • The Parameter kedua dapat permintaan apapun yang kembali satu baris per atribut yang cocok dengan urutan definisi kolom di akhir. Seringkali Anda ingin meminta atribut berbeda dari tabel yang mendasarinya seperti ini:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    Itu ada di manual.

    Karena Anda harus mengeja semua kolom dalam daftar definisi kolom (kecuali untuk varian yang telah ditentukan ), biasanya lebih efisien untuk memberikan daftar pendek dalam ekspresi seperti yang ditunjukkan:crosstabN()VALUES

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

    Atau (tidak ada dalam manual):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • Saya menggunakan kutipan dolar untuk membuat kutipan lebih mudah.

  • Anda bahkan dapat menampilkan kolom dengan tipe data berbeda dengan crosstab(text, text)- selama representasi teks dari kolom nilai adalah input yang valid untuk tipe target. Dengan cara ini Anda mungkin memiliki atribut yang berbeda dan output text, date, numericdll untuk atribut masing-masing. Ada contoh kode di akhir bab crosstab(text, text)dalam manual .

db <> biola di sini

Contoh lanjutan


\crosstabview dalam psql

Postgres 9.6 menambahkan meta-command ini ke psql terminal interaktif default . Anda dapat menjalankan kueri yang akan Anda gunakan sebagai crosstab()parameter pertama dan mengumpankannya \crosstabview(segera atau pada langkah berikutnya). Suka:

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

Hasil yang sama seperti di atas, tetapi ini adalah fitur representasi di sisi klien secara eksklusif. Baris input diperlakukan sedikit berbeda, oleh karena ORDER BYitu tidak diperlukan. Detail untuk \crosstabviewdalam manual. Ada lebih banyak contoh kode di bagian bawah halaman itu.

Jawaban terkait pada dba.SE oleh Daniel Vérité (penulis fitur psql):



The jawaban yang diterima sebelumnya sudah usang.

  • Varian fungsi crosstab(text, integer)sudah usang. integerParameter kedua diabaikan. Saya mengutip manual saat ini :

    crosstab(text sql, int N) ...

    Versi usang dari crosstab(text). Parameter Nsekarang diabaikan, karena jumlah kolom nilai selalu ditentukan oleh kueri panggilan

  • Pengecoran dan penggantian nama yang tidak perlu.

  • Gagal jika satu baris tidak memiliki semua atribut. Lihat varian aman dengan dua parameter input di atas untuk menangani atribut yang hilang dengan benar.

  • ORDER BYdiperlukan dalam bentuk satu-parameter dari crosstab(). Manual:

    Dalam praktiknya, kueri SQL harus selalu menentukan ORDER BY 1,2untuk memastikan bahwa baris input dipesan dengan benar

Erwin Brandstetter
sumber
3
+1, langganan yang baik, terima kasih telah memperhatikanIn practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
ChristopheD
Saya punya beberapa masalah menggunakan $$ VALUES .. $$. Sebagai gantinya saya menggunakan 'VALUES (' '<attr>' ':: <type>), ..'
Marco Fantasia
Bisakah kita menentukan pengikatan parameter dalam kueri tab silang? Saya mendapatkan kesalahan ini => tidak dapat menentukan tipe data dari parameter $ 2
Ashish
1
Apakah mungkin untuk menetapkan nilai default untuk kolom dalam kueri tab silang?
Ashish
2
@Ashish: Silakan mulai pertanyaan baru. Komentar bukan tempatnya. Anda selalu dapat menautkan ini untuk konteks.
Erwin Brandstetter
30

Anda dapat menggunakan crosstab()fungsi tablefunc modul tambahan - yang harus Anda instal satu kali per basis data. Sejak PostgreSQL 9.1 Anda dapat menggunakannya CREATE EXTENSIONuntuk itu:

CREATE EXTENSION tablefunc;

Dalam kasus Anda, saya percaya akan terlihat seperti ini:

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);
Jeremiah Peschka
sumber
Jika Anda menggunakan parameter dalam kueri tab silang, Anda harus menghindarinya dengan benar. Contoh: (dari atas) mengatakan Anda hanya menginginkan yang aktif: PILIH ... DARI crosstab ('pilih bagian :: teks, status, hitung :: teks dari t di mana status =' 'aktif' '', 2) AS. .. (perhatikan tanda kutip ganda). Dalam hal parameter dilewatkan pada saat runtime oleh pengguna (sebagai parameter fungsi misalnya) Anda dapat mengatakan: PILIH ... DARI crosstab ('pilih bagian :: teks, status, hitung :: teks dari t di mana status =' ' '|| par_active ||' '' ', 2) AS ... (tiga tanda kutip di sini!). Dalam BIRT ini juga berfungsi dengan? placeholder.
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
araqnid
sumber
1
Dapatkah seseorang menjelaskan apa fungsi tab silang dalam modul tablefunc menambah jawaban ini, yang keduanya bekerja dengan baik, dan bagi pikiran saya lebih mudah dimengerti?
John Powell
4
@ JohnBarça: Kasus sederhana seperti ini dapat dengan mudah diselesaikan dengan pernyataan KASUS. Namun, ini menjadi sangat cepat dengan atribut yang lebih banyak dan / atau tipe data lainnya dari hanya bilangan bulat. Sebagai tambahan: formulir ini menggunakan fungsi agregat sum(), akan lebih baik untuk menggunakan min()atau max()tidak ada ELSEyang berfungsi textjuga. Tetapi ini memiliki efek yang sedikit berbeda daripada corosstab(), yang hanya menggunakan nilai "pertama" per atribut. Tidak masalah asalkan hanya ada satu. Akhirnya, kinerja juga relevan. crosstab()ditulis dalam C dan dioptimalkan untuk tugas tersebut.
Erwin Brandstetter
Ini tidak berfungsi untuk saya, untuk postgresql. Saya mendapatkan kesalahanERROR: 42803: aggregate function calls may not be nested
Audrey
1
@ Audrey Anda tidak menjalankan SQL yang sama?
2
Pertimbangkan menambahkan penjelasan vs hanya satu blok kode
Daniel L. VanDenBosch
10

Solusi dengan agregasi 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
Milos
sumber
Terima kasih, ini membantu saya dengan masalah terkait.
JeffCharter
1

Maaf ini tidak lengkap karena saya tidak bisa mengujinya di sini, tetapi mungkin membuat Anda pergi ke arah yang benar. Saya menerjemahkan dari sesuatu yang saya gunakan yang membuat pertanyaan serupa:

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;

Kode tempat saya bekerja adalah:

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;

yang akan mengembalikan typeID, tawaran harga tertinggi dan harga terendah yang diminta dan perbedaan di antara keduanya (perbedaan positif berarti sesuatu dapat dibeli dengan harga lebih murah daripada yang bisa dijual).

LanceH
sumber
1
Anda kehilangan klausa, jika tidak ini benar. Rencana penjelasan sangat berbeda pada sistem saya - fungsi tab silang memiliki biaya 22,5 sedangkan pendekatan KIRI GABUNG adalah sekitar 4 kali lebih mahal dengan biaya 91,38. Ini juga menghasilkan sekitar dua kali lebih banyak membaca fisik dan melakukan hash joins - yang bisa sangat mahal dibandingkan dengan tipe join lainnya.
Jeremiah Peschka
Terima kasih Jeremiah, itu baik untuk diketahui. Saya telah mengangkat jawaban yang lain, tetapi komentar Anda layak disimpan sehingga saya tidak akan menghapus yang ini.
LanceH
-1

Crosstabfungsi tersedia di bawah tablefuncekstensi. Anda harus membuat ekstensi ini satu kali untuk basis data.

BUAT EKSTENSI tablefunc;

Anda dapat menggunakan kode di bawah ini untuk membuat tabel pivot menggunakan tab silang:

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)
Lekshmi Kurup
sumber
1
Jawaban ini tidak menambahkan apa pun dari jawaban yang sudah ada sebelumnya.
Erwin Brandstetter