为两个字段创建两个数组,保持数组的排序顺序同步(不带子查询)

时间:2012-03-19 15:20:43

标签: sql arrays postgresql sorting aggregate-functions

这个问题没有任何押韵或理由,除了我对如何做到这一点感到好奇。

平台:虽然我希望使用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}

是否有一种简单的方法可以将字段用于排序,而不使用子查询?也许构建一个数组数组然后不需要?

3 个答案:

答案 0 :(得分:4)

我将您的列名group更改为grp,因为group是Postgres中的reserved word和每个SQL标准,不应该用作标识符。< / p>

我理解你的问题:

获取以相同排序顺序排序的两个数组,以便相同的元素位置对应于两个数组中的同一行。

使用子查询 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 clauses in the aggregate function array_agg()更像@Mosty demonstrates(自PostgreSQL 9.0以来一直存在)。莫斯蒂也以不同的方式解释你的问题,并使用适当的工具进行解释。

子查询中的ORDER BY是否安全?

The manual:

  

汇总函数array_aggjson_agg,[...]以及   类似的用户定义的聚合函数,产生有意义的   不同的结果值取决于输入值的顺序。   默认情况下,此排序未指定,但可以通过控制   在聚合调用中编写ORDER BY子句,如图所示   第4.2.7节。或者,从a提供输入值   排序的子查询通常会起作用。例如:

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不需要。

使用window functions

还有一种完全不同的方法
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 :(得分:1)

我知道解决这个问题的唯一方法是使用recursive 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

答案 2 :(得分:1)

试试这个:

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 +