如何使用 SQL 获取列中每个分区的第一个和最后一个值

Posted

技术标签:

【中文标题】如何使用 SQL 获取列中每个分区的第一个和最后一个值【英文标题】:How to get a first and last value for each partition in a column using SQL 【发布时间】:2020-11-26 13:05:56 【问题描述】:

我的数据集如下所示。

         ts                c1           c2               c3
2019-01-04T01:50:00.000Z    C   25.48801612854004   33.317527770996094
2019-01-04T01:51:00.000Z    C   25.74610710144043   33.392295837402344
2019-01-04T01:52:00.000Z    C   25.978872299194336  33.29177474975586
2019-01-04T01:53:00.000Z    B   26.12158203125      33.2805061340332
2019-01-04T01:54:00.000Z    B   26.28511619567871   33.26923751831055
2019-01-04T01:55:00.000Z    C   26.470335006713867  33.25796890258789
2019-01-04T01:56:00.000Z    C   26.63957977294922   33.24669647216797
2019-01-04T01:57:00.000Z    C   26.954004287719727  33.23542785644531
2019-01-04T01:58:00.000Z    C   27.08258056640625   33.224159240722656
2019-01-04T01:59:00.000Z    A   27.25551986694336   33.212890625
2019-01-04T02:00:00.000Z    A   27.514263153076172  33.201622009277344
2019-01-04T02:01:00.000Z    A   27.588970184326172  33.17148971557617
2019-01-04T02:02:00.000Z    B   27.727638244628906  33.13819122314453
2019-01-04T02:03:00.000Z    B   27.956039428710938  33.104896545410156
2019-01-04T02:04:00.000Z    B   28.152463912963867  33.10499954223633

我想为“c1”列中的每个分区值获取“ts”的第一个和最后一个值。 我已经尝试了以下查询,但它没有返回正确的结果。

SELECT ts, c1, c2, c3,
first_value(ts) OVER (partition by c1 order by ts
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as first,
last_value(ts) OVER (partition by c1 order by ts
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as last
FROM `default`.`a07_a15`

问题:第一个值只返回三个不同的 ts 值,而最大值返回完全错误。

预期:我需要每个重复分区值的第一个和最后一个值。

         ts                c1           c2               c3                 first                last
2019-01-04T01:50:00.000Z    C   25.48801612854004   33.317527770996094  2019-01-04T01:50:00.000Z    2019-01-04T01:52:00.000Z
2019-01-04T01:51:00.000Z    C   25.74610710144043   33.392295837402344  2019-01-04T01:50:00.000Z    2019-01-04T01:52:00.000Z
2019-01-04T01:52:00.000Z    C   25.978872299194336  33.29177474975586   2019-01-04T01:50:00.000Z    2019-01-04T01:52:00.000Z
2019-01-04T01:53:00.000Z    B   26.12158203125      33.2805061340332    2019-01-04T01:53:00.000Z    2019-01-04T01:54:00.000Z
2019-01-04T01:54:00.000Z    B   26.28511619567871   33.26923751831055   2019-01-04T01:53:00.000Z    2019-01-04T01:54:00.000Z
2019-01-04T01:55:00.000Z    C   26.470335006713867  33.25796890258789   2019-01-04T01:55:00.000Z    2019-01-04T01:58:00.000Z
2019-01-04T01:56:00.000Z    C   26.63957977294922   33.24669647216797   2019-01-04T01:55:00.000Z    2019-01-04T01:58:00.000Z
2019-01-04T01:57:00.000Z    C   26.954004287719727  33.23542785644531   2019-01-04T01:55:00.000Z    2019-01-04T01:58:00.000Z    
2019-01-04T01:58:00.000Z    C   27.08258056640625   33.224159240722656  2019-01-04T01:55:00.000Z    2019-01-04T01:58:00.000Z
2019-01-04T01:59:00.000Z    A   27.25551986694336   33.212890625        2019-01-04T01:59:00.000Z    2019-01-04T02:01:00.000Z
2019-01-04T02:00:00.000Z    A   27.514263153076172  33.201622009277344  2019-01-04T01:59:00.000Z    2019-01-04T02:01:00.000Z
2019-01-04T02:01:00.000Z    A   27.588970184326172  33.17148971557617   2019-01-04T01:59:00.000Z    2019-01-04T02:01:00.000Z
2019-01-04T02:02:00.000Z    B   27.727638244628906  33.13819122314453   2019-01-04T02:02:00.000Z    2019-01-04T02:04:00.000Z
2019-01-04T02:03:00.000Z    B   27.956039428710938  33.104896545410156  2019-01-04T02:02:00.000Z    2019-01-04T02:04:00.000Z
2019-01-04T02:04:00.000Z    B   28.152463912963867  33.10499954223633   2019-01-04T02:02:00.000Z    2019-01-04T02:04:00.000Z

【问题讨论】:

【参考方案1】:

使用lag()lead()

select t.*
from (select t.*,
             lag(c1) over (order by ts) as prev_c1,
             lead(c1) over (order by ts) as next_c1
      from t
     ) t
where prev_c1 is null or next_c1 is null or
      prev_c1 <> c1 or next_c1 <> c1;

这会将值放在不同的行中。如果您希望它们在同一行中,可能将其视为间隙和孤岛问题是最简单的解决方案:

select c1, min(ts), max(ts)
from (select t.*,
             row_number() over (order by ts) as seqnum,
             row_number() over (partition by c1 order by ts) as seqnum_2
      from t
     ) t
group by c1, (seqnum - seqnum_2);

编辑:

如果您需要保留原始行,只需使用窗口函数:

select t.*,
       min(ts) over (partition by c1, (seqnum - seqnum2)) as min_ts,
       max(ts) over (partition by c1, (seqnum - seqnum2)) as max_ts
from (select t.*,
             row_number() over (order by ts) as seqnum,
             row_number() over (partition by c1 order by ts) as seqnum_2
      from t
     ) t

【讨论】:

@Linoff 上述解决方案工作正常(第二个)。但我想包括数据集中所有可用的列(ts、c1、c2 和 c3)。假设我们在第一个分区中有 4 行,我们需要为 4 行放置相同的常量值(第一个和最后一个),依此类推 @Sudha 。 . .第一个保留第一行和最后一行的所有列。 @Linoff 抱歉,我试过了,但没有按预期工作。我们需要“ts”值的最小值和最大值,但它返回分区值。请检查我在上述帖子中编辑的预期输出。 @Sudha 。 . .这确实是对查询的调整。您不需要聚合,只需要窗口函数。 太棒了!现在它按预期工作。非常感谢@Linoff。

以上是关于如何使用 SQL 获取列中每个分区的第一个和最后一个值的主要内容,如果未能解决你的问题,请参考以下文章

如何获取每个分区的最后一个值以在 Spark SQL 中估算缺失值

如何在sql中从给定的月份和年份获取月份的第一天和最后一天

如何检查sql的第一列或第二列中是不是存在一个特定值?

从 SQL 获取列中的最后一个单元格

SQL-Server:继续最后一个值而不是 NULL,分区不正确

如果另一列中的值是唯一的,那么如何在SQL中放置一个显示1的列,如果它是重复的则为0?