Ако не сте инсталирали допълнителния модул tablefunc , изпълнете тази команда веднъж за база данни:
CREATE EXTENSION tablefunc;
Отговор на въпрос
Много основно решение за кръстосани таблици за вашия случай:
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?
Специалната трудност ето, че няма категория (cat
) в основната таблица. За основния формуляр с 1 параметър можем просто да предоставим фиктивна колона с фиктивна стойност, служеща като категория. Стойността така или иначе се игнорира.
Това е един отредките случаи където е вторият параметър за crosstab()
функцията не е необходима , защото всички NULL
стойностите се появяват само в висящи колони вдясно по дефиниция на този проблем. И редът може да се определи от стойността .
Ако имахме действителна категория колона с имена, определящи реда на стойностите в резултата, ще ни е необходим формуляр с 2 параметъра на crosstab()
. Тук синтезирам колона за категория с помощта на функцията на прозореца row_number()
, към базата crosstab()
на:
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?
Останалото е доста безпроблемно. Намерете още обяснения и връзки в тези тясно свързани отговори.
Основни положения:
Прочетете това първо, ако не сте запознати с crosstab()
функция!
- PostgreSQL Crosstab Query
Разширени:
- Завъртане на множество колони с помощта на Tablefunc
- Обединете таблица и регистър на промените в изглед в PostgreSQL
Правилна настройка на теста
Ето как трябва да предоставите тестов случай за начало:
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');
Динамична кръстосана таблица?
Не много динамичен , все пак, както коментира @Clodoaldo. Типовете на динамично връщане са трудни за постигане с plpgsql. Но ги има начини за заобикаляне - с някои ограничения .
За да не усложнявам допълнително останалото, демонстрирам спо-просто тестов случай:
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);
Обадете се:
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);
Връща:
row_name | val1 | val2 | val3
----------+------+------+------
A | 10 | 20 |
B | 3 | 4 |
C | 5 | |
D | 6 | 7 | 8
Вградена функция на tablefunc
модул
Модулът tablefunc предоставя проста инфраструктура за общ crosstab()
повиквания без предоставяне на списък с дефиниции на колони. Редица функции, написани на C
(обикновено много бързо):
crosstabN()
crosstab1()
- crosstab4()
са предварително дефинирани. Една малка точка:те изискват и връщат целия text
. Така че трябва да изведем нашето integer
стойности. Но това опростява обаждането:
SELECT * FROM crosstab4('SELECT row_name, attrib, val::text -- cast!
FROM tbl ORDER BY 1,2')
Резултат:
row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
A | 10 | 20 | |
B | 3 | 4 | |
C | 5 | | |
D | 6 | 7 | 8 |
Персонализиран crosstab()
функция
За още колони или други типове данни , създаваме свой собствен композитен тип и функция (веднъж).
Въведете:
CREATE TYPE tablefunc_crosstab_int_5 AS (
row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);
Функция:
CREATE OR REPLACE FUNCTION crosstab_int_5(text)
RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;
Обадете се:
SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val -- no cast!
FROM tbl ORDER BY 1,2');
Резултат:
row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
A | 10 | 20 | | |
B | 3 | 4 | | |
C | 5 | | | |
D | 6 | 7 | 8 | |
Едно полиморфна, динамична функция за всички
Това надхвърля това, което се покрива от tablefunc
модул.
За да направя връщания тип динамичен, използвам полиморфен тип с техника, описана подробно в този свързан отговор:
- Рефакторирайте функция PL/pgSQL, за да върнете резултатите от различни SELECT заявки
Формуляр с 1 параметър:
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;
Претоварване с този вариант за формуляра с 2 параметъра:
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
:Има дефиниран тип ред за всеки дефиниран от потребителя съставен тип, така че атрибутите (колони) са изброени в системния каталог pg_attribute
. Бързата лента за получаване:прехвърляне на регистрирания тип (regtype
) към text
и пуснете този text
към regclass
.
Създаване на съставни типове веднъж:
Трябва да дефинирате веднъж всеки тип връщане, който ще използвате:
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);
...
За ad hoc разговори можете също да създадете временна таблица със същия (временен) ефект:
CREATE TEMP TABLE temp_xtype7 AS (
row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);
Или използвайте типа на съществуваща таблица, изглед или материализиран изглед, ако е наличен.
Обаждане
Използване на горните типове редове:
Формуляр с 1 параметър (без липсващи стойности):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);
Формуляр с 2 параметъра (някои стойности може да липсват):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
, $$VALUES ('val1'), ('val2'), ('val3')$$
, NULL::tablefunc_crosstab_int_3);
Тази една функция работи за всички типове връщане, докато crosstabN()
рамка, предоставена от tablefunc
модулът се нуждае от отделна функция за всеки.
Ако сте именували типовете си в последователност, както е показано по-горе, трябва само да замените удебеления номер. За да намерите максималния брой категории в основната таблица:
SELECT max(count(*)) OVER () FROM tbl -- returns 3
GROUP BY row_name
LIMIT 1;
Това е приблизително толкова динамично, колкото става, ако искате отделни колони . Масиви като демонстрирани от @Clocoaldo или просто текстово представяне или резултатът, увит в тип документ като json
или hstore
може да работи за произволен брой категории динамично.
Отказ от отговорност:
Винаги е потенциално опасно, когато въведеното от потребителя се преобразува в код. Уверете се, че това не може да се използва за SQL инжектиране. Не приемайте информация от ненадеждни потребители (директно).
Обадете се за оригинален въпрос:
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);