Распространение таблицы Postgres & ldquo; Wide & rdquo; [Дубликат]

Это то же самое в ответе 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 такие же, как в исходном ответе

21
задан Erwin Brandstetter 20 March 2013 в 04:44
поделиться

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-параметрической формы мы можем просто предоставить фиктивный столбец с фиктивным значением, служащим в качестве категории. В любом случае значение игнорируется.

Это один из редких случаев, когда второй параметр для функции 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()!

Дополнительно:

Правильно

Неправильно динамический

], но, поскольку @Clodoaldo прокомментировал . PLGsql трудно достичь динамических типов возврата. Но там есть пути вокруг него - с некоторыми ограничениями .

Поэтому, чтобы не усложнять остальные, я демонстрирую с помощью более простого :

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);
46
ответ дан Erwin Brandstetter 25 August 2018 в 07:28
поделиться

В вашем случае, я думаю, массив хорош. 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}
4
ответ дан Clodoaldo Neto 25 August 2018 в 07:28
поделиться

Это для завершения @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, нет недостатков. Единственное, что он не может быть использован в качестве источника таблицы.

5
ответ дан Community 25 August 2018 в 07:28
поделиться

Хотя это старый вопрос, я хотел бы добавить еще одно решение, которое стало возможным благодаря недавним улучшениям в 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:

  • Синтаксис чистых. Я думаю, что все согласятся с тем, что синтаксис этого подхода намного понятнее и понятнее, чем даже самые основные примеры кросс-таблицы.
  • Полностью динамический. Не нужно заранее указывать информацию о базовых данных. Ни имена столбцов, ни их типы данных не должны быть известны заранее.
  • Обрабатывает большое количество столбцов. Поскольку скользящие данные сохраняются как один столбец jsonb, вы не столкнетесь с лимитом столбца PostgreSQL (я полагаю, что ≤1 600 столбцов). По-прежнему существует предел, но я считаю, что это то же самое, что и для текстовых полей: 1 ГБ на созданный объект JSON (пожалуйста, поправьте меня, если я ошибаюсь). Это много пар ключ / значение!
  • Упрощенная обработка данных. Я считаю, что создание данных JSON в БД упростит (и, вероятно, ускорит) процесс преобразования данных в родительских приложениях. (Вы заметите, что целочисленные данные в нашем примере тестового теста были правильно сохранены как таковые в результирующих объектах JSON. PostgreSQL обрабатывает это путем автоматического преобразования его внутренних типов данных в JSON в соответствии со спецификацией JSON.) Это эффективно устранит необходимость вручную передавать данные, переданные родительским приложениям: все они могут быть делегированы в собственный парсер JSON приложения.

Различия (и возможные недостатки):

  • It выглядит иначе. Нельзя отрицать, что результаты этого подхода выглядят иначе. Объект JSON не так хорош, как набор результатов кросс-таблицы; однако различия являются чисто косметическими. Такая же информация создается - и в формате, который, вероятно, больше удобен для потребления родительскими приложениями.
  • Отсутствующие ключи. Отсутствующие значения в подходе кросс-таблицы заполняются нулями, в то время как объекты JSON просто пропускают соответствующие ключи. Вам придется решать за себя, если это приемлемый компромисс для вашего прецедента. Мне кажется, что любая попытка решить эту проблему в PostgreSQL значительно усложнит процесс и, вероятно, включит некоторую интроспекцию в виде дополнительных запросов.
  • Порядок ключей не сохраняется. Я не знаю, может ли это быть рассмотрено в PostgreSQL, но эта проблема в основном косметическая, так как любые родительские приложения либо вряд ли будут полагаться на порядок клавиш, либо иметь возможность определять правильный порядок клавиш другими способами. В худшем случае, вероятно, потребуется только запрос на добавление базы данных.

Заключение

Мне очень любопытно услышать мнения других (особенно @ ErwinBrandstetter's) об этом подход, особенно в том, что касается производительности. Когда я обнаружил этот подход в блоге Эндрю Бендера, это было похоже на попадание в сторону головы. Какой прекрасный способ сделать новый подход к сложной проблеме в PostrgeSQL. Он отлично решил мой прецедент, и я считаю, что он также будет служить многим другим.

11
ответ дан Damian C. Rossney 25 August 2018 в 07:28
поделиться

Прошу прощения за возвращение в прошлом, но решение «Динамический кросс-лист» возвращает ошибочную таблицу результатов. Таким образом, значения 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')"

1
ответ дан Kaushik Nayak 25 August 2018 в 07:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: