Sql server 2008 r2 SQL Server拆分重叠的日期范围

Sql server 2008 r2 SQL Server拆分重叠的日期范围,sql-server-2008-r2,Sql Server 2008 R2,我需要分割重叠的日期范围。我有一个主表(本例中我称之为Employment),我需要从该表中返回一个人的所有开始-结束日期范围。我还有多个子表(由Car和Food表示),我想返回在主表中给定的时间内子表中活动的值。这将涉及在子表项更改时拆分主表日期范围 我不想返回不在主表中的日期的子表信息 DECLARE @Employment TABLE ( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE ) DEC

我需要分割重叠的日期范围。我有一个主表(本例中我称之为Employment),我需要从该表中返回一个人的所有开始-结束日期范围。我还有多个子表(由Car和Food表示),我想返回在主表中给定的时间内子表中活动的值。这将涉及在子表项更改时拆分主表日期范围

我不想返回不在主表中的日期的子表信息

DECLARE @Employment TABLE
( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE )

DECLARE @Car TABLE
( Person_ID INT, Car VARCHAR(50), Begin_Date DATE, End_Date DATE )

DECLARE @Food TABLE
( Person_ID INT, Food VARCHAR(50), Begin_Date DATE, End_Date DATE )


INSERT INTO @Employment ( [Person_ID], [Employment], [Begin_Date], [End_Date] )
VALUES  ( 123 , 'ACME' , '1986-01-01' , '1990-12-31' )
,       ( 123 , 'Office Corp' , '1995-05-15' , '1998-10-03' )
,       ( 123 , 'Job 3' , '1998-10-04' , '2999-12-31' )

INSERT INTO @Car ( [Person_ID] , [Car] , [Begin_Date] , [End_Date] )
VALUES  ( 123, 'Red Car', '1986-05-01', '1997-06-23' )
,       ( 123, 'Blue Car', '1997-07-03', '2999-12-31' )

INSERT INTO @Food ( [Person_ID], [Food], [Begin_Date], [End_Date] )
VALUES  ( 123, 'Eggs', '1997-01-01', '1997-03-09' )
,       ( 123, 'Donuts', '2001-02-23', '2001-02-25' )
对于上述数据,结果应为:

Person_ID    Employment      Food        Car        Begin_Date    End_Date
123          ACME                                   1986-01-01    1986-04-30
123          ACME                        Red Car    1986-05-01    1990-12-31
123          Office Corp                 Red Car    1995-05-15    1996-12-31
123          Office Corp     Eggs        Red Car    1997-01-01    1997-03-09
123          Office Corp                 Red Car    1997-03-10    1997-06-23
123          Office Corp                            1997-06-24    1997-07-02
123          Office Corp                 Blue Car   1997-07-03    1998-10-03
123          Job 3                       Blue Car   1998-10-04    2001-02-22
123          Job 3           Donuts      Blue Car   2001-02-23    2001-02-25
123          Job 3                       Blue Car   2001-02-26    2999-12-31
第一排是他在ACME工作的时间,在那里他没有车,也没有奇怪的食物癖。在第二排,他买了一辆车,仍然在ACME工作。在第三排,他换了工作到Office公司,但仍然拥有那辆红色的汽车。请注意,在他失业期间,我们没有返回数据,即使他有一辆红色的汽车。我们只想知道在就业表中有价值的时候,汽车和食品表中有什么


我找到了一个SQL Server 2012的解决方案,它使用LEAD/LAG函数来实现这一点,但我坚持使用2008 R2。

要将该博客中的2012解决方案更改为与2008一起使用,您需要在以下内容中替换LEAD

with
ValidDates as …
,
ValidDateRanges1 as
(
select EmployeeNo, Date as ValidFrom, lead(Date,1) over (partition by EmployeeNo order by Date) ValidTo
from ValidDates
)
有很多方法可以做到这一点,但其中一个例子是自连接到同一个表+1行(这实际上就是潜在客户所做的)。一种方法是通过添加另一个中间CTE(例如ValidDatesWithRowno),在上一个表中添加一个行号(这样很容易找到下一行)。然后对EmployeeNo相同且rowno=rowno+1的表进行左外部联接,并使用该值替换前导。如果你想要一个潜在客户2,你会加入rowno+2,等等。所以2008版看起来像

