查询具有重叠日期的两个表
Query on two tables with overlapping dates
scale_table 是 table 员工 ID、他们的薪级以及薪级有效的 start/end 日期:
empl_id scale_id date_from date_to
------- --------- ----------- -----------
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
value_table 列出了所有薪资等级、薪资金额以及金额对该薪资等级有效的 start/end 日期:
scale_id amount date_from date_to
--------- --------- ----------- -----------
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
我需要一个查询来查找 2015-01-01 和当前日期之间员工薪水的所有变化。查询结果应按员工排序然后date_from.
员工 187 的预期结果是:
empl_id scale_id amount date_from date_to
------- -------- ------ ---------- -----------
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
我认为这是日期范围重叠的连接,然后是条件逻辑:
select s.empl_id,
s.scale_id,
v.amount,
case when s.date_from <= v.date_from then v.date_from else s.date_from end as date_from,
case when v.date_to <= s.date_from then v.date_to else s.date_to end as date_to
from scale_table s
inner join value_table v
on s.scale_id = v.scale_id
and s.date_from <= v.date_to
and s.date_to >= v.date_from
Demo on DB Fiddle - 过滤员工 187
:
empl_id | scale_id | amount | date_from | date_to
:------ | :------- | :-------- | :--------- | :---------
187 | B3EL9 | 78084.00 | 2014-03-01 | 2017-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
这是一个非常经典的重叠日期范围问题。
这是一个很好的答案,涵盖了它:
Determine Whether Two Date Ranges Overlap
对于您的代码,它应该是这样的(请参阅 post 底部的用于生成临时表的代码):
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 个不同的日期范围,并且您想找到它们重叠的地方。
这些日期范围是:
- 薪级表金额的 start/end 日期
- start/end 为员工分配薪级表的日期
- 报告的 start/end 日期
第一步只抓取有效记录:
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
现在我们有了一个可以玩的数据集。我们有每个员工的记录和他们的工资等级历史,以及等级本身的变化。
现在我们只需要弄清楚如何获得正确的 Start/End 日期...
下一个:
这就是它的用武之地:
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
到 Current。我们希望从 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');
scale_table 是 table 员工 ID、他们的薪级以及薪级有效的 start/end 日期:
empl_id scale_id date_from date_to
------- --------- ----------- -----------
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
value_table 列出了所有薪资等级、薪资金额以及金额对该薪资等级有效的 start/end 日期:
scale_id amount date_from date_to
--------- --------- ----------- -----------
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
我需要一个查询来查找 2015-01-01 和当前日期之间员工薪水的所有变化。查询结果应按员工排序然后date_from.
员工 187 的预期结果是:
empl_id scale_id amount date_from date_to
------- -------- ------ ---------- -----------
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
我认为这是日期范围重叠的连接,然后是条件逻辑:
select s.empl_id,
s.scale_id,
v.amount,
case when s.date_from <= v.date_from then v.date_from else s.date_from end as date_from,
case when v.date_to <= s.date_from then v.date_to else s.date_to end as date_to
from scale_table s
inner join value_table v
on s.scale_id = v.scale_id
and s.date_from <= v.date_to
and s.date_to >= v.date_from
Demo on DB Fiddle - 过滤员工 187
:
empl_id | scale_id | amount | date_from | date_to :------ | :------- | :-------- | :--------- | :--------- 187 | B3EL9 | 78084.00 | 2014-03-01 | 2017-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
这是一个非常经典的重叠日期范围问题。
这是一个很好的答案,涵盖了它: Determine Whether Two Date Ranges Overlap
对于您的代码,它应该是这样的(请参阅 post 底部的用于生成临时表的代码):
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 个不同的日期范围,并且您想找到它们重叠的地方。
这些日期范围是:
- 薪级表金额的 start/end 日期
- start/end 为员工分配薪级表的日期
- 报告的 start/end 日期
第一步只抓取有效记录:
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
现在我们有了一个可以玩的数据集。我们有每个员工的记录和他们的工资等级历史,以及等级本身的变化。
现在我们只需要弄清楚如何获得正确的 Start/End 日期...
下一个:
这就是它的用武之地:
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
然后我们应该显示该行的开始日期 2014-03-01
和结束日期 2015-06-30
。
最后一步:
确定第一个日期。钳位意味着将一个值绑定在另外两个值的范围内。由于此报告是 运行 基于 2015-01-01
到 Current。我们希望从 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');