无法计算 CTE 子查询输出之间的差异以用于更大的 PostgreSQL 查询输出列

Posted

技术标签:

【中文标题】无法计算 CTE 子查询输出之间的差异以用于更大的 PostgreSQL 查询输出列【英文标题】:Unable to calculate difference between CTE subquery outputs for use in larger PostgreSQL query output column 【发布时间】:2016-10-29 12:24:43 【问题描述】:

在 Shell 中使用 PostgreSQL v9.4.5 我通过运行create database momentspsql 中创建了一个名为moments 的数据库。然后我创建了一个时刻表:

CREATE TABLE moments
(
  id SERIAL4 PRIMARY KEY,
  moment_type BIGINT NOT NULL,
  flag BIGINT NOT NULL,
  time TIMESTAMP NOT NULL,
  UNIQUE(moment_type, time)
);
INSERT INTO moments (moment_type, flag, time) VALUES (1, 7, '2016-10-29 12:00:00');
INSERT INTO moments (moment_type, flag, time) VALUES (1, -30, '2016-10-29 13:00:00');
INSERT INTO moments (moment_type, flag, time) VALUES (3, 5, '2016-10-29 14:00:00');
INSERT INTO moments (moment_type, flag, time) VALUES (2, 9, '2016-10-29 18:00:00');
INSERT INTO moments (moment_type, flag, time) VALUES (2, -20, '2016-10-29 17:00:00');
INSERT INTO moments (moment_type, flag, time) VALUES (3, 10, '2016-10-29 16:00:00');

我运行select * from moments 来查看表格:

时刻表

 id | moment_type | flag |        time         
----+-------------+------+---------------------
  1 |           1 |    7 | 2016-10-29 12:00:00
  2 |           1 |  -30 | 2016-10-29 13:00:00
  3 |           3 |    5 | 2016-10-29 14:00:00
  4 |           2 |    9 | 2016-10-29 18:00:00
  5 |           2 |  -20 | 2016-10-29 17:00:00
  6 |           3 |   10 | 2016-10-29 16:00:00

然后我尝试编写一个产生以下输出的 SQL 查询,由此对于每对重复的 moment_type 值,它返回具有最新时间戳值的 moment_type 的标志值与第二个的标志值之间的差异最近的时间戳值,并按 moment_type 升序列出结果。

预期的 SQL 查询输出

moment_type | flag | 
------------+------+
          1 |  -37 |  (i.e. -30 - 7)
          2 |   29 |  (i.e.   9 - -20)
          3 |   5  |  (i.e.  10 - 5)

我想出的SQL查询如下,它使用WITH查询写了多个Common Table Expressions (CET)子查询,作为最后更大的SELECT查询中的临时表。我还使用SQL function 来计算两个子查询输出之间的差异(或者我认为我可以只使用DIFFERENCE DIFFERENCE(most_recent_flag, second_most_recent_flag) AS flag 而不是函数):

CREATE FUNCTION difference(most_recent_flag, second_most_recent_flag) RETURNS numeric AS $$
  SELECT $1 - $2;
$$ LANGUAGE SQL;

-- get two flags that have the most recent timestamps
WITH two_most_recent_flags AS (
SELECT moments.flag
FROM moments
ORDER BY moments.time DESC
LIMIT 2
),
-- get one flag that has the most recent timestamp
most_recent_flag AS (
SELECT *
FROM two_most_recent_flags 
ORDER BY flag DESC
LIMIT 1
), 
-- get one flag that has the second most recent timestamp
second_most_recent_flag AS (
SELECT *
FROM two_most_recent_flags 
ORDER BY flag ASC
LIMIT 1
)
SELECT DISTINCT ON (moments.moment_type)
moments.moment_type,
difference(most_recent_flag, second_most_recent_flag) AS flag
FROM moments
ORDER BY moment_type ASC
LIMIT 2;

但是当我在 PostgreSQL 中运行上述 SQL 查询时,它返回以下错误:

ERROR:  column "most_recent_flag" does not exist
LINE 21: difference(most_recent_flag, second_most_recent_flag) AS fla...

问题

我可以使用哪些技术以及如何应用它们来克服此错误,并计算和显示flag 列中的差异以实现预期的 SQL 查询输出

注意:Window Function 可能会以某种方式使用,因为它跨表行执行计算

【问题讨论】:

【参考方案1】:

使用lag()窗口函数:

select moment_type, difference
from (
    select *, flag- lag(flag) over w difference
    from moments
    window w as (partition by moment_type order by time)
    ) s
where difference is not null
order by moment_type

 moment_type | difference 
-------------+------------
           1 |        -37
           2 |         29
           3 |          5
(3 rows)    

【讨论】:

【参考方案2】:

一种方法是使用条件聚合。窗口函数row_number()可用于识别第一个和最后一个时间值:

select m.moment_type,
       (max(case when seqnum_desc = 1 then flag end) -
        min(case when seqnum_asc = 1 then flag end)
       )
from (select m.*,
             row_number() over (partition by m.moment_type order by m.time) as seqnum_asc,
             row_number() over (partition by m.moment_type order by m.time desc) as seqnum_desc
      from moments m
     ) m
group by m.moment_type;

【讨论】:

以上是关于无法计算 CTE 子查询输出之间的差异以用于更大的 PostgreSQL 查询输出列的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL--with子句

如何优化此代码以运行更大的值? [复制]

MYSQL查询优化,多查询还是一大查询

GREENPLUM中的with,即CTE用法,转自gp中文网文档

如何减少许多相似的相关子查询?

重写查询以使用除 CTE 和子查询之外的窗口函数