【问题标题】:oracle sql - how to prepare a queryoracle sql - 如何准备查询
【发布时间】:2025-12-04 13:15:01
【问题描述】:

我已经获得了如下所示的数据库数据(有关日期的其他信息:包括 valid_from 中的日期,不包括 valid_to 中的日期)

obj_number status valid_from valid_to
A1001 active 01.01.2018 01.01.2019
A1001 pending 01.01.2019 31.03.2019
A1001 pending 31.03.2019 30.06.2019
A1001 pending 30.06.2019 31.12.2019
A1001 active 31.12.2019 31.12.2020
A1001 active 31.12.2020

当状态发生变化时,我必须合并数据,但 valid_from 应该来自最旧的记录,valid_to 应该来自最新的记录。您可以在上面看到的数据的结果应该像您在下面看到的那样

obj_number status valid_from valid_to
A1001 active 01.01.2018 01.01.2019
A1001 pending 01.01.2019 31.12.2019
A1001 active 31.12.2019

如何编写一个sql查询来实现呢?

提前致谢

【问题讨论】:

    标签: sql oracle


    【解决方案1】:

    这是一种有趣的方法(假设您使用的是 oracle 12c+):

    select *
    from tt
    match_recognize (
      partition by
        obj_number
      order by
        valid_from
      measures
        init.status as status,
        first(valid_from) as valid_from,
        last(valid_to) as valid_to
      one row per match
      pattern (init same_status*)
      define
        same_status as status = prev(status)
    )
    ;
    

    sqlfiddle

    使用MATCH_RECOGNIZE我们可以

    通过其 PARTITION BY 和 ORDER BY 子句对 MATCH_RECOGNIZE 子句中使用的数据进行逻辑分区和排序。

    使用 MATCH_RECOGNIZE 子句的 PATTERN 子句定义要查找的行模式。这些模式使用正则表达式语法,这是一种强大且富有表现力的特性,适用于您定义的模式变量。

    在 DEFINE 子句中指定将行映射到行模式变量所需的逻辑条件。

    在 MEASURES 子句中定义度量,它们是可用于 SQL 查询其他部分的表达式。

    那么它是如何工作的呢?

    1. 我们partition byobj_number
    2. order by 使用 valid_from 的分区内的行
    3. 使用measures定义我们希望看到的列
    4. 使用pattern定义我们想要匹配的内容,在这种情况下,我们取一行,然后尝试匹配尽可能多的same_status行(*是0-many重复正则表达式中的符号)
    5. define same_status 是什么意思,在这种情况下,如果该行与前一行具有相同的状态,则该行是 same_status
    6. 在度量中,我们从匹配的第一行中选择 valid_from,从匹配的最后一行中选择 valid_to

    【讨论】:

    • Petr,惊人的解决方案!!!它工作正常。老实说,现在我必须花一些时间来了解它是如何工作的以及如何使用 match_recognize
    • 也许你会发现this video hepful,我觉得这是一个很好的主题介绍
    【解决方案2】:

    您需要检查 status 和日期(如果不重叠,则应为相同的连续状态生成不同的行),以便您可以使用 lagsum 分析函数以及 group by,如下所示:

    select obj_number, status, min(valid_from) as valid_from, 
           case when count(case when valid_to is null then 1 end) = 0 
                then max(valid_to) 
           end as valid_to
    From
    (select t.*,
           sum(case when lgtodt >= valid_from then 0 else 1 end) 
              over (partition by obj_number, status order by valid_from) as sm
      from
    (select t.*,
           lag(valid_to) over (partition by obj_number, status order by valid_from) as lgtodt
      from your_table t) t ) t
    group by obj_number, status, sm
    

    【讨论】:

    • 有趣的解决方案,但合并行中的日期不正确(有效到来自同一记录而不是最后一个合并记录)
    • 好的,更新了答案。立即检查。
    • 感谢更新 - 现在它可以正常工作了!!! (请编辑查询并从第 4 行到第 5 行添加,以便其他人清楚)
    【解决方案3】:

    这是一种孤岛问题。假设相邻没有间隙,row_number()s 的区别是最简单的方法:

    select obj_number, status, min(valid_from),
           (case when count(*) = count(valid_to)
                 then max(valid_to)
            end)
    from (select t.*,
                 row_number() over (partition by obj_number) order by valid_from) as seqnum_2,
                 row_number() over (partition by obj_number, status order by valid_from) as seqnum
          from t
         ) t
    group by obj_number, status, (seqnum - seqnum_2);
    

    注意处理valid_toNULL 值的额外逻辑。

    【讨论】:

    • 谢谢戈登。小改动后就可以了! (请在 t.* 之后添加逗号,并从第 6 行和第 7 行删除多余的括号以供其他用户阅读)。它应该看起来像这样:select t.*, row_number() over (partition by obj_number order by valid_from) as seqnum_2, row_number() over (partition by obj_number, status order by valid_from) as seqnum