【问题标题】:split datetime periods by null values按空值拆分日期时间段
【发布时间】:2016-10-27 08:14:20
【问题描述】:

我想用空值分割数据周期。 假设我有这样的表。

Date                |  abc
2016-04-18 07:10:00 | 2.3
2016-04-18 07:20:00 | 2.1
2016-04-18 07:30:00 |
2016-04-18 07:40:00 | 
2016-05-01 10:00:00 | 1.9
2016-05-01 10:10:00 | 4.5
2016-05-01 10:20:00 | 3.9

部分abc列数据为null,日期列间隔为10分钟。

我想用 abc 的空值分割日期期间。

结果

      start                     end 
2016-04-18 07:10:00 ~ 2016-04-18 07:20:00 
2016-05-01 10:00:00 ~ 2016-05-01 10:20:00

【问题讨论】:

    标签: sql postgresql datetime gaps-and-islands


    【解决方案1】:

    “差距和孤岛”问题通常通过应用一个窗口函数来检查数据的变化并根据这些变化分配一个组号来解决。

    首先,需要根据时间戳列定义的排序顺序将当前值与前一个值进行比较:

    此声明:

    select *,
           case
              when abc is null or lag(abc) over (order by "date") is not null then null
              else 1
            end as group_flag
    from data
    order by "date";
    

    返回这个结果:

    date                | abc | group_flag
    --------------------+-----+-----------
    2016-04-18 07:10:00 | 2.3 |          1
    2016-04-18 07:20:00 | 2.1 |           
    2016-04-18 07:30:00 |     |           
    2016-04-18 07:40:00 |     |          
    2016-05-01 10:00:00 | 1.9 |          1
    2016-05-01 10:10:00 | 4.5 |           
    2016-05-01 10:20:00 | 3.9 |           
    

    如您所见,每次新“组”开始时,我们都会收到一个标志。

    下一步是使用运行总和,将“标志”更改为实际组:

    select *,
           sum(group_flag) over (order by date) as group_nr
    from (
      select *,
             case
                when abc is null lag(abc) over (order by "date") is not null then null
                else 1
              end as group_flag
      from data
    ) t1
    order by "date";
    

    这会返回:

    date                | abc | group_flag | group_nr
    --------------------+-----+------------+---------
    2016-04-18 07:10:00 | 2.3 |          1 |        1
    2016-04-18 07:20:00 | 2.1 |            |        1
    2016-04-18 07:30:00 |     |            |        1
    2016-04-18 07:40:00 |     |            |        1
    2016-05-01 10:00:00 | 1.9 |          1 |        2
    2016-05-01 10:10:00 | 4.5 |            |        2
    2016-05-01 10:20:00 | 3.9 |            |        2
    

    如您所见,新列 group_nr 现在标识了我们感兴趣的连续期间。对于您的结果,我们只需过滤掉 abc 为空的那些行:

    select min(date) as period_start, max(date) as period_end
    from (
        select *,
               sum(group_flag) over (order by date) as group_nr
        from (
          select *,
                 case
                    when abc is null or lag(abc) over (order by date) is not null then null
                    else 1
                  end as group_flag
          from data
        ) t1
        order by "date"
    ) t2
    where abc is not null
    group by group_nr;
    

    这会返回:

    period_start        | period_end         
    --------------------+--------------------
    2016-04-18 07:10:00 | 2016-04-18 07:20:00
    2016-05-01 10:40:00 | 2016-05-01 11:00:00
    

    【讨论】:

      【解决方案2】:

      使用 CTE 并避免嵌套查询的更具可读性的解决方案。

      我不确定在相同的日期出现的情况下,NULL 和 NOT NULL abc 值会出现什么行为。这种情况下要从数据集中排除NULL吗?

      -- Only if your server supports LAG and LEAD windowed functions
      -- See alternative BeginEndFlagedSet below
      WITH BeginEndFlagedSet as
      (
          SELECT
              Date,
              Abc,
              ROW_NUMBER() OVER(ORDER BY Date ASC) as Num,
              CASE 
                  WHEN Abc IS NOT NULL AND LAG(Abc) OVER(ORDER BY Date) IS NULL THEN 'Start'
                  WHEN Abc IS NOT NULL AND LEAD(Abc) OVER(ORDER BY Date) IS NULL THEN 'End'
              END as BeginEndFlag
          FROM [YourTable]
      )
      SELECT 
          MIN(StartRow.Date) as "Start Date",
          CASE 
              WHEN MIN(CASE EndRow.BeginEndFlag WHEN 'End' THEN EndRow.Date END) 
                  > MIN(CASE EndRow.BeginEndFlag WHEN 'Start' THEN EndRow.Date END) THEN MIN(StartRow.Date)
              WHEN MIN(CASE EndRow.BeginEndFlag WHEN 'End' THEN EndRow.Date END) IS NULL THEN MIN(StartRow.Date)
              ELSE MIN(CASE EndRow.BeginEndFlag WHEN 'End' THEN EndRow.Date END)
          END as "End Date"
      FROM BeginEndFlagedSet StartRow
      LEFT JOIN BeginEndFlagedSet EndRow on 
          StartRow.Num < EndRow.Num 
          and EndRow.BeginEndFlag in ('Start', 'End') 
      WHERE StartRow.BeginEndFlag = 'Start'
      GROUP BY StartRow.Num
      

      第一步,我们为每一行分配数字:

      Date Abc Num
      2016-04-18 07:10:002 1
      2016-04-18 07:20:00 2 2
      2016-04-18 07:30:00NULL3
      2016-04-18 07:40:00NULL4
      2016-05-01 10:00:002
      5
      2016-05-01 10:10:005 6 kbd>
      2016-05-01 10:20:004 7

      然后比较每一行是否设置为之前和之后的值:

      Date Abc Num LAG(Abc) LEAD(Abc) Flag
      2016-04-18 07:10:002 1 NULL 2 Start
      2016-04-18 07:20:002 2 2 NULL End
      2016-04-18 07:30:00NULL 3 2 NULL
      2016-04-18 07:40:00NULL4 NULL 2
      2016-05-01 10:00:002 5 NULL 5 Start
      2016-05-01 10:10:005 6 2 4
      2016-05-01 10:20:00@ 987654389@7 5 NULL End

      最后,我们正在为每个开始标记的日期寻找相应的结束标记日期:

      Start Date End Date
      2016-04-18 07:10:00.000 2016-04-18 07:20:00.000
      2016-05-01 10:00:00.0002016-05-01 10:20:00.000

      如果您的服务器不支持 LAG 和 LEAD 窗口函数(如我的),则可以选择 BeginEndFlagedSet:

      WITH NumberedSet as
      (
          SELECT 
              Date, 
              Abc, 
              ROW_NUMBER() OVER(ORDER BY Date ASC) as Num
          FROM [YourTable]
      )
      , BeginEndFlagedSet as
      (
          SELECT
              This.Num,
              This.Date,
              This.Abc,
              CASE 
                  WHEN This.Abc IS NOT NULL AND Prev.Abc IS NULL THEN 'Start'
                  WHEN This.Abc IS NOT NULL AND Next.Abc IS NULL THEN 'End'
              END as BeginEndFlag
          FROM NumberedSet as This
          LEFT JOIN NumberedSet as Prev 
              on This.Num = Prev.Num + 1
          LEFT JOIN NumberedSet as Next 
              on This.Num = Next.Num - 1
      )
      

      【讨论】:

      • 谢谢你,@a_horse_with_no_name
      猜你喜欢
      • 2020-10-02
      • 1970-01-01
      • 1970-01-01
      • 2014-05-02
      • 2021-09-16
      • 1970-01-01
      • 2021-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多