【问题标题】:First and last value of window function in one row in PostgreSQLPostgreSQL中一行中窗口函数的第一个和最后一个值
【发布时间】:2017-11-06 05:03:55
【问题描述】:

我想在指定分区的一行中拥有一列的第一个值和第二列的最后一个值。为此我创建了这个查询:

SELECT DISTINCT
b.machine_id,
batch,
timestamp_sta,
timestamp_stp,
FIRST_VALUE(timestamp_sta) OVER w AS batch_start,
LAST_VALUE(timestamp_stp) OVER w AS batch_end
FROM db_data.sta_stp AS a
JOIN db_data.ll_lu AS b
ON a.ll_lu_id=b.id
WINDOW w AS (PARTITION BY batch, machine_id ORDER BY timestamp_sta)
ORDER BY timestamp_sta, batch, machine_id;

但如图所示,batch_end 列返回的数据不正确。

batch_start 列具有正确的 timestamp_sta 列的第一个值。但是 batch_end 应该是 "2012-09-17 10:49:45" 并且它等于同一行的 timestamp_stp

为什么会这样?

【问题讨论】:

    标签: postgresql window-functions


    【解决方案1】:

    这个问题很老,但这个解决方案比目前发布的更简单、更快:

    SELECT b.machine_id
         , batch
         , timestamp_sta
         , timestamp_stp
         , min(timestamp_sta) OVER w AS batch_start
         , max(timestamp_stp) OVER w AS batch_end
    FROM   db_data.sta_stp a
    JOIN   db_data.ll_lu   b ON a.ll_lu_id = b.id
    WINDOW w AS (PARTITION BY batch, b.machine_id) -- No ORDER BY !
    ORDER  BY timestamp_sta, batch, machine_id; -- why this ORDER BY?
    

    如果您将 ORDER BY 添加到窗口框架定义中,则具有更大ORDER BY 表达式的每个下一行都有一个较晚的框架开始。 min()first_value() 都不能返回整个分区的“第一个”时间戳。如果没有ORDER BY,同一分区的所有行都是peers,你会得到你想要的结果。

    您添加的ORDER BY 有效(不是窗框定义中的那个,外部那个),但似乎没有意义,并且使查询更加昂贵。您可能应该使用与您的窗框定义一致的ORDER BY 子句以避免额外的排序成本:

    ... 
    ORDER BY batch, b.machine_id, timestamp_sta, timestamp_stp;
    

    我认为在此查询中不需要DISTINCT。如果你真的需要它,你可以添加它。或DISTINCT ON ()。但随后ORDER BY 子句变得更加相关。见:

    如果您需要同一行中的一些其他列(同时仍按时间戳排序),您的 FIRST_VALUE()LAST_VALUE() 的想法可能是可行的方法。您可能需要将此附加到窗口框架定义中然后

    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    

    见:

    【讨论】:

      【解决方案2】:

      @Łukasz Kamiński 给出的解释解决了问题的核心。

      但是,last_value 应替换为 max()。您按timestamp_sta 排序,因此最后一个值是具有最大timestamp_sta 的值,它可能与timestamp_stp 相关,也可能不相关。我也会按这两个字段排序。

      SELECT DISTINCT
        b.machine_id,
        batch,
        timestamp_sta,
        timestamp_stp,
        FIRST_VALUE(timestamp_sta) OVER w AS batch_start,
        MAX(timestamp_stp) OVER w AS batch_end
      FROM db_data.sta_stp AS a
      JOIN db_data.ll_lu AS b
      ON a.ll_lu_id=b.id
      WINDOW w AS (PARTITION BY batch, machine_id 
                   ORDER BY timestamp_sta,timestamp_stp 
                   RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
      ORDER BY timestamp_sta, batch, machine_id;
      

      http://rextester.com/UTDE60342

      【讨论】:

      • 虽然这应该可行,但它不必要地复杂且昂贵。我添加了一个替代方案。
      【解决方案3】:

      来自syntax documentation

      frame_clause 指定构成窗口框架的行集,它是当前分区的子集,用于那些作用于框架而不是整个分区的窗口函数。可以在 RANGE 或 ROWS 模式下指定帧;无论哪种情况,它都从 frame_start 运行到 frame_end。 如果省略 frame_end,则默认为 CURRENT ROW

      UNBOUNDED PRECEDING 的 frame_start 表示该帧从分区的第一行开始,同样 UNBOUNDED FOLLOWING 的 frame_end 表示该帧以分区的最后一行结束。

      function list

      last_value(value any) 返回在 窗口框架的最后一行

      处计算的值

      所以正确的 SQL 应该是:

      SELECT DISTINCT
      b.machine_id,
      batch,
      timestamp_sta,
      timestamp_stp,
      FIRST_VALUE(timestamp_sta) OVER w AS batch_start,
      LAST_VALUE(timestamp_stp) OVER w AS batch_end
      FROM db_data.sta_stp AS a
      JOIN db_data.ll_lu AS b
      ON a.ll_lu_id=b.id
      WINDOW w AS (PARTITION BY batch, machine_id ORDER BY timestamp_sta range between unbounded preceding and unbounded following)
      ORDER BY timestamp_sta, batch, machine_id;
      

      【讨论】:

      • 太好了,就是这样!我也刚刚发现了这个手册页,但我仍然没有完全理解它是如何工作的。感谢您的工作示例。
      • 不幸的是,我认为它不会返回预期的结果。
      • @klin 是的,它对我有用。这个解决方案有什么问题?
      • 查看@JGH 答案中的注释。
      • 如果您在该表上有并发插入(或其他原因对这 2 列进行不同的排序)并且您的最后一个 started 插入将结束在所述批次中的其他一些之前。所以它将是最后一次启动,而不是最后一次完成,但last_value() 仍会返回此行值,而不是实际上最后一次批量完成。
      猜你喜欢
      • 2021-04-06
      • 2018-07-24
      • 1970-01-01
      • 2012-01-09
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多