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