with
ValidDates as …
,
ValidDatesWithRowno as  --This is the ValidDates + a RowNo for easy self joining below
(
select EmployeeNo, Date, ROW_NUMBER() OVER (ORDER BY EmployeeNo, Date) as RowNo from ValidDates
)
,
ValidDateRanges1 as
(
select VD.EmployeeNo, VD.Date as ValidFrom, VDLead1.Date as ValidTo
from ValidDatesWithRowno VD
    left outer join ValidDatesWithRowno VDLead1 on VDLead1.EmployeeNo = VD.EmployeeNo 
        and VDLead1.RowNo = VD.RowNo + 1
)

所述解决方案的其余部分看起来将在2008年如您所愿地工作。

以下是我提出的答案。它很管用,但不太好看

它经历了两个阶段,首先拆分所有重叠的雇佣/乘车日期,然后再次运行相同的SQL添加食物日期并再次拆分所有重叠的日期

DECLARE @Employment TABLE
( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE )

DECLARE @Car TABLE
( Person_ID INT, Car VARCHAR(50), Begin_Date DATE, End_Date DATE )

DECLARE @Food TABLE
( Person_ID INT, Food VARCHAR(50), Begin_Date DATE, End_Date DATE )


INSERT INTO @Employment ( [Person_ID], [Employment], [Begin_Date], [End_Date] )
VALUES  ( 123 , 'ACME' , '1986-01-01' , '1990-12-31' )
,       ( 123 , 'Office Corp' , '1995-05-15' , '1998-10-03' )
,       ( 123 , 'Job 3' , '1998-10-04' , '2999-12-31' )

INSERT INTO @Car ( [Person_ID] , [Car] , [Begin_Date] , [End_Date] )
VALUES  ( 123, 'Red Car', '1986-05-01', '1997-06-23' )
,       ( 123, 'Blue Car', '1997-07-03', '2999-12-31' )

INSERT INTO @Food ( [Person_ID], [Food], [Begin_Date], [End_Date] )
VALUES  ( 123, 'Eggs', '1997-01-01', '1997-03-09' )
,       ( 123, 'Donuts', '2001-02-23', '2001-02-25' )


DECLARE @Person_ID INT = 123;


--A table to hold date ranges that need to be merged together
DECLARE @DatesToMerge TABLE 
(
   ID INT,
   Person_ID INT,
   Date_Type VARCHAR(10),
   Begin_Date DATETIME,
   End_Date DATETIME
)

INSERT INTO @DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Car])
     , Person_ID
     , 'Car'
     , Begin_Date
     , End_Date
FROM @Car
WHERE Person_ID = @Person_ID

INSERT INTO @DatesToMerge 
SELECT ROW_NUMBER() OVER(ORDER BY [Employment])
     , Person_ID
     , 'Employment'
     , Begin_Date
     , End_Date
FROM @Employment
WHERE Person_ID = @Person_ID;


--A table to hold the merged @Employment and Car records
DECLARE @EmploymentAndCar TABLE
(
    RowNumber INT,
    Person_ID INT,
    Begin_Date DATETIME,
    End_Date DATETIME
)

;
WITH CarCTE AS 
(--This CTE grabs just the Car rows so we can compare and split dates from them
    SELECT  ID,
            Person_ID,
            Date_Type,
            Begin_Date,
            End_Date
    FROM @DatesToMerge
    WHERE Date_Type = 'Car'
),

NewRowsCTE AS 
( --This CTE creates just new rows starting after the Car dates for each @Employment date range
    SELECT  a.ID,
            a.Person_ID,
            a.Date_Type,
            DATEADD(DAY,1,b.End_Date) AS Begin_Date,
            a.End_Date
    FROM @DatesToMerge a
    INNER JOIN CarCTE b
    ON a.Begin_Date <= b.Begin_Date
    AND a.End_Date > b.Begin_Date
    AND a.End_Date > b.End_Date -- This is needed because if both the Car and @Employment end on the same date, there is split row after 
),

UnionCTE AS 
( -- This CTE merges the new rows with the existing ones
    SELECT  ID,
            Person_ID,
            Date_Type,
            Begin_Date,
            End_Date
    FROM @DatesToMerge
    UNION ALL
    SELECT  ID,
            Person_ID,
            Date_Type,
            Begin_Date,
            End_Date
    FROM NewRowsCTE
),

FixEndDateCTE AS 
(
    SELECT  CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date) AS FixID, 
            MIN(d.Begin_Date) AS Begin_Date
    FROM UnionCTE c
    LEFT OUTER JOIN CarCTE d
    ON c.Begin_Date < d.Begin_Date
    AND c.End_Date >= d.Begin_Date
    WHERE c.Date_Type <> 'Car'
    GROUP BY CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date)
),

