这是一个非常经典的重叠日期范围问题。
这是一个很好的答案,涵盖了它:
Determine Whether Two Date Ranges Overlap
对于您的代码,它会是这样的(有关用于生成临时表的代码,请参阅帖子底部):
DECLARE @StartDate date = '2015-01-01',
@EndDate date = '2020-12-14';
SELECT s.empl_id
, s.scale_id
, v.amount
, StartDate = IIF(x.EmpScaleStartDate < @StartDate, @StartDate, x.EmpScaleStartDate) -- Clamp the start date
, EndDate = x.EmpScaleEndDate
FROM #scale_table s
JOIN #value_table v ON v.scale_id = s.scale_id
CROSS APPLY (
SELECT EmpScaleStartDate = IIF(v.date_from <= s.date_from, s.date_from, v.date_from) -- Pick the valid start date
, EmpScaleEndDate = IIF(s.date_to <= v.date_to , s.date_to , v.date_to) -- Pick the valid end date
) x
WHERE (s.date_from <= v.date_to) AND (s.date_to >= v.date_from) -- Do the two ranges overlap?
AND (s.date_to >= @StartDate AND s.date_from <= @EndDate) -- Only look at records within our target range
ORDER BY s.empl_id, s.date_from
对于emp 187,这将输出:
| empl_id | scale_id | amount | StartDate | EndDate |
|---------|----------|-----------|------------|------------|
| 187 | B3EL9 | 78084.00 | 2015-01-01 | 2015-06-30 |
| 187 | B3EL9 | 81432.00 | 2015-07-01 | 2017-06-30 |
| 187 | B4EL6 | 81720.00 | 2017-07-01 | 2019-10-31 |
| 187 | B5EL9 | 100092.00 | 2019-11-01 | 2099-12-31 |
解释:
您基本上要处理 3 个单独的日期范围,并且您想找到它们都重叠的位置。
这些日期范围是:
- 薪级表金额的开始/结束日期
- 将工资表分配给员工的期间的开始/结束日期
- 报告的开始/结束日期
第一步是只抓取有效记录:
DECLARE @StartDate date = '2015-01-01', @EndDate date = '2020-12-14';
SELECT *
FROM #scale_table s
WHERE s.date_to >= @StartDate AND s.date_from <= @EndDate
这是说:
确保我们只提取规模日期在我们关心的范围内的员工记录。由于您提供的数据,这是每个员工的记录。
下一步:
我们希望将#value_table 加入这些记录,以便我们可以查看在他们拥有这些薪级表期间是否有任何变化。
DECLARE @StartDate date = '2015-01-01', @EndDate date = '2020-12-14';
SELECT *
FROM #scale_table s
JOIN #value_table v ON v.scale_id = s.scale_id
WHERE (s.date_from <= v.date_to) AND (s.date_to >= v.date_from) -- Do the two ranges overlap?
AND (s.date_to >= @StartDate AND s.date_from <= @EndDate) -- Only look at records within our target range
现在我们有了一个可以玩的数据集。我们记录了每位员工及其薪酬等级历史记录,以及薪酬等级本身的变化。
现在我们只需要弄清楚如何获得正确的开始/结束日期...
下一步:
这就是它的用武之地:
CROSS APPLY (
SELECT EmpScaleStartDate = IIF(v.date_from <= s.date_from, s.date_from, v.date_from)
, EmpScaleEndDate = IIF(v.date_to >= s.date_to , s.date_to , v.date_to)
) x
此逻辑决定显示哪个开始日期或结束日期。我只使用了CROSS APPLY,以便在整个查询过程中更容易重复使用此逻辑,并使其更具可读性,这样您就不会在一行中有一堆嵌套函数。
如果187 有B3EL9 从2014-03-01 到2017-06-30
而薪级表从2013-01-01 到2015-06-30 支付了 78,084 美元
那么我们应该为该行显示2014-03-01 的开始日期和2015-06-30 的结束日期。
最后一步:
钳制第一个日期。钳位意味着将一个值绑定在其他两个值的范围内。由于此报告是基于2015-01-01 运行到当前。我们希望从 2013 年或 2014 年开始的范围改为显示 2015-01-01。
这就是所有这些:
SELECT StartDate = IIF(x.EmpScaleStartDate < @StartDate, @StartDate, x.EmpScaleStartDate)
SQL 形式的示例数据:
IF OBJECT_ID('tempdb..#scale_table','U') IS NOT NULL DROP TABLE #scale_table; --SELECT * FROM #scale_table
CREATE TABLE #scale_table (
empl_id int NOT NULL,
scale_id varchar(100) NOT NULL,
date_from date NOT NULL,
date_to date NOT NULL
);
INSERT INTO #scale_table (empl_id, scale_id, date_from, date_to)
VALUES (187,'B3EL9', '2014-03-01','2017-06-30')
, (187,'B4EL6', '2017-07-01','2019-10-31')
, (187,'B5EL9', '2019-11-01','2099-12-31')
, (214,'M115' , '2006-10-01','2099-12-31')
, (618,'B3L9' , '2014-01-01','2019-10-31')
, (618,'B6L9' , '2019-11-01','2099-12-31');
IF OBJECT_ID('tempdb..#value_table','U') IS NOT NULL DROP TABLE #value_table; --SELECT * FROM #value_table
CREATE TABLE #value_table (
scale_id varchar(100) NOT NULL,
amount decimal(10, 2) NOT NULL,
date_from date NOT NULL,
date_to date NOT NULL
);
INSERT INTO #value_table (scale_id, amount, date_from, date_to)
VALUES ('B3EL9',78084.00 ,'2013-01-01', '2015-06-30')
, ('B3EL9',81432.00 ,'2015-07-01', '2099-12-31')
, ('B4EL6',78348.00 ,'2013-01-01', '2015-06-30')
, ('B4EL6',81720.00 ,'2015-07-01', '2099-12-31')
, ('B5EL9',95964.00 ,'2013-01-01', '2015-06-30')
, ('B5EL9',100092.00 ,'2015-07-01', '2099-12-31')
, ('B3L9 ',52728.00 ,'2013-01-01', '2015-08-15')
, ('B3L9 ',54996.00 ,'2015-08-16', '2017-11-30')
, ('B3L9 ',56100.00 ,'2017-12-01', '2020-11-15')
, ('B3L9 ',56664.00 ,'2020-11-16', '2099-12-31')
, ('B6L9 ',64140.00 ,'2013-01-01', '2015-08-15')
, ('B6L9 ',66900.00 ,'2015-08-16', '2017-11-30')
, ('B6L9 ',68244.00 ,'2017-12-01', '2020-11-15')
, ('B6L9 ',68928.00 ,'2020-11-16', '2099-12-31')
, ('M115 ',108528.00 ,'2012-07-01', '2015-06-30')
, ('M115 ',115128.00 ,'2015-07-01', '2099-12-31');