Это то же самое в ответе zero323, но в способе sql query
Предполагая, что датафрейм создан и зарегистрирован как
df.createOrReplaceTempView("table")
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|0 |cat26 |30.9 |
//|0 |cat13 |22.1 |
//|0 |cat95 |19.6 |
//|0 |cat105 |1.3 |
//|1 |cat67 |28.5 |
//|1 |cat4 |26.8 |
//|1 |cat13 |12.6 |
//|1 |cat23 |5.3 |
//|2 |cat56 |39.6 |
//|2 |cat40 |29.7 |
//|2 |cat187 |27.9 |
//|2 |cat68 |9.8 |
//|3 |cat8 |35.6 |
//+----+--------+----------+
Функция окна:
sqlContext.sql("select Hour, Category, TotalValue from (select *, row_number() OVER (PARTITION BY Hour ORDER BY TotalValue DESC) as rn FROM table) tmp where rn = 1").show(false)
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|1 |cat67 |28.5 |
//|3 |cat8 |35.6 |
//|2 |cat56 |39.6 |
//|0 |cat26 |30.9 |
//+----+--------+----------+
Простая агрегация SQL, за которой следует соединение:
sqlContext.sql("select Hour, first(Category) as Category, first(TotalValue) as TotalValue from " +
"(select Hour, Category, TotalValue from table tmp1 " +
"join " +
"(select Hour as max_hour, max(TotalValue) as max_value from table group by Hour) tmp2 " +
"on " +
"tmp1.Hour = tmp2.max_hour and tmp1.TotalValue = tmp2.max_value) tmp3 " +
"group by tmp3.Hour")
.show(false)
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|1 |cat67 |28.5 |
//|3 |cat8 |35.6 |
//|2 |cat56 |39.6 |
//|0 |cat26 |30.9 |
//+----+--------+----------+
Использование упорядочения по структурам:
sqlContext.sql("select Hour, vs.Category, vs.TotalValue from (select Hour, max(struct(TotalValue, Category)) as vs from table group by Hour)").show(false)
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|1 |cat67 |28.5 |
//|3 |cat8 |35.6 |
//|2 |cat56 |39.6 |
//|0 |cat26 |30.9 |
//+----+--------+----------+
Способ DataSets и дон 't do s такие же, как в исходном ответе
Если вы еще не установили дополнительный модуль 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()
!
Дополнительно:
Поэтому, чтобы не усложнять остальные, я демонстрирую с помощью более простого :
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
. Чтобы сделать динамический тип возвращаемого значения, я использую полиморфный тип с метод, подробно описанный в этом связанном ответе:
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;
Перегрузка с этим вариантом для двухпараметрической формы:
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);
...
Для специальных вызовов вы также можете просто создать временную таблицу для того же (временного) эффекта:
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);
В вашем случае, я думаю, массив хорош. 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}
Это для завершения @Damian хорошего ответа. Я уже предлагал подход JSON в других ответах до удобной функции json_object_agg
9.6. Это просто требует больше работы с предыдущим набором инструментов.
Два из приведенных возможных недостатков на самом деле нет. При необходимости случайный порядок ключей трижды корректируется. Отсутствующие ключи, если необходимо, занимают почти тривиальное количество кода, подлежащего устранению:
select
row_name as bar,
json_object_agg(attrib, val order by attrib) as data
from
tbl
right join
(
(select distinct row_name from tbl) a
cross join
(select distinct attrib from tbl) b
) c using (row_name, attrib)
group by row_name
order by row_name
;
bar | data
-----+----------------------------------------------
a | { "val1" : 10, "val2" : 20, "val3" : null }
b | { "val1" : 3, "val2" : 4, "val3" : null }
c | { "val1" : 5, "val2" : null, "val3" : null }
d | { "val1" : 6, "val2" : 7, "val3" : 8 }
Для конечного пользователя запроса, который понимает JSON, нет недостатков. Единственное, что он не может быть использован в качестве источника таблицы.
Хотя это старый вопрос, я хотел бы добавить еще одно решение, которое стало возможным благодаря недавним улучшениям в PostgreSQL. Это решение достигает той же цели - вернуть структурированный результат из динамического набора данных без использования функции кросс-таблицы вообще. Другими словами, это хороший пример повторного изучения непреднамеренных и неявных предположений, которые мешают нам открывать новые решения старых проблем. ;)
Чтобы проиллюстрировать, вы попросили метод переноса данных со следующей структурой:
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
традиционное решение является умным (и невероятно осведомленным) подходом к созданию динамических кросс-табличных запросов, которые объясняются в изящной детализации в ответе Эрвина Брандстретера.
Однако, если ваш конкретный вариант использования достаточно гибкий, чтобы принять несколько иной результат формат, тогда возможно другое решение, которое красиво обрабатывает динамические повороты. Этот метод, о котором я узнал здесь
использует новый PostgreSQL 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 }
Как вы можете видеть , эта функция работает путем создания пар ключ / значение в объекте JSON из столбцов attrib
и value
в образцах данных, все сгруппированы по row_name
.
Хотя этот набор результатов явно выглядит по-разному , Я считаю, что он действительно удовлетворит многие (если не большинство) случаи использования в реальном мире, особенно те, где для данных требуется динамически сгенерированный опорный стержень, или когда результирующие данные потребляются родительским приложением (например, требуется переформатировать для передача в HTTP-ответе).
Преимущества этого pproach:
Различия (и возможные недостатки):
Заключение
Мне очень любопытно услышать мнения других (особенно @ ErwinBrandstetter's) об этом подход, особенно в том, что касается производительности. Когда я обнаружил этот подход в блоге Эндрю Бендера, это было похоже на попадание в сторону головы. Какой прекрасный способ сделать новый подход к сложной проблеме в PostrgeSQL. Он отлично решил мой прецедент, и я считаю, что он также будет служить многим другим.
Прошу прощения за возвращение в прошлом, но решение «Динамический кросс-лист» возвращает ошибочную таблицу результатов. Таким образом, значения 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-го SELECT в кросс-таблице, что-то вроде этого "crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2', 'select distinct row_name from tbl order by 1')"