【问题标题】:PostgreSQL: detecting the first/last rows of result setPostgreSQL:检测结果集的第一行/最后一行
【发布时间】:2019-02-06 15:28:42
【问题描述】:

有没有办法在选择中嵌入一个标志,表明它是结果集的第一行或最后一行?我在想一些大意的东西:

> SELECT is_first_row() AS f, is_last_row() AS l FROM blah;
  f  |  l
-----------
  t  |  f
  f  |  f
  f  |  f
  f  |  f
  f  |  t

答案可能在window functions,但我才刚刚了解它们,我质疑它们的效率。

SELECT first_value(unique_column) OVER () = unique_column, last_value(unique_column) OVER () = unique_column, * FROM blah;

似乎做我想做的事。不幸的是,我什至不完全理解这种语法,但由于unique_column 是独一无二的,而NOT NULL 它应该会提供明确的结果。但如果它进行分类,那么治疗可能比疾病更糟糕。 (实际上,在我的测试中,unique_column没有排序的,所以是这样的。)

EXPLAIN ANALYZE 并不表示存在效率问题,但它什么时候告诉过我需要知道什么?

我可能需要在聚合函数中使用它,但我刚刚被告知那里不允许使用窗口函数。 ???

编辑: 实际上,我只是在上面的查询中添加了ORDER BY unique_column,并将标识为 first 和 last 的行放入结果集的中间。好像first_value()/last_value() 真的意味着“我在开始排序之前拾取的第一个/最后一个值”。我认为我不能安全地以最佳方式做到这一点。除非对 OVER 关键字的使用有更好的理解。

我在 Debian 9.5 环境中运行 PostgreSQL 9.6。

这不是重复的,因为我试图让结果集的第一行和最后一行来标识自己,而 Postgres: get min, max, aggregate values in one select 只是在结果中获取列的最小值和最大值设置。

【问题讨论】:

  • 知道第一条和最后一条记录的目的是什么?你想用它进一步做什么?是的,窗口函数是此类问题的最佳解决方案。如果您需要聚合,只需将第一个查询的结果用作聚合查询中的表,然后它就可以正常工作
  • 结果集将被异步发送到另一个系统,该系统想知道它正在查看结果集的哪一部分。生成结果集后很难应用这些符号
  • 在关系数据库中没有“第一行”或“最后一行”之类的东西。表中的行不以任何方式排序。因此,除非您指定排序定义,否则您无法分辨“第一”行是什么。
  • 你有一个 empty 窗口定义:OVER (),这意味着:一切顺利!。将其与我的答案中的窗口定义进行比较,后者 确实 强加了一个命令。
  • @Opux,说实话,我认为你把事情复杂化了很多。如果您不订购记录,那么第一个和最后一个是完全随机的。更重要的是,当它进入另一个系统时,它可以以与您的语句完全不同的顺序阅读。您将只有 2 条记录被标记为数百、数千或数百万条记录。有用性接近于零,或者我错过了一些非常重要的事情,不明白你为什么要那样做。

标签: postgresql select window-functions


【解决方案1】:

尝试使用

SELECT columns 
FROM mytable 
Join conditions
WHERE conditions ORDER BY date DESC LIMIT 1

UNION ALL 

SELECT columns
FROM mytable 
Join conditions
WHERE conditions ORDER BY date ASC LIMIT 1

SELECT 只是减少了一半的处理时间。你也可以去索引。

【讨论】:

  • 是的,但是,其中存在排序,如果可能的话,这是我想避免的事情。 AFAIK,窗口函数不需要排序
  • @Opux:没有排序就没有“第一”或“最后”行。窗口函数——如果你正确使用它们——也需要排序
【解决方案2】:

事实上,Window Functions 是一种很棒的方法,而且对于您的这种需求来说,它们非常棒。

关于效率,窗口函数可以处理手头的数据集。这意味着 DBMS 只会添加额外的处理来推断第一个/最后一个值。

我想建议一件事:我喜欢在OVER 子句中放置一个ORDER BY 条件,以确保多次执行之间的数据集顺序相同,从而向您返回相同的值.

【讨论】:

  • 看起来你对排序是正确的。我刚刚编辑了我的问题,有了新的发展。如果我需要排序,那么我认为这件事不值得做。我的下一个努力是更好地了解OVER,以防万一提供解决方案。
【解决方案3】:

您可以使用lead()lag() 窗口函数(在适当的窗口上)并将它们与NULL 进行比较:


-- \i tmp.sql

CREATE TABLE ztable
( id SERIAL PRIMARY KEY
  , starttime TIMESTAMP
);

INSERT INTO ztable (starttime) VALUES ( now() - INTERVAL '1 minute');
INSERT INTO ztable (starttime) VALUES ( now() - INTERVAL '2 minute');
INSERT INTO ztable (starttime) VALUES ( now() - INTERVAL '3 minute');
INSERT INTO ztable (starttime) VALUES ( now() - INTERVAL '4 minute');
INSERT INTO ztable (starttime) VALUES ( now() - INTERVAL '5 minute');
INSERT INTO ztable (starttime) VALUES ( now() - INTERVAL '6 minute');

SELECT id, starttime
        , ( lead(id) OVER www IS NULL) AS is_first
        , ( lag(id) OVER www IS NULL) AS is_last
