【问题标题】:Collapse consecutive similar records into a single record将连续的相似记录折叠成一条记录
【发布时间】:2017-05-26 15:57:24
【问题描述】:

我有来自旧系统的人员的记录,我正试图将其转换为新系统。在旧系统中,一个人最终可能会在同一位置获得多条记录。他们也可以从一个位置移动到另一个位置,然后返回之前的位置。以下是一些示例数据:

PersonID  | LocationID | StartDate  | EndDate
1         | 1          | 1980-07-30 | 2007-07-16
1         | 1          | 2007-07-16 | 2008-01-30
1         | 2          | 2008-01-30 | 2009-03-02
1         | 2          | 2009-03-02 | 2009-11-06
1         | 3          | 2014-07-16 | 2015-01-16
1         | 1          | 2016-01-26 | 2999-12-31

我想折叠此数据,以便获得任何连续 LocationID 的日期范围。对于上面的数据,这是我所期望的:

PersonID  | LocationID | StartDate  | EndDate
1         | 1          | 1980-07-30 | 2008-01-30
1         | 2          | 2008-01-30 | 2009-11-06
1         | 3          | 2014-07-16 | 2015-01-16
1         | 1          | 2016-01-26 | 2999-12-31

我不确定如何执行此操作。我之前尝试加入上一条记录,但这仅在有两个连续位置时才有效,而不是 3 个或更多(可能存在未定义的连续记录数量)。

select
    a.PersonID,
    a.LocationID,
    a.StartDate,
    a.EndDate,
    case when a.LocationID = b.LocationID then a.PK_ID else b.PK_ID end as NewID
from employees a
left outer join employees b
on a.PersonID = b.PersonID
and a.PK_ID = b.PK_ID - 1

那么,如何编写查询来获得所需的结果?

注意:我们将“2999-12-31”视为我们的“NULL”日期字段