Finalize AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY e.Begin_Date) AS RowNumber,
            e.Person_ID,
            e.Begin_Date,
            CASE    WHEN f.Begin_Date IS NULL THEN e.End_Date
                    ELSE DATEADD (DAY,-1,f.Begin_Date)
                    END AS EndDate
    FROM UnionCTE e
    LEFT OUTER JOIN FixEndDateCTE f
    ON (CONVERT (CHAR,e.ID)+CONVERT (CHAR,e.Begin_Date)) = f.FixID 
)

INSERT INTO @EmploymentAndCar ( RowNumber, Person_ID, Begin_Date, End_Date )

SELECT F.RowNumber
     , F.Person_ID
     , F.Begin_Date
     , F.EndDate
FROM Finalize F
INNER JOIN @Employment Employment
ON F.Begin_Date BETWEEN Employment.Begin_Date AND Employment.End_Date AND Employment.Person_ID = @Person_ID
ORDER BY F.Begin_Date


--------------------------------------------------------------------------------------------------
--Now that the Employment and Car dates have been merged, empty the DatesToMerge table
DELETE FROM @DatesToMerge;

--Reload the DatesToMerge table with the newly-merged Employment and Car records, 
--and the Food records that still need to be merged
INSERT INTO @DatesToMerge
SELECT RowNumber
     , Person_ID
     , 'PtBCar'
     , Begin_Date
     , End_Date
FROM @EmploymentAndCar
WHERE Person_ID = @Person_ID

INSERT INTO @DatesToMerge 
SELECT ROW_NUMBER() OVER(ORDER BY [Food]) 
     , Person_ID
     , 'Food'
     , Begin_Date
     , End_Date
FROM @Food
WHERE Person_ID = @Person_ID


;
WITH CarCTE AS 
(--This CTE grabs just the Food rows so we can compare and split dates from them
    SELECT  ID,
            Person_ID,
            Date_Type,
            Begin_Date,
            End_Date
    FROM @DatesToMerge
    WHERE Date_Type = 'Food'
),

NewRowsCTE AS 
( --This CTE creates just new rows starting after the Food dates for each Employment date range
    SELECT  a.ID,
            a.Person_ID,
            a.Date_Type,
            DATEADD(DAY,1,b.End_Date) AS Begin_Date,
            a.End_Date
    FROM @DatesToMerge a
    INNER JOIN CarCTE b
    ON a.Begin_Date <= b.Begin_Date
    AND a.End_Date > b.Begin_Date
    AND a.End_Date > b.End_Date -- This is needed because if both the Food and Car/Employment end on the same date, there is split row after 
),

UnionCTE AS 
( -- This CTE merges the new rows with the existing ones
    SELECT  ID,
            Person_ID,
            Date_Type,
            Begin_Date,
            End_Date
    FROM @DatesToMerge
    UNION ALL
    SELECT  ID,
            Person_ID,
            Date_Type,
            Begin_Date,
            End_Date
    FROM NewRowsCTE
),

FixEndDateCTE AS 
(
    SELECT  CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date) AS FixID, 
            MIN(d.Begin_Date) AS Begin_Date
    FROM UnionCTE c
    LEFT OUTER JOIN CarCTE d
    ON c.Begin_Date < d.Begin_Date
    AND c.End_Date >= d.Begin_Date
    WHERE c.Date_Type <> 'Food'
    GROUP BY CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date)
),

Finalize AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY e.Begin_Date) AS RowNumber,
            e.Person_ID,
            e.Begin_Date,
            CASE    WHEN f.Begin_Date IS NULL THEN e.End_Date
                    ELSE DATEADD (DAY,-1,f.Begin_Date)
                    END AS EndDate
    FROM UnionCTE e
    LEFT OUTER JOIN FixEndDateCTE f
    ON (CONVERT (CHAR,e.ID)+CONVERT (CHAR,e.Begin_Date)) = f.FixID 
)

SELECT DISTINCT
       F.Person_ID
     , Employment
     , Car
     , Food
     , F.Begin_Date
     , F.EndDate
FROM Finalize F
INNER JOIN @Employment Employment
ON F.Begin_Date BETWEEN Employment.Begin_Date AND Employment.End_Date AND Employment.Person_ID = @Person_ID

