【问题标题】:Filtering date ranges by special condition按特殊条件过滤日期范围
【发布时间】:2017-03-17 22:43:35
【问题描述】:

我有一个包含以下数据的表(按 StartDate 排序):

    | Id | StartDate           | EndDate             |
    |----|---------------------|---------------------|
    | 1  | 2017-03-18 00:00:00 | 2017-03-18 02:00:00 |
    | 2  | 2017-03-18 01:30:00 | 2017-03-18 04:00:00 |
    | 3  | 2017-03-18 01:45:00 | 2017-03-18 03:00:00 |
    | 4  | 2017-03-18 02:10:00 | 2017-03-18 02:30:00 |
    | 5  | 2017-03-18 02:35:00 | 2017-03-18 02:50:00 |
    | 6  | 2017-03-18 03:20:00 | 2017-03-18 03:50:00 |
    | 7  | 2017-03-18 05:00:00 | 2017-03-18 05:30:00 |
    | 8  | 2017-03-18 05:10:00 | 2017-03-18 07:00:00 |
    | 9  | 2017-03-18 05:50:00 | 2017-03-18 08:00:00 |

过滤逻辑: 在第一段日期之后,我们发现另一个与它不相交的日期。 然后针对找到的周期重复该逻辑。


过滤后应保留:

    | Id | StartDate           | EndDate             |
    |----|---------------------|---------------------|
    | 1  | 2017-03-18 00:00:00 | 2017-03-18 02:00:00 |
    | 4  | 2017-03-18 02:10:00 | 2017-03-18 02:30:00 |
    | 5  | 2017-03-18 02:35:00 | 2017-03-18 02:50:00 |
    | 6  | 2017-03-18 03:20:00 | 2017-03-18 03:50:00 |
    | 7  | 2017-03-18 05:00:00 | 2017-03-18 05:30:00 |
    | 9  | 2017-03-18 05:50:00 | 2017-03-18 08:00:00 |


是否可以在不使用 CURSOR 的情况下实现这一点?

为了方便使用记录:

CREATE TABLE #Dates (Id INT, StartDate DATETIME, EndDate DATETIME);
INSERT INTO #Dates SELECT 1, '2017-03-18 00:00:00', '2017-03-18 02:00:00';
INSERT INTO #Dates SELECT 2, '2017-03-18 01:30:00', '2017-03-18 04:00:00';
INSERT INTO #Dates SELECT 3, '2017-03-18 01:45:00', '2017-03-18 03:00:00';
INSERT INTO #Dates SELECT 4, '2017-03-18 02:10:00', '2017-03-18 02:30:00';
INSERT INTO #Dates SELECT 5, '2017-03-18 02:35:00', '2017-03-18 02:50:00';
INSERT INTO #Dates SELECT 6, '2017-03-18 03:20:00', '2017-03-18 03:50:00';
INSERT INTO #Dates SELECT 7, '2017-03-18 05:00:00', '2017-03-18 05:30:00';
INSERT INTO #Dates SELECT 8, '2017-03-18 05:10:00', '2017-03-18 07:00:00';
INSERT INTO #Dates SELECT 9, '2017-03-18 05:50:00', '2017-03-18 08:00:00';
DROP TABLE #Dates;

【问题讨论】:

  • 你使用的sql-server是什么版本的?
  • 我使用 MSSQL Server 2014
  • 你可以使用LAG函数来实现这个,查看我的答案了解更多详情
  • @IvanAntonov 感谢您提供示例数据 - 让每个人都更轻松。

标签: sql sql-server tsql intervals date-range


【解决方案1】:

是的,您可以在没有光标的情况下执行此操作。这是一个使用exists 的版本,假设在开始日期没有完全相同的重复:

select min(id), min(startdate), max(startdate)
from (select t.*, sum(flag) over (order by startdate) as grp
      from (select t.*,
                   (case when not exists (select 1
                                          from t t2
                                          where t2.startdate < t.startdate and
                                                t2.enddate >= t.startdate
                                         )
                         then 1 else 0
                    end) as flag
            from t
           ) t
      ) t
group by grp;

最里面的子查询计算一个标志以确定行是否重叠。中间层累加这个标志来标识每个组,外层做聚合。

