Sql server 2008 r2 SQL Server拆分重叠的日期范围
我需要分割重叠的日期范围。我有一个主表(本例中我称之为Employment),我需要从该表中返回一个人的所有开始-结束日期范围。我还有多个子表(由Car和Food表示),我想返回在主表中给定的时间内子表中活动的值。这将涉及在子表项更改时拆分主表日期范围 我不想返回不在主表中的日期的子表信息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
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=@