LEFT JOIN @Car Car
ON Car.[Begin_Date] <= F.Begin_Date 
AND Car.[End_Date] >= F.[EndDate]
AND Car.Person_ID = @Person_ID

LEFT JOIN @Food Food
ON Food.[Begin_Date] <= F.[Begin_Date] 
AND Food.[End_Date] >= F.[EndDate]
AND Food.Person_ID = @Person_ID

ORDER BY F.Begin_Date
DECLARE@Employment表
(人员ID INT,雇佣VARCHAR(50),开始日期,结束日期)
声明@Car表
(个人ID整数,汽车VARCHAR(50),开始日期,结束日期)
申报食物表
(人员ID INT、食品VARCHAR(50)、开始日期、结束日期)
插入@Employment([人员ID]、[就业]、[开始日期]、[结束日期])
数值(123,‘顶点’、‘1986-01-01’、‘1990-12-31’)
,(123,“办公公司”,“1995-05-15”,“1998-10-03”)
,(123,“工作3”,“1998-10-04”,“2999-12-31”)
插入@Car([Person\u ID]、[Car]、[Begin\u Date]、[End\u Date])
数值(123,‘红色汽车’、‘1986-05-01’、‘1997-06-23’)
,(123,“蓝色汽车”,“1997-07-03”,“2999-12-31”)
插入@Food([个人ID]、[食物]、[开始日期]、[结束日期])
数值(123,‘蛋’、‘1997-01-01’、‘1997-03-09’)
,(123,‘甜甜圈’、‘2001-02-23’、‘2001-02-25’)
声明@Person\u ID INT=123;
--用于保存需要合并在一起的日期范围的表
声明@DatesToMerge表
(
ID INT,
个人身份信息,
日期类型VARCHAR(10),
开始日期日期时间,
结束日期日期时间
)
插入@DatesToMerge
选择上面的行号()(按[汽车]订购)
,人名
“汽车”
,开始日期
,结束日期
来自@Car
其中Person\u ID=@Person\u ID
插入@DatesToMerge
选择上面的行号()(按[雇佣]订购)
,人名
“就业”
,开始日期
,结束日期
来自@Employment
其中Person\u ID=@Person\u ID;
--保存合并的@Employment和Car记录的表
声明@EmploymentAndCar表
(
行数INT,
个人身份信息,
开始日期日期时间,
结束日期日期时间
)
;
以CarCTE为例
(-此CTE仅获取汽车行,以便我们可以比较和分割它们的日期
选择ID,
个人识别码,
日期类型,
开始约会,
结束日期
来自@DatesToMerge
其中Date_Type='Car'
),
纽罗维斯特AS
(-此CTE仅为每个@Employment date范围创建在Car日期之后开始的新行
选择a.ID,
a、 个人识别码,
a、 日期类型,
日期添加(第1天,b.结束日期)作为开始日期,
a、 结束日期
从@DatesToMerge a
内连接槽b
在a.开始日期b.开始日期
a.End_Date>b.End_Date——这是必需的,因为如果Car和@Employment都在同一日期结束,则后面会有拆分行
),
工会
(-此CTE将新行与现有行合并
选择ID,
个人识别码,
日期类型,
开始约会,
结束日期
来自@DatesToMerge
联合所有
选择ID,
个人识别码,
日期类型,
开始约会,
结束日期
来自纽罗维斯特
),
固定日期
(
选择CONVERT(CHAR,c.ID)+CONVERT(CHAR,c.Begin\u Date)作为FixID,
最小值(d.开始日期)作为开始日期
来自UnionCTE c
左外连接槽d
在c.开始日期=d.开始日期
其中c.日期\类型“汽车”
按转换(字符,c.ID)+转换(字符,c.Begin\u日期)分组
),
定稿为
(
选择(按e.Begin_日期排序)上方的行编号()作为行编号,
e、 个人识别码,
e、 开始约会,
如果f.Begin\u Date为空,则e.End\u Date为空
ELSE DATEADD(日期-1,f.开始日期)
结束为结束日期
来自UnionCTE
左外联接FixedDateCte f
ON(CONVERT(CHAR,e.ID)+CONVERT(CHAR,e.Begin_Date))=f.FixID
)
插入@EmploymentAndCar(行号、人员ID、开始日期、结束日期)
选择F.RowNumber
,F.个人识别码
,F.开始日期
,F.EndDate
从F
内部连接@就业
在F.Begin_日期,介于Employment.Begin_日期和Employment.End_日期和Employment.Person_ID=@