【讨论】:

  • 也许我描述问题的本质是错误的。目前,一个不存在的解决方案应该输出第二个表中的记录。但是,这个解决方案不能正常工作,我看不到修复它的可能性:(。
  • @IvanAntonov 。 . .我认为问题仅仅是not exists 正在寻找时间范围内的任何重叠。但是,仅当重叠在 starttime 时才需要设置标志。
【解决方案2】:

从SQL Server 2012开始,可以使用LEADLAG函数来实现:

CREATE TABLE #Dates (Id INT, StartDate DATETIME, EndDate DATETIME);
INSERT INTO #Dates SELECT 1, '2017-03-18 00:00:00', '2017-03-18 02:00:00';
INSERT INTO #Dates SELECT 2, '2017-03-18 01:30:00', '2017-03-18 04:00:00';
INSERT INTO #Dates SELECT 3, '2017-03-18 01:45:00', '2017-03-18 03:00:00';
INSERT INTO #Dates SELECT 4, '2017-03-18 02:10:00', '2017-03-18 02:30:00';
INSERT INTO #Dates SELECT 5, '2017-03-18 02:35:00', '2017-03-18 02:50:00';
INSERT INTO #Dates SELECT 6, '2017-03-18 03:20:00', '2017-03-18 03:50:00';
INSERT INTO #Dates SELECT 7, '2017-03-18 05:00:00', '2017-03-18 05:30:00';
INSERT INTO #Dates SELECT 8, '2017-03-18 05:10:00', '2017-03-18 07:00:00';
INSERT INTO #Dates SELECT 9, '2017-03-18 05:50:00', '2017-03-18 08:00:00';


SELECT T1.Id, T1.StartDate, T1.EndDate FROM (
    SELECT *,
        LAG(EndDate) OVER(ORDER BY ID) as lagdate  ,
        LEAD(StartDate) OVER(ORDER BY ID) as leaddate  
    FROM #Dates) AS T1
WHERE T1.StartDate > T1.lagdate OR T1.lagdate IS NULL 
   OR T1.EndDate < T1.leaddate  OR T1.leaddate IS NULL

结果如下:

注意:T1.lagdate IS NULL 用于获取第一行,T1.leaddate IS NULL 用于获取最后一行

【讨论】:

  • 抱歉,该解决方案无法正常工作。这项任务比看起来要困难得多。您的解决方案显示 ID 为 (1, 5, 6, 7) 的记录。
  • @IvanAntonov 等待您的反馈 :)
  • 非常感谢您的帮助!但是,这个方案只适用于当前的输入数据,如果你稍微改变一下输入数据,这个方案就会产生不正确的结果:
  • 使用此数据:INSERT INTO #Dates SELECT 1, '2017-03-18 04:00:00', '2017-03-18 10:30:00';插入#Dates SELECT 2, '2017-03-18 05:00:00', '2017-03-18 15:00:00';插入#Dates SELECT 3, '2017-03-18 07:00:00', '2017-03-18 20:00:00';插入#Dates SELECT 4, '2017-03-18 11:00:00', '2017-03-18 23:00:00';插入#Dates SELECT 5, '2017-03-18 12:30:00', '2017-03-18 19:00:00';您的解决方案选择 ID 为 IN (1, 5) 的记录,但按逻辑应该获取 ID 为 1 和 4 的记录
  • @IvanAntonov 很高兴您的问题得到解决。祝你好运
【解决方案3】:

找到的解决方案:

WITH PIP AS (
    SELECT [D1].*, [T].*
    FROM #Dates [D1]
    OUTER APPLY (
        SELECT TOP 1 Id AS NextId FROM #Dates [D2]
        WHERE 
            NOT ([D1].StartDate <= [D2].EndDate AND [D1].EndDate >= [D2].StartDate) AND [D2].Id > [D1].Id
        ORDER BY [D2].StartDate
    ) AS [T]
), POP AS (
    SELECT [T].Id, [T].StartDate, [T].EndDate, [T].NextId
        FROM PIP [T]
    WHERE [T].Id = 1
    UNION ALL
    SELECT [X].Id, [X].StartDate, [X].EndDate, [X].NextId
        FROM PIP [X]
    JOIN POP [H] ON [H].NextId = [X].Id
)
SELECT * FROM POP;

【讨论】:

  • 干得好。祝你好运
【解决方案4】:
CREATE TABLE #Dates (Id INT, StartDate DATETIME, EndDate DATETIME);
INSERT INTO #Dates SELECT 1, '2017-03-18 00:00:00', '2017-03-18 02:00:00';
INSERT INTO #Dates SELECT 2, '2017-03-18 01:30:00', '2017-03-18 04:00:00';
INSERT INTO #Dates SELECT 3, '2017-03-18 01:45:00', '2017-03-18 03:00:00';
INSERT INTO #Dates SELECT 4, '2017-03-18 02:10:00', '2017-03-18 02:30:00';
INSERT INTO #Dates SELECT 5, '2017-03-18 02:35:00', '2017-03-18 02:50:00';
INSERT INTO #Dates SELECT 6, '2017-03-18 03:20:00', '2017-03-18 03:50:00';
INSERT INTO #Dates SELECT 7, '2017-03-18 05:00:00', '2017-03-18 05:30:00';
INSERT INTO #Dates SELECT 8, '2017-03-18 05:10:00', '2017-03-18 07:00:00';
INSERT INTO #Dates SELECT 9, '2017-03-18 05:50:00', '2017-03-18 08:00:00';

DECLARE  @Results TABLE (Id INT, StartDate DATETIME, EndDate DATETIME);

INSERT INTO @Results
SELECT TOP 1 * 
FROM #Dates
ORDER BY StartDate

/* 这是您的过滤逻辑的代码:在第一个日期之后,我们找到另一个与它不相交的日期。然后针对找到的周期重复该逻辑。 */

WHILE(@@ROWCOUNT > 0)   -- we did something the last insert
BEGIN

    INSERT INTO @Results
    SELECT TOP 1 * 
    FROM #Dates d
    WHERE d.StartDate = 
        (SELECT MIN(StartDate)
        FROM #Dates
END 

SELECT *
FROM @Results
ORDER BY 1

DROP TABLE #Dates;

Id  StartDate               EndDate
1   2017-03-18 00:00:00.000 2017-03-18 02:00:00.000
4   2017-03-18 02:10:00.000 2017-03-18 02:30:00.000
5   2017-03-18 02:35:00.000 2017-03-18 02:50:00.000
6   2017-03-18 03:20:00.000 2017-03-18 03:50:00.000
7   2017-03-18 05:00:00.000 2017-03-18 05:30:00.000
9   2017-03-18 05:50:00.000 2017-03-18 08:00:00.000

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-25
    • 2018-06-19
    • 1970-01-01
    相关资源
    最近更新 更多