【问题标题】:Query on two tables with overlapping dates查询两个日期重叠的表
【发布时间】:2020-12-14 22:41:36
【问题描述】:

scale_table 是一张员工 ID、他们的薪酬等级以及薪酬等级有效的开始/结束日期的表格:

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 列出了所有的薪酬等级、薪酬金额以及该金额对该薪酬等级有效的开始/结束日期:

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 年 1 月 1 日到当前日期之间员工工资的所有变化。查询结果应按员工排序,然后按 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  

【问题讨论】:

  • 请定义“员工工资的变化”。这不是工资标准吗?
  • 为了澄清,我们正在寻找员工的工资金额每次发生变化,无论是因为 scale_table 中的工资表 (scale_id) 发生变化,还是因为工资表金额发生变化value_table。
  • 那么你有什么尝试?你在哪里卡住了?请向我们展示您的尝试。

标签: sql sql-server datetime inner-join


【解决方案1】:

这是一个非常经典的重叠日期范围问题。

这是一个很好的答案,涵盖了它: 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,以便在整个查询过程中更容易重复使用此逻辑,并使其更具可读性,这样您就不会在一行中有一堆嵌套函数。

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

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

【讨论】:

    【解决方案2】:

    我认为这是日期范围重叠的连接,然后是条件逻辑:

    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 |金额 | 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

    【讨论】:

    • 谢谢,我会使用您提供的内容,看看我是否可以对其进行微调。第一行 scale_id B3EL9 和金额 78084.00 的 date_to 值应为 2015-06-30,而不是 2017-06-30。
    猜你喜欢
    • 2013-09-27
    • 2017-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多