Перекрестный запрос PostgreSQL
-
25-09-2019 - |
Вопрос
Кто-нибудь знает, как создавать перекрестные запросы в 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)
с 2 входные параметры:
SELECT *
FROM crosstab(
'SELECT section, status, ct
FROM tbl
ORDER BY 1,2' -- could also just be "ORDER BY 1" here
, $$VALUES ('Активный'::текст), ('Неактивный')$$
) 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Н()
вариантов), обычно более эффективно предоставить краткий список в видеVALUES
выражение, подобное продемонстрированному:$$VALUES ('Active'::text), ('Inactive')$$)
Или (нет в инструкции):
$$SELECT unnest('{Active,Inactive}'::text[])$$ -- short syntax for long lists
я использовал котировка доллара чтобы облегчить цитирование.
Вы даже можете выводить столбцы с помощью другой типы данных с
crosstab(text, text)
- до тех пор, пока текстовое представление столбца значений является допустимым вводом для целевого типа.Таким образом, у вас могут быть атрибуты разного типа и вывода.text
,date
,numeric
и т. д.для соответствующих атрибутов.В конце есть пример кода. главаcrosstab(text, text)
в руководстве.
БД<>скрипка здесь
Расширенные примеры
Сводка по нескольким столбцам с помощью Tablefunc - также демонстрация упомянутых "дополнительных столбцов"
Динамическая альтернатива повороту с помощью CASE и GROUP BY
\crosstabview
в psql
Постгрес 9.6 добавил эту метакоманду в интерактивный терминал по умолчанию psql.Вы можете запустить запрос, который будете использовать в качестве первого. crosstab()
параметр и передать его в \crosstabview
(сразу или на следующем этапе).Нравиться:
db=> SELECT section, status, ct FROM tbl \crosstabview
Результат аналогичен приведенному выше, но это функция представления на стороне клиента исключительно.Входные строки обрабатываются немного по-другому, следовательно ORDER BY
не требуется.Подробности для \crosstabview
в руководстве. Внизу этой страницы приведены дополнительные примеры кода.
Соответствующий ответ на dba.SE от Даниэля Верите (автора функции psql):
А ранее принятый ответ устарело.
Вариант функции
crosstab(text, integer)
устарело.Второйinteger
параметр игнорируется.Я цитирую текущий руководство:crosstab(text sql, int N)
...Устаревшая версия
crosstab(text)
.ПараметрN
теперь игнорируется, поскольку количество столбцов значений всегда определяется призывом к запросуНенужное кастинг и переименование.
Это не удастся, если строка не имеет всех атрибутов.См. безопасный вариант с двумя входными параметрами выше, чтобы правильно обработать отсутствующие атрибуты.
ORDER BY
требуется в однопараметрической формеcrosstab()
. Руководство:На практике SQL-запрос всегда должен указывать
ORDER BY 1,2
Чтобы убедиться, что входные строки правильно упорядочены
Другие советы
Вы можете использовать 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, --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
Решение с агрегацией 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
Извините, это не завершено, потому что я не могу проверить это здесь, но он может выключить вас в правильном направлении. Я переводю от чего-то, что я использую, это делает подобный запрос:
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;
Что вернется к типу, самая высокая цена заявки и самая низкая цена, и разница между двумя (положительная разница будет означать, что что-то может быть куплено за меньшее, чем она может быть продана).
Crosstab
Функция доступна под tablefunc
расширение. Вам придется создать это расширение один раз для базы данных.
Создать расширение tablefunc
;
Вы можете использовать код ниже для создания таблицы Pivot, используя вкладку CORD:
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)