Кто-нибудь знает, как создавать кросс-таблицы в 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
Возможно ли это?
Установите дополнительный модуль 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);
Возвращает:
Section | Active | Inactive ---------+--------+---------- A | 1 | 2 B | 4 | 5 C | 7 | -- !!
C
: значение 7
заполняется для первого столбца. Иногда это поведение желательно, но не для этого варианта использования. crosstab(text, text)
с двумя входными параметрами:
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);
Возвращает:
Section | Active | Inactive ---------+--------+---------- A | 1 | 2 B | 4 | 5 C | | 7 -- !!
Обратите внимание на правильный результат для C
Второй параметр может быть любым запросом, который возвращает одну строку для каждого атрибута, соответствующую порядку определения столбца в конце. Часто вам нужно запросить отдельные атрибуты из таблицы ниже:
'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
Это в руководстве.
Поскольку вы все равно должны указывать все столбцы в списке определения столбцов (за исключением заранее определенных вариантов crosstab N()
- crosstab N()
), обычно более эффективно предоставлять короткий список в выражении 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 <> скрипка здесь
Pivot на нескольких столбцах с помощью Tablefunc - также демонстрирует упомянутые "дополнительные столбцы",
Динамическая альтернатива повороту с помощью CASE и GROUP BY
\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 BY
требуется в однопараметрической форме crosstab()
. Руководство:
На практике SQL-запрос должен всегда указывать
ORDER BY 1,2
чтобы убедиться, что строки ввода правильно упорядочены
crosstab
.
In practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
Вы можете использовать функцию 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 section,
SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active,
SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive
FROM t
GROUP BY section
sum()
, было бы лучше использовать min()
или max()
и не использовать ELSE
который также работает с text
. Но это имеет несколько иные эффекты, чем corosstab()
, который использует только «первое» значение для каждого атрибута. Не имеет значения, пока может быть только один. Наконец, производительность тоже актуальна. crosstab()
написан на C и оптимизирован для этой задачи.
Решение с агрегацией 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
Crosstab
функция доступна под tablefunc
расширения. Вам нужно будет создать это расширение один раз для базы данных.
CREATE EXTENSION tablefunc
;
Вы можете использовать приведенный ниже код для создания сводной таблицы с использованием кросс-вкладок:
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)
Извините, это не полно, потому что я не могу проверить его здесь, но это может привести вас в правильном направлении. Я переводю то, что я использую, что делает аналогичный запрос:
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;
который вернет типID, самую высокую цену и самую низкую цену и разницу между ними (положительная разница означает, что что-то можно купить за меньшее, чем можно продать).