Динамическая альтернатива pivot с CASE и GROUP BY
у меня есть таблица, которая выглядит так:
id feh bar
1 10 A
2 20 A
3 3 B
4 4 B
5 5 C
6 6 D
7 7 D
8 8 D
и я хочу, чтобы это выглядеть так:
bar val1 val2 val3
A 10 20
B 3 4
C 5
D 6 7 8
у меня есть этот запрос, который делает это:
SELECT bar,
MAX(CASE WHEN abc."row" = 1 THEN feh ELSE NULL END) AS "val1",
MAX(CASE WHEN abc."row" = 2 THEN feh ELSE NULL END) AS "val2",
MAX(CASE WHEN abc."row" = 3 THEN feh ELSE NULL END) AS "val3"
FROM
(
SELECT bar, feh, row_number() OVER (partition by bar) as row
FROM "Foo"
) abc
GROUP BY bar
Это очень хитрый подход и становится громоздким, если нужно создать много новых столбцов. Мне было интересно, если CASE
операторы можно сделать лучше, чтобы сделать этот запрос более динамичным? Кроме того, я хотел бы увидеть другие подходы к этому.
5 ответов
если вы не установили дополнительный модуль 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-параметр формы мы можем просто предоставить фиктивный столбец с фиктивным значением, служащим категорией. Значение все равно игнорируется.
это один из самых редких случаях здесь второй параметр на с именами, определяющими порядок значений в результате, нам понадобится 2-параметр формы of crosstab()
. Здесь я синтезирую столбец категории с помощью функции window row_number()
, база crosstab()
on:
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?
остальное запускать-оф-мельница. Найдите больше объяснений и ссылок в этих тесно связанных ответах.
основные:
читать это, если вы не знакомый с
хотя это старый вопрос, я хотел бы добавить еще одно решение, ставшее возможным благодаря недавним улучшениям в PostgreSQL. Это решение достигает той же цели возвращения структурированного результата из динамического набора данных без использования функции перекрестной таблицы вообще.
чтобы проиллюстрировать, вы спросили для метода транспонирования данных со следующей структурой: в следующем формате: условное решение-это умный (и невероятно грамотный) подход к созданию динамических перекрестных запросов, который описан в мельчайших подробностях в ответ Эрвин задач по. однако, если ваш конкретный вариант использования достаточно гибок, чтобы принять немного другой формат результата, возможно другое решение, которое обрабатывает динамический пивоты красиво. Эта техника, о которой я узнал здесь использует новый PostgreSQL я буду использовать г-н Брандштеттер это "проще тест", чтобы проиллюстрировать: С помощью выходы: как вы можете видеть, эта функция работает, создавая пары ключ / значение в объекте JSON из хотя этот результирующий набор, очевидно, выглядит по-другому, я считаю, что он действительно удовлетворит многие (если не большинство) реальных случаев использования, особенно те, где данные требуют динамически генерируемого разворота, или где результирующие данные потребляются родительским приложением (например, необходимо переформатировать для передачи в http-ответе). преимущества такого подхода: более чистый синтаксис. я думаю, все согласятся, что синтаксис для этого подхода намного чище и легче понять, чем даже самые основные примеры перекрестных таблиц. полностью динамическим. нет информации о базовой данные необходимо уточнить заранее. Ни имена столбцов, ни их типы данных должны быть известны заранее. обрабатывает большое количество столбцов. поскольку сводные данные сохраняются как один столбец jsonb, вы не столкнетесь с ограничением столбца PostgreSQL (≤1,600 столбцов, я считаю). Существует еще ограничение, но я считаю, что это то же самое, что и для текстовых полей: 1 ГБ на созданный объект JSON (пожалуйста, исправьте меня, если я ошибаюсь). Это много пары ключ/значение! упрощенная работа с данными. я считаю, что создание данных JSON в БД упростит (и, вероятно, ускорит) процесс преобразования данных в родительских приложениях. (Вы заметите, что целочисленные данные в нашем тестовом примере были правильно сохранены как таковые в результирующих объектах JSON. PostgreSQL обрабатывает это путем автоматического преобразования своих внутренних типов данных в JSON в соответствии со спецификацией JSON.) Эта воля эффективно устраните необходимость ручного приведения данных, передаваемых родительским приложениям: все это можно делегировать собственному парсеру JSON приложения. различия (и возможные ошибки): это выглядит по-другому. нельзя отрицать, что результаты этого подхода выглядят по-другому. Объект JSON не так красив, как результирующий набор перекрестных таблиц; однако различия являются чисто косметическими. Тот же информация производится и в формате, который, вероятно,больше дружелюбный для потребления родительского приложения. отсутствуют ключи. отсутствующие значения в подходе перекрестной таблицы заполняются нулями, в то время как объекты JSON просто отсутствуют применимые ключи. Вам придется решить для себя, Является ли это приемлемым компромиссом для вашего случая использования. Мне кажется, что любая попытка решить эту проблему в PostgreSQL будет значительно усложняют процесс и, вероятно, предполагают некоторую интроспекцию в виде дополнительных запросов. порядок ключей не сохраняется. я не знаю, можно ли это решить в PostgreSQL, но эта проблема в основном косметическая, так как любые родительские приложения либо вряд ли будут полагаться на порядок ключей, либо имеют возможность определять правильный порядок ключей другими средствами. В худшем случае, вероятно, потребуется только запрос добавления база данных. вывод мне очень любопытно услышать мнения других (особенно @Erwinbrandstetter'S) об этом подходе, особенно в том, что касается производительности. Когда я обнаружил этот подход в блоге Эндрю Бендера, это было похоже на удар в висок. Какой прекрасный способ по-новому подойти к сложной проблеме в PostrgeSQL. Он отлично решил мой вариант использования, и я считаю, что он также будет служите и многим другим.id feh bar
1 10 A
2 20 A
3 3 B
4 4 B
5 5 C
6 6 D
7 7 D
8 8 D
bar val1 val2 val3
A 10 20
B 3 4
C 5
D 6 7 8
jsonb_object_agg
функция для построения поворотных данных на лету в виде объекта JSON.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);
jsonb_object_agg
функция, мы можем создать необходимый сводный набор результатов с этой выразительной красотой:SELECT
row_name AS bar,
json_object_agg(attrib, val) AS data
FROM tbl
GROUP BY row_name
ORDER BY row_name;
bar | data
-----+----------------------------------------
A | { "val1" : 10, "val2" : 20 }
B | { "val1" : 3, "val2" : 4 }
C | { "val1" : 5 }
D | { "val3" : 8, "val1" : 6, "val2" : 7 }
attrib
и value
столбцы в образце данных, сгруппированные по row_name
.
Это полный @Damian хороший ответ. Я уже предложил подход JSON в других ответах, прежде чем 9.6 удобно
в вашем случае я думаю, что массив хорош. SQL Fiddle
select
bar,
feh || array_fill(null::int, array[c - array_length(feh, 1)]) feh
from
(
select bar, array_agg(feh) feh
from foo
group by bar
) s
cross join (
select count(*)::int c
from foo
group by bar
order by c desc limit 1
) c(c)
;
bar | feh
-----+---------------
A | {10,20,NULL}
B | {3,4,NULL}
C | {5,NULL,NULL}
D | {6,7,8}
Я сожалею о возвращении в прошлом, но решение "динамическая перекрестная таблица" возвращает ошибочную таблицу результатов. Таким образом, значения valN ошибочно "выровнены влево" и не соответствуют именам столбцов. Когда входная таблица имеет " отверстия "в значениях, например," C " имеет val1 и val3, но не val2. Это приводит к ошибке: val3 значение будет варьироваться в столбце val2 (т. е. следующий свободный столбец) в конечной таблице.
CREATE TEMP TABLE tbl (row_name text, attrib text, val int);
INSERT INTO tbl (row_name, attrib, val) VALUES ('C', 'val1', 5) ('C', 'val3', 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
C | 5| 7 |
для того, чтобы вернуть правильные клетки с "отверстия" в правом столбце, запрос перекрестной таблицы требует 2-го выбора в перекрестной таблице, что-то вроде этого "crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2', 'select distinct row_name from tbl order by 1')"