【问题标题】:How to get min max date per row type如何获取每行类型的最小最大日期
【发布时间】:2023-03-07 23:39:01
【问题描述】:

场景:一家公司在许多州有许多分支机构。一个州可能有多个分支。每当员工从一个分支转移到另一个分支时,都会在如下表中输入一个条目

| EID |          DT | BRANCH | STATE |
|-----|-------------|--------|-------|
|   1 | 01-JAN-2000 |      A |    AA |
|   1 | 01-JAN-2001 |      B |    AA |
|   1 | 01-JAN-2002 |      C |    AA |
|   1 | 01-JAN-2003 |      D |    AA |
|   1 | 01-JAN-2004 |      E |    BB |
|   1 | 01-JAN-2005 |      F |    BB |
|   1 | 01-JAN-2006 |      G |    BB |
|   1 | 01-JAN-2007 |      H |    BB |
|   1 | 01-JAN-2008 |      A |    AA |
|   1 | 01-JAN-2009 |      B |    AA |
|   1 | 01-JAN-2010 |      C |    AA |
|   1 | 01-JAN-2011 |      D |    AA |

要求是找出员工处于某种状态的持续时间。输出应该是这样的

| STATE |         MIN |         MAX |    Duration |
|-------|-------------|-------------|-------------|
|    AA | 01-JAN-2000 | 01-JAN-2003 |           3 |
|    BB | 01-JAN-2004 | 01-JAN-2007 |           3 |
|    AA | 01-JAN-2008 | 01-JAN-2011 |           3 |

我似乎无法弄清楚如何在 PL/SQL 中做到这一点。漫长的方法是使用 for 循环遍历每一行并找到持续时间。但是有没有办法在不使用循环的情况下在 PLSQL 中做到这一点?

这是一个 SQLFiddle Demo

【问题讨论】:

  • Duration 是日期之间的年差,还是别的什么?
  • 是的,最小最大年数之间的差异。但如果我们能得到 min max,那么找到持续时间并不是什么大问题
  • 为什么要存储过程?

标签: sql oracle plsql


【解决方案1】:

SQL Fiddle

WITH groups AS (
  SELECT
    t1.*,
    ROW_NUMBER() OVER ( ORDER BY dt )
      - ROW_NUMBER() OVER ( PARTITION BY state ORDER BY dt ) AS grp
  FROM t1
)
SELECT state,
       MIN( dt ) AS first_date,
       MAX( dt ) AS last_date,
       TRUNC( ( MAX( dt ) - MIN( dt ) ) / 365 ) AS duration
FROM   groups
GROUP BY state, grp
ORDER BY first_date

Results

| STATE |                     FIRST_DATE |                      LAST_DATE | DURATION |
|-------|--------------------------------|--------------------------------|----------|
|    AA | January, 01 2000 00:00:00+0000 | January, 01 2003 00:00:00+0000 |        3 |
|    BB | January, 01 2004 00:00:00+0000 | January, 01 2007 00:00:00+0000 |        3 |
|    AA | January, 01 2008 00:00:00+0000 | January, 01 2011 00:00:00+0000 |        3 |

至于它是如何工作的:

  • groups 子查询选择每一行并将其分配给一个组,方法是从任何 state 的总行数中减去该行的 state 的行数 - 结果是:
    • 任何具有相同状态的连续系列行将具有相同的组号;和
    • 对于任何给定的状态,随着日期的增加,每组行的组号都会增加(这在比较不同状态的组时不一定适用,但考虑到最后一位中使用的分组,这并不重要)。
  • 最后的查询然后将 stategrp 上的所有内容分组,并为每个组中的日期找到 minmaxdifference

