查询具有重叠日期的两个表

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 以便在整个查询中更容易重复使用此逻辑,并使其更具可读性,这样您就不会在一行中有一堆嵌套函数。

如果 187B3EL92014-03-012017-06-30

薪级从 2013-01-012015-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');