【问题讨论】:

    标签: sql sql-server tsql sql-server-2008-r2


    【解决方案1】:

    这是一个经典的峡谷和岛屿(编辑-更正了更大的跨度 2999)

    Select [PersonID]
          ,[LocationID]
          ,[StartDate]  = min(D)
          ,[EndDate]    = max(D)
     From (
            Select *
                  ,Grp = Row_Number() over (Order By D) - Row_Number() over (Partition By [PersonID],[LocationID] Order By D) 
             from YourTable A
             Cross Apply (
                            Select Top (DateDiff(DAY,A.[StartDate],A.[EndDate])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[StartDate])  
                            From  master..spt_values n1,master..spt_values n2
                         ) B
          ) G
     Group By [PersonID],[LocationID],Grp
     Order By [PersonID],min(D)
    

    退货

    PersonID    LocationID  StartDate   EndDate
    1           1           1980-07-30  2008-01-30
    1           2           2008-01-30  2009-11-06
    1           3           2014-07-16  2015-01-16
    1           1           2016-01-26  2999-12-31
    

    使用您的原始查询

    Select [PersonID]
          ,[LocationID]
          ,[StartDate]  = min(D)
          ,[EndDate]    = max(D)
     From (
            Select *
                  ,Grp = Row_Number() over (Order By D) - Row_Number() over (Partition By [PersonID],[LocationID] Order By D) 
             From (
                    -- Your Original Query
                    select
                        a.PersonID,
                        a.LocationID,
                        a.StartDate,
                        a.EndDate,
                        case when a.LocationID = b.LocationID then a.PK_ID else b.PK_ID end as NewID
                    from employees a
                    left outer join employees b
                    on a.PersonID = b.PersonID
                    and a.PK_ID = b.PK_ID - 1
                  ) A
             Cross Apply (
                            Select Top (DateDiff(DAY,A.[StartDate],A.[EndDate])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[StartDate])  
                            From  master..spt_values n1,master..spt_values n2
                         ) B
          ) G
     Group By [PersonID],[LocationID],Grp
     Order By [PersonID],min(D)
    

    征求意见

    让我们把它分解成它的组成部分。

    1) CROSS APPLY 部分: 这会将单个记录扩展为 N 条记录。例如:

    Declare @YourTable Table ([PersonID] int,[LocationID] int,[StartDate] date,[EndDate] date)
    Insert Into @YourTable Values
     (1,1,'1980-07-01','1980-07-03' )
    ,(1,1,'1980-07-02','1980-07-04' )  -- Notice the Overlap
    ,(1,2,'2008-01-30','2008-02-05')
    
    Select *
        from @YourTable A
        Cross Apply (
                    Select Top (DateDiff(DAY,A.[StartDate],A.[EndDate])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[StartDate])  
                    From  master..spt_values n1,master..spt_values n2
                    ) B
    

    上面的查询会生成

    2) Grp 部分: 如果我提供一个简单的示例,可能会更容易:

    Declare @YourTable Table ([PersonID] int,[LocationID] int,[StartDate] date,[EndDate] date)
    Insert Into @YourTable Values
     (1,1,'1980-07-01','1980-07-03' )
    ,(1,1,'1980-07-02','1980-07-04' )  -- Notice the Overlap
    ,(1,2,'2008-01-30','2008-02-05')
    
    Select *
          ,Grp = Row_Number() over (Order By D) - Row_Number() over (Partition By [PersonID],[LocationID] Order By D) 
          ,RN1 = Row_Number() over (Order By D)
          ,RN2 = Row_Number() over (Partition By [PersonID],[LocationID] Order By D) 
        from @YourTable A
        Cross Apply (
                    Select Top (DateDiff(DAY,A.[StartDate],A.[EndDate])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[StartDate])  
                    From  master..spt_values n1,master..spt_values n2
                    ) B
    

    以上查询生成:

    RN1 和 RN2 是 GRP 的突破,只是为了说明机制。注意 RN1 减去 RN2 等于 GRP。一旦我们有了 GRP,就可以通过 group by 进行简单的聚合了

    3) 齐心协力:

    Declare @YourTable Table ([PersonID] int,[LocationID] int,[StartDate] date,[EndDate] date)
    Insert Into @YourTable Values
     (1,1,'1980-07-01','1980-07-03' )
    ,(1,1,'1980-07-02','1980-07-04' )  -- Notice the Overlap
    ,(1,2,'2008-01-30','2008-02-05')
    
    Select [PersonID]
          ,[LocationID]
          ,[StartDate]  = min(D)
          ,[EndDate]    = max(D)
     From (
            Select *
                  ,Grp = Row_Number() over (Order By D) - Row_Number() over (Partition By [PersonID],[LocationID] Order By D) 
                from @YourTable A
                Cross Apply (
                            Select Top (DateDiff(DAY,A.[StartDate],A.[EndDate])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[StartDate])  
                            From  master..spt_values n1,master..spt_values n2
                            ) B
          ) G
     Group By [PersonID],[LocationID],Grp
     Order By [PersonID],min(D)
    

    返回

    【讨论】:

    • 这解决了我的问题!你能解释一下这是如何工作的吗?
    • @DForck42 我稍后会添加一些 cmets。
    【解决方案2】:

    对于您的样本数据,您可以使用行数差异的方法:

    select personid, locationid, min(startdate), max(enddate)
    from (select e.*,
                 row_number() over (partition by personid order by startdate) as seqnum_p,
                 row_number() over (partition by personid, locationid order by startdate) as seqnum_pl
          from employees  e
         ) e
    group by (seqnum_p - seqnum_pl), personid, locationid;
    

    这假定开始日期和结束日期是连续的。也就是说,同一地点的给定员工没有间隙。

    【讨论】:

    • 比我的回答快得多。刚刚更正了两个错别字+1
    猜你喜欢
    • 1970-01-01
    • 2016-02-06
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    • 2021-08-20
    • 2017-05-01
    • 1970-01-01
    • 2013-04-04
    相关资源
    最近更新 更多