【问题标题】:t-SQL to update table to remove overlapping time framest-SQL 更新表以删除重叠的时间范围
【发布时间】:2012-07-19 22:39:39
【问题描述】:

我想知道是否有人可以帮助我处理这条 SQL 语句?

假设,我有一个这样的 SQL Server 2008 表:

id -- INT PRIMARY KEY
dtIn -- DATETIME2
dtOut -- DATETIME2
type -- INT

id  dtIn    dtOut    type
1   05:00   10:00    1
2   08:00   16:00    2
3   02:00   08:00    1
4   07:30   11:00    1
5   07:00   12:00    2

我需要删除上表中的任何时间重叠。这可以用这张图来说明:

所以我想出了这个 SQL:

UPDATE [table] AS t 
SET dtOut = (SELECT MIN(dtIn) FROM [table] WHERE type = t.type AND t.dtIn >= dtIn AND t.dtIn < dtOut) 
WHERE type = t.type AND t.dtIn >= dtIn AND t.dtIn < dtOut

但它不起作用。知道我在这里做错了什么吗?

****编辑****

好的,我花了一段时间才明白这一点。似乎是我需要的有效 SQL:

--BEGIN TRANSACTION;

--delete identical dtIn
DELETE dT1
FROM tbl dT1
WHERE EXISTS
(
    SELECT *
    FROM tbl dT2
    WHERE dT1.Type = dT2.Type
    AND dT1.dtIn = dT2.dtIn
    AND (
            dT1.dtOut < dT2.dtOut
            OR (dT1.dtOut = dT2.dtOut AND dT1.id < dT2.id)
        )
);

--adjust dtOuts to the max dates for overlapping section
UPDATE tbl
SET dtOut = COALESCE((
    SELECT MAX(dtOut)
    FROM tbl as t1
    WHERE t1.type = tbl.type
    AND t1.dtIn < tbl.dtOut 
AND t1.dtOut > tbl.dtIn
    ), dtOut);

-- Do the actual updates of dtOut
UPDATE tbl
SET dtOut = COALESCE((
    SELECT MIN(dtIn)
    FROM tbl as t2
    WHERE t2.type = tbl.type AND
          t2.id <> tbl.id AND
          t2.dtIn >= tbl.dtIn AND t2.dtIn < tbl.dtOut
    ), dtOut);

--COMMIT TRANSACTION;

