Создайте два массива для двух полей, сохраняя порядок сортировки массивов в синхронизации (без подзапроса) - PullRequest
1 голос
/ 19 марта 2012

Нет никакого смысла или причины для этого вопроса, кроме того, что мне было любопытно, как можно поступить так.

Платформа: В то время как я надеялся на решение SQL-Standard, я сконцентрировался на PostgreSQL 8.4 + .(Я знаю, что в 9.0+ есть некоторые функции сортировки массивов.)

SELECT    id, group, dt
FROM      foo
ORDER BY  id;
  id   | group |    dt
-------+-------+-----------
   1   |  foo  | 2012-01-01
   1   |  bar  | 2012-01-03
   1   |  baz  | 2012-01-02
   2   |  foo  | 2012-01-01
   3   |  bar  | 2012-01-01
   4   |  bar  | 2012-01-01
   4   |  baz  | 2012-01-01

Я знаю, что следующий запрос неверен, но результат аналогичен тому, что я получаю;способ связать два поля (сортировка group должна также сортировать dt):

SELECT    id, sort_array(array_agg(group)), array_agg(dt)
FROM      foo
GROUP BY  id;
  id   |     group      |                dt
-------+----------------+------------------------------------
   1   |  {bar,baz,foo} | {2012-01-03,2012-01-02,2012-01-01}
   2   |  {foo}         | {2012-01-01}
   3   |  {bar}         | {2012-01-01}
   4   |  {bar,baz}     | {2012-01-01,2012-01-01}

Есть ли простой способ связать поля для сортировки, без использования подзапроса?Может быть, построить массив массивов, а затем unnest?

Ответы [ 3 ]

4 голосов
/ 19 марта 2012

Я изменил имя вашего столбца group на grp, потому что group является зарезервированным словом в Postgres и каждом стандарте SQL и не должно использоваться в качестве идентификатора.

Я понимаю ваш вопрос следующим образом:

Получите два массива, отсортированные в одинаковом порядке сортировки, чтобы одна и та же позиция элемента соответствовала одной и той же строке в обоих массивах.

Используйте подзапрос или CTE и упорядочите строки перед агрегированием.

SELECT id, array_agg(grp) AS grp, array_agg(dt) AS dt
FROM  (
    SELECT *
    FROM   tbl
    ORDER  BY id, grp, dt
    ) x
GROUP  BY id;

Это на быстрее , чем для использования индивидуального ORDER BY предложения в агрегатной функции array_agg() подобно @ Mosty демонстрирует (и существует с PostgreSQL 9.0).Мости также интерпретирует ваш вопрос по-разному и использует соответствующие инструменты для своей интерпретации.

Является ли ORDER BY безопасным подзапросом?

Руководство:

Агрегатные функции array_agg, json_agg, [...], а также аналогичные пользовательские агрегатные функции выдают значимо разные значения результата в зависимости от порядка входных значений.Этот порядок не указан по умолчанию, но им можно управлять, написав предложение ORDER BY в совокупном вызове, как показано в разделе 4.2.7 .Альтернативно, подача входных значений из отсортированного подзапроса обычно работает.Например:

SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;

Помните, что этот подход может завершиться ошибкой, если внешний уровень запроса содержит дополнительную обработку, такую ​​как объединение, поскольку это может привести к переупорядочению вывода подзапроса до вычисления агрегата.

Так что да, в этом примере это безопасно.

Без подзапроса

Если вам действительно нужно решение без подзапроса Вы можете:

SELECT id
     , array_agg(grp ORDER BY grp)
     , array_agg(dt  ORDER BY grp, dt)
FROM   tbl
GROUP  BY id;

Обратите внимание на ORDER BY grp, dt.Я сортирую по dt в дополнение к разрыву связей и делаю порядок сортировки однозначным.Однако для grp не требуется.

Существует также совершенно другой способ сделать это с оконными функциями :

SELECT DISTINCT ON (id)
       id
     , array_agg(grp) OVER w AS grp
     , array_agg(dt)  OVER w AS dt
FROM   tbl
WINDOW w AS (PARTITION BY id ORDER BY grp, dt
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
ORDER  BY id;

Обратите внимание на DISTINCT ON (id)вместо DISTINCT, который дает тот же результат, но работает на порядок быстрее, потому что нам не нужна дополнительная сортировка.

Я провел несколько тестов, и это почти так же быстро, как и два других решения.Как и ожидалось, версия подзапроса была еще самой быстрой.Проверьте с EXPLAIN ANALYZE, чтобы убедиться в этом.

1 голос
/ 19 марта 2012

Попробуйте это:

select id,
  array_agg(agroup order by agroup) as AGroup,
  array_agg(dt order by dt desc) as dt
from t
group by id

Это должно работать на PostgreSQL 9.1 +

1 голос
/ 19 марта 2012

Единственный способ, который я знаю, чтобы сгладить это, - это использовать рекурсивный CTE . Вот как будет выглядеть запрос:

--We first need to create the order hierarchy to recurse properly
SELECT *, ROW_NUMBER() OVER(PARTITION BY id ORDER BY group) AS rownum
INTO TEMP TableToRecurse
FROM foo

WITH RECURSIVE FinalOutput (id, group, dt, rownum) AS
(
--Anchor row of recursion
SELECT id, group, dt, rownum
FROM TableToRecurse 
WHERE rownum = 1
UNION ALL 
--Recursion piece
SELECT tr.id, FinalOutput.group || ', ' || tr.group, 
    FinalOutput.dt || ', ' || tr.dt, tr.rownum
FROM TableToRecurse AS tr
    JOIN FinalOutput
        ON  FinalOutput.id = tr.id AND FinalOutput.rownum = tr.rownum +1
)
--Final output only showing the last row (Max)
--Which should have everything concatenated together
SELECT FinalOutput.id, FinalOutput.group, FinalOutput.dt
FROM FinalOutput 
    JOIN 
    (
        SELECT MAX(rownum) AS MaxRowNum, id
        FROM FinalOutput
        GROUP BY id
    ) AS MaxForEach
        ON FinalOutput.id = MaxForEach.id 
            AND FinalOutput.rownum = MaxForEach.MaxRowNum
...