PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Alternatif dinamis untuk pivot dengan CASE dan GROUP BY

Jika Anda belum menginstal modul tambahan tablefunc , jalankan perintah ini sekali per basis data:

CREATE EXTENSION tablefunc;

Jawaban pertanyaan

Solusi tab silang yang sangat mendasar untuk kasus Anda:

SELECT * FROM crosstab(
  'SELECT bar, 1 AS cat, feh
   FROM   tbl_org
   ORDER  BY bar, feh')
 AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Kesulitan khusus di sini adalah, bahwa tidak ada kategori (cat ) di tabel dasar. Untuk formulir 1 parameter dasar kami hanya dapat menyediakan kolom dummy dengan nilai dummy yang berfungsi sebagai kategori. Nilainya tetap diabaikan.

Ini adalah salah satu kasus langka di mana parameter kedua untuk crosstab() fungsi tidak diperlukan , karena semua NULL nilai hanya muncul di kolom menjuntai ke kanan menurut definisi masalah ini. Dan urutannya dapat ditentukan oleh nilai .

Jika kita memiliki kategori yang sebenarnya kolom dengan nama yang menentukan urutan nilai dalam hasil, kita memerlukan formulir 2 parameter dari crosstab() . Di sini saya mensintesis kolom kategori dengan bantuan fungsi jendela row_number() , untuk mendasarkan crosstab() pada:

SELECT * FROM crosstab(
   $$
   SELECT bar, val, feh
   FROM  (
      SELECT *, 'val' || row_number() OVER (PARTITION BY bar ORDER BY feh) AS val
      FROM tbl_org
      ) x
   ORDER BY 1, 2
   $$
 , $$VALUES ('val1'), ('val2'), ('val3')$$         -- more columns?
) AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Sisanya cukup banyak run-of-the-mill. Temukan lebih banyak penjelasan dan tautan dalam jawaban yang terkait erat ini.

Dasar-dasar:
Baca ini dulu jika Anda tidak terbiasa dengan crosstab() fungsi!

  • Kueri Tab Silang PostgreSQL

Lanjutan:

  • Pivot pada Beberapa Kolom menggunakan Tablefunc
  • Menggabungkan tabel dan mengubah log menjadi tampilan di PostgreSQL

Penyiapan pengujian yang tepat

Begitulah cara Anda harus memberikan kasus uji untuk memulai dengan:

CREATE TEMP TABLE tbl_org (id int, feh int, bar text);
INSERT INTO tbl_org (id, feh, bar) VALUES
   (1, 10, 'A')
 , (2, 20, 'A')
 , (3,  3, 'B')
 , (4,  4, 'B')
 , (5,  5, 'C')
 , (6,  6, 'D')
 , (7,  7, 'D')
 , (8,  8, 'D');

Tab silang dinamis?

Tidak terlalu dinamis , namun, seperti yang dikomentari @Clodoaldo. Jenis pengembalian dinamis sulit dicapai dengan plpgsql. Tapi ada ada cara mengatasinya - dengan beberapa batasan .

Agar tidak semakin memperumit sisanya, saya tunjukkan dengan lebih sederhana kasus uji:

CREATE TEMP TABLE tbl (row_name text, attrib text, val int);
INSERT INTO tbl (row_name, attrib, val) VALUES
   ('A', 'val1', 10)
 , ('A', 'val2', 20)
 , ('B', 'val1', 3)
 , ('B', 'val2', 4)
 , ('C', 'val1', 5)
 , ('D', 'val3', 8)
 , ('D', 'val1', 6)
 , ('D', 'val2', 7);

Telepon:

SELECT * FROM crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2')
AS ct (row_name text, val1 int, val2 int, val3 int);

Pengembalian:

 row_name | val1 | val2 | val3
----------+------+------+------
 A        | 10   | 20   |
 B        |  3   |  4   |
 C        |  5   |      |
 D        |  6   |  7   |  8

Fitur bawaan tablefunc modul

Modul tablefunc menyediakan infrastruktur sederhana untuk crosstab() generic generik panggilan tanpa memberikan daftar definisi kolom. Sejumlah fungsi ditulis dalam C (biasanya sangat cepat):

crosstabN()

crosstab1() - crosstab4() telah ditentukan sebelumnya. Satu poin kecil:mereka membutuhkan dan mengembalikan semua text . Jadi kita perlu mentransmisikan integer nilai-nilai. Tapi itu menyederhanakan panggilan:

SELECT * FROM crosstab4('SELECT row_name, attrib, val::text  -- cast!
                         FROM tbl ORDER BY 1,2')

Hasil:

 row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
 A        | 10         | 20         |            |
 B        | 3          | 4          |            |
 C        | 5          |            |            |
 D        | 6          | 7          | 8          |

Kustom crosstab() fungsi

Untuk kolom lainnya atau tipe data lainnya , kami membuat jenis komposit kami sendiri dan fungsi (sekali).
Ketik:

CREATE TYPE tablefunc_crosstab_int_5 AS (
  row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);

Fungsi:

CREATE OR REPLACE FUNCTION crosstab_int_5(text)
  RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;

Telepon:

SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val   -- no cast!
                              FROM tbl ORDER BY 1,2');

Hasil:

 row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
 A        |   10 |   20 |      |      |
 B        |    3 |    4 |      |      |
 C        |    5 |      |      |      |
 D        |    6 |    7 |    8 |      |

Satu polimorfik, fungsi dinamis untuk semua

Ini melampaui apa yang dicakup oleh tablefunc modul.
Untuk membuat tipe pengembalian dinamis, saya menggunakan tipe polimorfik dengan teknik yang dirinci dalam jawaban terkait ini:

  • Memfaktorkan ulang fungsi PL/pgSQL untuk mengembalikan output dari berbagai kueri SELECT

Formulir 1 parameter:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L) t(%s)'
                , _qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

Overload dengan varian ini untuk formulir 2 parameter:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _cat_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L, %L) t(%s)'
                , _qry, _cat_qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

pg_typeof(_rowtype)::text::regclass :Ada tipe baris yang ditentukan untuk setiap tipe komposit yang ditentukan pengguna, sehingga atribut (kolom) tercantum dalam katalog sistem pg_attribute . Jalur cepat untuk mendapatkannya:gunakan tipe terdaftar (regtype ) menjadi text dan berikan text ini ke regclass .

Buat tipe komposit sekali:

Anda perlu menentukan sekali setiap jenis pengembalian yang akan Anda gunakan:

CREATE TYPE tablefunc_crosstab_int_3 AS (
    row_name text, val1 int, val2 int, val3 int);

CREATE TYPE tablefunc_crosstab_int_4 AS (
    row_name text, val1 int, val2 int, val3 int, val4 int);

...

Untuk panggilan ad-hoc, Anda juga dapat membuat tabel sementara dengan efek (sementara) yang sama:

CREATE TEMP TABLE temp_xtype7 AS (
    row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);

Atau gunakan jenis tabel yang ada, tampilan, atau tampilan terwujud jika tersedia.

Telepon

Menggunakan jenis baris di atas:

Formulir 1 parameter (tidak ada nilai yang hilang):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
 , NULL::tablefunc_crosstab_int_3);

Formulir 2 parameter (beberapa nilai dapat hilang):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
 , $$VALUES ('val1'), ('val2'), ('val3')$$
 , NULL::tablefunc_crosstab_int_3);

satu fungsi . ini berfungsi untuk semua jenis pengembalian, sedangkan crosstabN() kerangka kerja yang disediakan oleh tablefunc modul membutuhkan fungsi terpisah untuk masing-masingnya.
Jika Anda telah menamai tipe Anda secara berurutan seperti yang ditunjukkan di atas, Anda hanya perlu mengganti nomor yang dicetak tebal. Untuk menemukan jumlah maksimum kategori dalam tabel dasar:

SELECT max(count(*)) OVER () FROM tbl  -- returns 3
GROUP  BY row_name
LIMIT  1;

Itu kira-kira sedinamis ini jika Anda menginginkan kolom individual . Array seperti yang ditunjukkan oleh @Clocoaldo atau representasi teks sederhana atau hasilnya dibungkus dengan jenis dokumen seperti json atau hstore dapat bekerja untuk sejumlah kategori secara dinamis.

Penafian:
Itu selalu berpotensi berbahaya ketika input pengguna diubah menjadi kode. Pastikan ini tidak dapat digunakan untuk injeksi SQL. Jangan terima masukan dari pengguna yang tidak dipercaya (secara langsung).

Telepon untuk pertanyaan awal:

SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
                       , NULL::tablefunc_crosstab_int_3);


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. PostgreSQL Hosting Terkelola Sepenuhnya di AWS dan Azure Diluncurkan Tepat Waktu Untuk Migrasi Lama

  2. Bisakah PostgreSQL melakukan penggabungan antara dua prosedur tersimpan SQL Server?

  3. Urutan PostgreSQL berdasarkan kolom lain

  4. Pernyataan GROUP BY + CASE

  5. PSQLException:ERROR:relasi TABLE_NAME tidak ada