【问题讨论】:

    标签: sql sql-server sql-update


    【解决方案1】:

    我认为CROSS APPLY 可能会成功:

    DECLARE @T TABLE (ID INT, DTIn DATETIME2, dtOut DATETIME2, Type INT)
    INSERT @T VALUES
    (1, '05:00', '10:00', 1),
    (2, '08:00', '16:00', 2),
    (3, '02:00', '08:00', 1),
    (4, '07:30', '11:00', 1),
    (5, '07:00', '12:00', 2)
    
    UPDATE  @T
    SET     DtOut = T3.DtOut
    FROM    @T T1
            CROSS APPLY
            (   SELECT  MIN(DtIn) [DtOut]
                FROM    @T T2
                WHERE   T2.Type = T1.Type
                AND     T2.DtIn > T1.dtIn 
                AND     T2.DtIn < T1.dtOut
            ) T3
    WHERE   T3.dtOut IS NOT NULL
    
    SELECT  *
    FROM    @T
    

    【讨论】:

    • 有趣。不过我不清楚——最后一个 SELECT * FROM @T 是为了什么?
    • 我只是将它留在测试中以显示数据。它与更新语句无关。
    【解决方案2】:

    就在我的脑海中,我相信 Joe Celko 的一本书以这个问题为例。您可能会在 Google 上找到摘录。

    这可能更接近。我认为您并没有真正以正确的方式执行子查询。

    UPDATE table
    SET dtOut = (
        SELECT MIN(t2.dtIn)
        FROM [table] as t2
        WHERE t2.id <> table.id AND t2.type = table.type
            AND table.dtIn < t2.dtIn AND t2.dtIn < table.dtOut
            AND table.dtOut <= t2.dtOut
        )
    WHERE EXISTS (
        SELECT 1
        FROM [table] as t3
        WHERE
                t3.type = table.type
            AND t3.id <> table.id
            AND table.dtIn < t3.dtIn AND t3.dtIn < table.dtOut
            AND table.dtOut <= t3.dtOut
        )
    

    编辑 我忽略了页面顶部的 id 列,所以显然这比确保端点不匹配更好。如果您可以假设没有两行相同类型的行具有 dtIn,则该解决方案可能会更容易。

    顺便说一句,当子查询将完成完全相同的工作时,没有理由使用 CROSS APPLY。

    编辑 2 我做了一些快速测试,我认为我的查询处理了您图表中的场景。在一种情况下,它可能无法满足您的需求。

    对于给定的类型,按开始时间的顺序考虑最后两个段 S1 和 S2。 S2 在 S1 之后开始,但也可以想象它在 S1 之前结束。 S2 完全包含在 S1 的区间中,因此要么可以忽略,要么需要将两个段的信息分成第三段,这就是问题变得更加棘手的地方。

    所以这个解决方案只是假设它们可以被忽略。


    EDIT 3 基于关于合并更新的评论

    OP 发布的 SQLFiddle

    -- eliminate redundant rows
    DELETE dT1 /* FROM tbl dT1 -- unnecessary */
    WHERE EXISTS
    (
        SELECT *
        FROM tbl dT2
        WHERE dT1.Type = dT2.Type AND dT1.dtIn = dT2.dtIn
        AND (
            dT1.dtOut < dT2.dtOut
            OR (dT1.dtOut = dT2.dtOut AND dT1.id < dT2.id)
        )
    );
    
    --adjust dtOuts to the max dates
    UPDATE tbl
    SET dtOut = COALESCE((
        SELECT MAX(dtOut)
        FROM tbl as t1
        WHERE t1.type = tbl.type
        ), dtOut);
    
    -- Do the actual updates of dtOut
    UPDATE tbl
    SET dtOut = COALESCE((
        SELECT MIN(dtIn)
        FROM tbl as t2
        WHERE t2.type = tbl.type AND
              t2.id <> tbl.id AND
              t2.dtIn >= tbl.dtIn AND t2.dtIn < tbl.dtOut
        ), dtOut);
    

    以下两个更新中的任何一个都应替换上面的两个更新。

    UPDATE tbl
    SET dtOut = (
        SELECT
            COALESCE(
                MIN(dtIn),
                /* as long as there's no GROUP BY, there's always one row */
                (SELECT MAX(dtOut) FROM tbl as tmax WHERE tmax.type = tbl.type)
            )
        FROM tbl as tmin
        WHERE tmin.type = tbl.type
    
            AND tmin.dtIn > tbl.dtIn
            /*  
            regarding the original condition in the second update:
                t2.dtIn >= tbl.dtIn AND t2.dtIn < tbl.dtOut
    
            dtIns can't be equal because you already deleted those
            and if dtIn was guaranteed to be less than dtOut it's
            also automatically always less than max(dtOut)
            */
    );
    
    UPDATE tbl
    SET dtOut = COALESCE(
      (
        SELECT MIN(dtIn) FROM tbl as tmin
        WHERE tmin.type = tbl.type AND tmin.dtIn > tbl.dtIn
      ),
      (        
        SELECT MAX(dtOut) FROM tbl as tmax
        WHERE tmax.type = tbl.type
      )
    );
    

    【讨论】:

    • 谢谢。我需要尝试一下。只是出于好奇,您提到的代码参考是哪本书,在哪里?
    • 我在想 Joe Celko 为 Smarties 编写的 SQL:高级 SQL 编程的第 29 章。我可以看到目录而不是章节。
    • 同样对于同一行的检查,不是更容易检查id列吗?
    • 我相信它会的。我只是忽略了这一点。您最大的问题之一肯定是您没有从子查询中的匹配中排除当前行。但我认为您还试图在外部 WHERE 子句中引用子查询的表。也许我的方法能让你走上正轨,但我知道这不太对。
    • 好吧,如果 S2 包含在 S1 中,它会被 S1 消耗。我的多部分 SQL 似乎可以处理它:sqlfiddle.com/#!3/89b6c/2 不幸的是,我没有足够的 SQL 技能将它整合到一个语句中。
    猜你喜欢
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多