使用具有不同 order by 子句的 postgres 窗口函数

Posted

技术标签:

【中文标题】使用具有不同 order by 子句的 postgres 窗口函数【英文标题】:Using postgres window functions with different order by clause 【发布时间】:2021-03-16 11:44:19 【问题描述】:

我在 postgres 窗口函数中使用多个 order by 时遇到问题。这是一个简短的例子。从单个查询中选择总行数、N 第一行和 N 最后一行。 (这不是我想要完成的任务,只是问题的一个例子) 它是预期的行为还是 postgres 中的错误?我正在使用 postgres 9.6

select generate_series(1, 10) id
into q;

select
       count(*) over (),
       lag(id , 0) over (order by id asc) a,
       lag(id , 0) over (order by id desc) d
from q
limit 5;

输出:

10,10,10
10,9,9
10,8,8
10,7,7
10,6,6

预期:

10,1,10
10,2,9
10,3,8
10,4,7
10,5,6

如果只选择前 N 行或只选择最后 N 行,则代码运行良好。

【问题讨论】:

您需要使用带有“over”的窗口或聚合函数。 'id over (order by id)' 不是有效的 sql lag(...,0) 是一个无操作的操作。为什么排序顺序很重要? 【参考方案1】:

我曾经遇到过类似的问题,解释相同:https://***.com/a/48668220/3984221


行为说明:

demo:db<>fiddle

您可以在查看 EXPLAIN 输出时解释这一点:

> | QUERY PLAN                                                                                                                   |
> | :--------------------------------------------------------------------------------------------------------------------------- |
> | WindowAgg  (cost=368.69..445.19 rows=2550 width=20) (actual time=0.146..0.150 rows=10 loops=1)                               |
> |   -&gt;  WindowAgg  (cost=368.69..413.32 rows=2550 width=12) (actual time=0.128..0.136 rows=10 loops=1)                         |
> |         -&gt;  Sort  (cost=368.69..375.07 rows=2550 width=8) (actual time=0.126..0.128 rows=10 loops=1)                         |
> |               Sort Key: id                                                                                                   |
> |               Sort Method: quicksort  Memory: 25kB                                                                           |
> |               -&gt;  WindowAgg  (cost=179.78..224.41 rows=2550 width=8) (actual time=0.048..0.056 rows=10 loops=1)              |
> |                     -&gt;  Sort  (cost=179.78..186.16 rows=2550 width=4) (actual time=0.033..0.034 rows=10 loops=1)             |
> |                           Sort Key: id DESC                                                                                  |
> |                           Sort Method: quicksort  Memory: 25kB                                                               |
> |                           -&gt;  Seq Scan on q  (cost=0.00..35.50 rows=2550 width=4) (actual time=0.013..0.014 rows=10 loops=1) |
> | Planning Time: 0.292 ms                                                                                                      |
> | Execution Time: 0.445 ms                                                                                                     |

在这里你可以看到:首先有一个SORT Key: id DESC。所以一切都按DESC 的顺序订购。如果您只有 DESC 有序函数,这将是您的结果,正如您已经看到的那样。现在,您有了第二个窗口函数。因此,整个结果将再次排序,进入ASC 顺序,包括。你的第一个结果。因此,您的第一个lag() 结果10, 9, 8, 7, 6, ... 将被排序回1, 2, 3, 4, 5, ...,然后将添加第二个lag() 结果。

但是,您的 lag() 函数的具体结果当然是可以解释的:您不会移动数据,因此您会得到当前值。当您将 0 移位值转换为 1 时,您可以交叉检查(就像我在上面的小提琴中所做的那样)。然后你的DESClag() 将返回2id 1,但ASC 给出NULL。一切都很好。

因此,要创建预期的输出,您需要另一种方法,例如使用row_number()ASCDESC 顺序添加行数,然后过滤它们:

demo:db<>fiddle

SELECT
    COUNT(*) OVER (),
    a.id,
    d.id
FROM ( 
   select
       id,
       row_number() over (order by id asc)
   from q
) a
JOIN ( 
   select
       id,
       row_number() over (order by id desc)
   from q
) d ON a.row_number = d.row_number
LIMIT 5

【讨论】:

是的,你是对的,数据的顺序只由查询本身决定。我很困惑,窗口函数内的“排序依据”会改变数据的总顺序。完全有效的行为,因为查询本身没有“排序依据”【参考方案2】:

lag() 的第二个参数决定回溯多少行。所以lag(id, 0) 意味着回顾零行,这使得lag(id, 0) 相当于id。所以你得到的结果是完全理智的。

得到你想要的,你可以使用row_number()加入。

SELECT count(*) OVER (),
       x1.id,
       x2.id
       FROM (SELECT id,
                    row_number() OVER (ORDER BY id ASC) r
                    FROM q) x1
            INNER JOIN (SELECT id,
                               row_number() OVER (ORDER BY id DESC) r
                               FROM q) x2
                       ON x2.r = x1.r
      ORDER BY x1.r
      LIMIT 5;

【讨论】:

也许 ijrandom 想要一个默认值,所以 lag(id, 1, 0) 比较合适 @a_horse_with_no_name:嗯,可能(几乎所有事情都是如此)。但是他们的预期结果中没有0s,所以......也许不是?我猜他们以某种方式相信窗口函数以一种方式对有问题的列进行排序,而其余的则以另一种方式排序并加入,这将使其成为获得预期结果的巧妙技巧。除了它不那样工作...... 是的,我可能误解了窗口函数的工作原理。尝试评论'lag(id, 0) over (order by id asc) a,' 或'lag(id, 0) over (order by id desc) d'。该查询将产生有效的输出。将它们结合起来,“order by”子句之一被忽略 改变'a'和'd'列的顺序,输出会有所不同。它在我看来就像一个错误。输出应该不同,或者至少 postgres 应该抱怨不可能使用多个 order by【参考方案3】:

over 子句之一的顺序应用于数据。但数据只排序一次。通过以下查询可以实现所需的行为。

select count(*) over (),
       (array_agg(id) over (order by id asc rows between unbounded preceding and unbounded  following))[row_number() over ()] a,
       (array_agg(id) over (order by id desc rows between unbounded preceding and unbounded  following))[row_number() over ()] d
from q
order by id
limit 5

可能内存对大表无效,因为 array_agg 从所有行构造数组。

【讨论】:

以上是关于使用具有不同 order by 子句的 postgres 窗口函数的主要内容,如果未能解决你的问题,请参考以下文章

如何对包含子句“order by”、“desc”和“Limit”的多个列使用 distinct

具有多列的Order by子句的语法不正确

我们如何在具有GROUP BY子句的查询中选择非聚合列,而GROUP BY子句在功能上不依赖于GROUP BY子句中的列?

由于 ORDER BY 子句,Partition By Clause 给出不同的结果

DB2 order by子句在不同的OS上给出了不同的结果

在 mysql 中使用 union 和 order by 子句