FROM ztable
WINDOW www AS (ORDER BY id )
ORDER BY id
        ;


SELECT id, starttime
        , ( lead(id) OVER www IS NULL) AS is_first
        , ( lag(id) OVER www IS NULL) AS is_last
FROM ztable
WINDOW www AS (ORDER BY starttime )
ORDER BY id
        ;

SELECT id, starttime
        , ( lead(id) OVER www IS NULL) AS is_first
        , ( lag(id) OVER www IS NULL) AS is_last
FROM ztable
WINDOW www AS (ORDER BY starttime )
ORDER BY random()
        ;

结果:


INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
 id |         starttime          | is_first | is_last 
----+----------------------------+----------+---------
  1 | 2018-08-31 18:38:45.567393 | f        | t
  2 | 2018-08-31 18:37:45.575586 | f        | f
  3 | 2018-08-31 18:36:45.587436 | f        | f
  4 | 2018-08-31 18:35:45.592316 | f        | f
  5 | 2018-08-31 18:34:45.600619 | f        | f
  6 | 2018-08-31 18:33:45.60907  | t        | f
(6 rows)

 id |         starttime          | is_first | is_last 
----+----------------------------+----------+---------
  1 | 2018-08-31 18:38:45.567393 | t        | f
  2 | 2018-08-31 18:37:45.575586 | f        | f
  3 | 2018-08-31 18:36:45.587436 | f        | f
  4 | 2018-08-31 18:35:45.592316 | f        | f
  5 | 2018-08-31 18:34:45.600619 | f        | f
  6 | 2018-08-31 18:33:45.60907  | f        | t
(6 rows)

 id |         starttime          | is_first | is_last 
----+----------------------------+----------+---------
  2 | 2018-08-31 18:37:45.575586 | f        | f
  4 | 2018-08-31 18:35:45.592316 | f        | f
  6 | 2018-08-31 18:33:45.60907  | f        | t
  5 | 2018-08-31 18:34:45.600619 | f        | f
  1 | 2018-08-31 18:38:45.567393 | t        | f
  3 | 2018-08-31 18:36:45.587436 | f        | f
(6 rows)

[更新:添加了一个随机排序的案例]

【讨论】:

  • 第一个 SELECT 应该是什么?数据似乎有误。看来我仍然需要排序。我尝试从中删除ORDER BYs,数据也是错误的。
  • 我添加了一个额外的查询来说明结果不依赖于排序顺序。
  • 我的期望是第一个记录将被称为第一个,最后一个将被称为最后一个。第一个询问对耶稣来说很好(马太福音 20:16)。但是第二个给出了正确的结果,但是排序
  • 根据窗口定义决定first/last,结果的最终顺序无关。 [ 和:除了排序之外,查询 #2 和 #3 的结果是相同的。
  • 我刚刚编辑了带有新障碍的问题。也许这让我更清楚我想要完成的事情。
【解决方案4】:

使用带有特定frames的窗口函数很简单:

with t(x, y) as (select generate_series(1,5), random()) 
select *,
  count(*) over (rows between unbounded preceding and current row),
  count(*) over (rows between current row and unbounded following)
from t;
┌───┬───────────────────┬───────┬───────┐
│ x │         y         │ count │ count │
├───┼───────────────────┼───────┼───────┤
│ 1 │ 0.543995119165629 │     1 │     5 │
│ 2 │ 0.886343683116138 │     2 │     4 │
│ 3 │ 0.124682310037315 │     3 │     3 │
│ 4 │ 0.668972567655146 │     4 │     2 │
│ 5 │ 0.266671542543918 │     5 │     1 │
└───┴───────────────────┴───────┴───────┘

如您所见,count(*) over (rows between unbounded preceding and current row) 返回从数据集开始到当前行的行数,count(*) over (rows between current row and unbounded following) 返回从当前到数据集结束的行数。 1 表示第一行/最后一行。

在您通过order by 订购数据集之前,它一直有效。在这种情况下,您需要在框架定义中复制它:

with t(x, y) as (select generate_series(1,5), random()) 
select *,
  count(*) over (order by y rows between unbounded preceding and current row),
  count(*) over (order by y rows between current row and unbounded following)
from t order by y;
┌───┬───────────────────┬───────┬───────┐
│ x │         y         │ count │ count │
├───┼───────────────────┼───────┼───────┤
│ 1 │ 0.125781774986535 │     1 │     5 │
│ 4 │  0.25046408502385 │     2 │     4 │
│ 5 │ 0.538880597334355 │     3 │     3 │
│ 3 │ 0.802807193249464 │     4 │     2 │
│ 2 │ 0.869908029679209 │     5 │     1 │
└───┴───────────────────┴───────┴───────┘

PS:正如 comment 中的 a_horse_with_no_name 所述:

没有排序就没有“第一”或“最后”行。

【讨论】:

  • 这似乎也有效。不过有点啰嗦。我是不是很幸运 first_value(unique_column) OVER () = unique_column 为我工作,但它可能无法在某个地方工作?我发现如果我进行了排序,那么我可以通过将我的主查询放在子查询中,然后将 first_value() 的东西放在超级查询中来让它工作。
  • @Opux 这是一篇很棒的文章,详细解释了窗口函数的工作原理:red-gate.com/simple-talk/sql/t-sql-programming/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多