【讨论】:

    【解决方案2】:

    这是完成它的方法之一:

    select max(z.state) as state
         , min(z.dt)    as min_date   /* main query */
         , max(z.dt)    as max_date
         , trunc((max(z.dt) - min(z.dt)) / 365) as duaration
      from (select q.eid
                 , q.dt              /* query # 2*/
                 , state 
                 , sum(grp) over(order by q.dt) as grp
              from (select eid
                         , dt
                         , state     /* query # 1*/
                         , case
                             when state <> lag(state) over(order by dt)
                             then 1
                           end as grp 
                      from t1 ) q
           ) z
      group by z.grp
    

    结果:

    STATE MIN_DATE    MAX_DATE     DUARATION
    ----- ----------- ----------- ----------
    AA    01-JAN-00   01-JAN-03            3
    BB    01-JAN-04   01-JAN-07            3
    AA    01-JAN-08   01-JAN-11            3
    

    SQLFiddle Demo


    附录#1:查询说明。

    为了获得最小和最大日期,我们只需要应用group by 子句,这很明显,但我们不能,因为BB 之前的AA 状态和@987654327 之后的状态之间存在逻辑差异@ 状态。所以我们必须做一些事情来将它们分开,将它们放入不同的逻辑组中。这就是最内部的 (/* query # 1*/) 和 /* query # 2*/ 所做的。查询 #1 找到状态变化的时刻(将当前行 state 与前一个比较。lag() over() 函数用于引用数据集中的前一行),查询 #2 通过计算运行形成一个逻辑组总共grpsum() over() 分析功能负责)。

    查询 #1 告诉我们:

           EID DT           STATE        GRP
    ---------- -----------  -----    ----------
             1 01-JAN-2000   AA    
             1 01-JAN-2001   AA    
             1 01-JAN-2002   AA    
             1 01-JAN-2003   AA    
             1 01-JAN-2004   BB           1  --<-- moment when state changes
             1 01-JAN-2005   BB    
             1 01-JAN-2006   BB    
             1 01-JAN-2007   BB    
             1 01-JAN-2008   AA           1  --<-- moment when state changes
             1 01-JAN-2009   AA    
             1 01-JAN-2010   AA    
             1 01-JAN-2011   AA    
    

    查询 #2 形成逻辑组:

           EID DT           STATE        GRP
    ---------- -----------  -----    ----------
             1 01-JAN-2000   AA    
             1 01-JAN-2001   AA    
             1 01-JAN-2002   AA    
             1 01-JAN-2003   AA    
             1 01-JAN-2004   BB           1   
             1 01-JAN-2005   BB           1
             1 01-JAN-2006   BB           1
             1 01-JAN-2007   BB           1 
             1 01-JAN-2008   AA           2 
             1 01-JAN-2009   AA           2
             1 01-JAN-2010   AA           2
             1 01-JAN-2011   AA           2
    

    然后,在主查询中,我们只是按GRP 分组以产生最终输出。

    【讨论】:

    • 它似乎工作正常,但你能解释一下这里到底发生了什么吗?
    【解决方案3】:

    好的,我更改了查询,但它似乎不起作用:

    with t2 as
    (select t1.*,
      case lag(state,1,state) over (order by dt)
      when state then 0 else 1 end as state_chng
    from t1),
    t3 as 
      (select t2.*,
        sum(state_chng) over (order by dt) as group_id
      from t2)
    select distinct state,
      min(dt) over (partition by GROUP_ID) as min_dt,
      max(dt) over (partition by GROUP_ID) as max_dt
    from t3
    order by 2;
    
    | STATE |                         MIN_DT |                         MAX_DT |
    |-------|--------------------------------|--------------------------------|
    |    AA | January, 01 2000 00:00:00+0000 | January, 01 2003 00:00:00+0000 |
    |    BB | January, 01 2004 00:00:00+0000 | January, 01 2008 00:00:00+0000 |
    |    AA | January, 01 2009 00:00:00+0000 | January, 01 2012 00:00:00+0000 |
    |    BB | January, 01 2013 00:00:00+0000 | January, 01 2014 00:00:00+0000 |
    |    AA | January, 01 2015 00:00:00+0000 | January, 01 2018 00:00:00+0000 |
    

    【讨论】:

    • 这高度依赖于分支数据 - 如果员工在同一状态下两次加入分支,则它不起作用。当您有许多不同大小的组时,它也不起作用:SQLFIDDLE
    【解决方案4】:

    没有存储过程,解析函数是实现这一点的唯一方法。

    WITH s1 AS (
    SELECT eid
         , dt
         , state 
         , CASE WHEN LAG(state) 
                     OVER (PARTITION BY eid 
                               ORDER BY dt) 
                     = state           
                THEN NULL 
                ELSE dt 
           END mindt
         , CASE WHEN LEAD(state) 
                     OVER (PARTITION BY eid 
                               ORDER BY dt) 
                     = state           
                THEN NULL 
                ELSE dt 
           END maxdt
      FROM t1
    ), s2 as (
    select eid
         , state
         , MAX(mindt) 
           OVER (PARTITION BY eid 
                  ORDER BY dt) 
           mindt
         , MAX(maxdt) 
           OVER (PARTITION BY eid 
                     ORDER BY dt) 
           maxdt
      FROM s1
    )
    SELECT eid
         , state
         , mindt
         , MAX(maxdt) maxdt
      FROM s2
     GROUP BY eid
         , state
         , mindt
     ORDER BY eid
         , mindt
    

    【讨论】:

      猜你喜欢
      • 2021-04-18
      • 1970-01-01
      • 2020-02-12
      • 2016-07-23
      • 1970-01-01
      • 1970-01-01
      • 2013-05-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多