在 PostgreSQL 中,行顺序是否保留在函数和 CTE 中?

kbk*_*bkb 10 postgresql order-by

在 SELECT 语句中,如果未指定 ORDER BY 子句,则不能保证返回行的顺序。这适用于“普通”表。

对于使用 WITH 表达式 (CTE) 生成的有序表也是如此吗?函数返回的有序表?我认为不是。这是文档中明确说明的地方吗?

具体来说,我可以假设这个(更有效的)查询:

WITH ordered AS ( SELECT * FROM table1 ORDER BY col1 )
SELECT sum(col2) result FROM
generate_series(0,50) nr,
LATERAL (SELECT * FROM ordered LIMIT 100 OFFSET nr*100) a
GROUP BY nr ORDER BY nr;
Run Code Online (Sandbox Code Playgroud)

将等同于这个查询:

SELECT sum(col2) result FROM
generate_series(0,50) nr,
LATERAL (SELECT * FROM table1 ORDER BY col1 LIMIT 100 OFFSET nr*100) a
GROUP BY nr ORDER BY nr;
Run Code Online (Sandbox Code Playgroud)

对于函数:

如果我有这样的功能:

CREATE FUNCTION do_sort(name text[]) RETURNS TABLE(name text) AS $$
SELECT name.name FROM unnest($1) name ORDER BY name.name ASC;
$$ LANGUAGE SQL IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)

我可以制作这样的包装函数并假设数组总是正确排序吗?

CREATE FUNCTION do_sort_returns_array(name text[]) RETURNS text[] AS $$
SELECT array_agg(name) FROM do_sort($1);
$$ LANGUAGE SQL IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 11

SELECT语句中,如果ORDER BY未指定子句,则不能保证返回行的顺序。这适用于所有表、简单或复杂的查询。

现在,以此为基础,我们应该考虑 Postgres 以一种特殊的方式实现了 CTE。它们总是被物化(参见:PostgreSQL 的 CTE 是优化栅栏)。

这并不意味着您应该依赖于此。当使用 CTE 时,就像在第一个查询中一样,ORDER BY在引用 CTE 时也应该添加一个,因为 CTE 的ORDER BY内部可能会被删除(我不是说它总是会被删除,因为我的测试表明排序是执行。但它可能会在未来的优化器更改中删除,因为ORDER BY没有LIMIT是多余的):

WITH ordered AS ( SELECT * FROM table1 ORDER BY col1 )    -- redundant ORDER BY
SELECT sum(col2) result FROM
generate_series(0,50) nr,
LATERAL (SELECT * FROM ordered ORDER BY col1        -- ORDER BY added
         LIMIT 100 OFFSET nr*100) a
GROUP BY nr ORDER BY nr;
Run Code Online (Sandbox Code Playgroud)

更新,来自 Craig Ringer,在我在他上面链接的博客文章中发表评论后:

就像很多其他情况一样 - 现在 PostgreSQL 将始终按照 CTE 输出的顺序返回行,但从技术上讲您不应该依赖它。谁知道未来的哪些功能会改变这种情况?

不幸的是,如果您在外部查询中添加另一个,IIRC 也不够聪明,无法识别出这些行的顺序是正确的ORDER BY。(尚未测试,但很确定)。因此,以“正确”的方式进行操作不一定是免费的。

因此,如果您希望按特定顺序对结果进行排序,请ORDER BY在最终的SELECT. 您现在可以离开它(并且您可能会获得一些轻微的性能提升),但不能保证这在未来的版本中不会改变。

显式使用的另一个原因ORDER BY是您可能并不总是维护您现在编写的代码。另一位开发人员可能会尝试在没有 CTE 的情况下使用派生表或LATERAL连接重新组织查询。他们需要知道您的查询依赖于 CTE 提供的顺序,因此需要在最终版本中使用SELECT或在某处进行注释或记录。


现在,另一种编写查询的方法是LIMIT在 CTE 中添加 a并使用ROW_NUMBER()

WITH ordered AS 
    ( SELECT col2, (ROW_NUMBER() OVER (ORDER BY col1) - 1) / 100 AS nr 
      FROM table1 
      ORDER BY col1 LIMIT 5100 )
SELECT n.nr, sum(o.col2) AS result 
FROM generate_series(0, 50) AS n (nr)
    LEFT JOIN ordered AS o
      ON n.nr = o.nr 
GROUP BY n.nr
ORDER BY n.nr ;
Run Code Online (Sandbox Code Playgroud)

这样,CTE 将只为您想要的 5100 行实现,而不是为表的所有(可能是数百万)行实现。如果有索引就更好了(col1, col2)。您甚至可以删除generate_series()和/或删除 CTE:

SELECT o.nr, sum(o.col2) AS result 
FROM 
    ( SELECT col2, (ROW_NUMBER() OVER (ORDER BY col1) - 1) / 100 AS nr 
      FROM table1 
      ORDER BY col1 LIMIT 5100 
    ) AS o
GROUP BY o.nr
ORDER BY o.nr ;
Run Code Online (Sandbox Code Playgroud)