获取比较当前行和上一行的最小生效日期

Fetch minimum effective date comparing current and previous rows

我在 Oracle 12c 上工作,我需要根据列的分组获取最小生效日期,但是只有一段时间,直到没有变化,示例如下:

假设我们的员工 ID 在一段时间内更改了部门,并且我们想要捕获每个部门的最低生效日期,但最低生效日期仅在更改之前。

EMP_ID EFF_DT DEPT_ID
100 01/01/2015 ENGINEERING
100 01/01/2016 ENGINEERING
100 01/01/2017 ENGINEERING
100 01/01/2018 FINANCE
100 01/01/2019 FINANCE
100 01/01/2020 ENGINEERING
100 01/01/2021 ENGINEERING

目标:

EMP_ID EFF_DT DEPT_ID
100 01/01/2015 ENGINEERING
100 01/01/2018 FINANCE
100 01/01/2020 ENGINEERING

如何实现?我尝试做 LAG 并尝试比较当前和以前的但无法在没有变化的时间范围内识别最小值。

使用 match_recognize 的解决方案(自数据库版本 12.1 起可用)

正在设置测试数据:

alter session set nls_date_format='mm/dd/yyyy';

create table my_table (emp_id, eff_dt, dept_id) as
  select 100, to_date('01/01/2015'), 'ENGINEERING' from dual union all
  select 100, to_date('01/01/2016'), 'ENGINEERING' from dual union all
  select 100, to_date('01/01/2017'), 'ENGINEERING' from dual union all
  select 100, to_date('01/01/2018'), 'FINANCE'     from dual union all
  select 100, to_date('01/01/2019'), 'FINANCE'     from dual union all
  select 100, to_date('01/01/2020'), 'ENGINEERING' from dual union all
  select 100, to_date('01/01/2021'), 'ENGINEERING' from dual
;

查询和输出:

select emp_id, eff_dt, dept_id
from   my_table
match_recognize(
  partition by emp_id
  order     by eff_dt
  all rows per match
  pattern   ( a {- b* -} )
  define    b as dept_id = a.dept_id
);

    EMP_ID EFF_DT     DEPT_ID    
---------- ---------- -----------
       100 01/01/2015 ENGINEERING
       100 01/01/2018 FINANCE    
       100 01/01/2020 ENGINEERING

简要说明:

match_recognize 子句按 emp_id 对输入行进行分区,并按 eff_dt 对它们进行排序。然后,它在每个分区内并按照日期顺序进一步将行划分为“匹配项”,匹配 pattern 子句中给出的模式。这是单个 a 行后跟 0 个或多个 b 行,其中 b 被定义(在 define 子句中)以要求 dept_id 是与比赛的第一行相同。一行成为 a 行没有条件;任何行,如果它不能被分类为 b,将被分类为 a(并且它将开始一个新的匹配!)

返回匹配中的“所有”行,pattern 子句中包含在 {- -} 中的行除外。即:返回每个匹配项中的 a 行(第一行),而不返回 b 行。完全符合规定。

编辑

对于 Oracle 11.2 或更低版本,以及还不支持 match_recognize 的数据库产品(Oracle 除外),这可以通过解析函数来完成,绝大多数数据库都支持这些函数数据库。

以下版本与 match_recognize 解决方案几乎完全相同:

select emp_id, eff_dt, dept_id
from   (
         select emp_id, eff_dt, dept_id,
                case when lag(dept_id) over (partition by emp_id
                                             order     by eff_dt) = dept_id
                     then 'B' else 'A' end as classifier
         from   my_table
       )
where  classifier = 'A'
;

实现此查询的最简单也是我认为最快的方法使用 lag():

select t.*
from (select t.*,
             lag(dept_id) over (partition by emp_id order by eff_dt) as prev_dept_id
      from t
     ) t
where prev_dept_id is null or prev_dept_id <> dept_id;