6

I was wondering if someone could help me with this SQL statement?

Say, I have an SQL Server 2008 table like this:

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

I need to remove any time overlaps in the table above. This can be illustrated with this diagram: enter image description here

So I came up with this 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

But it doesn't work. Any idea what am I doing wrong here?

****EDIT****

OK, it took me awhile to get to this. Seems to be a working SQL for what I need it for:

--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;
4

2 に答える 2

2

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
于 2012-07-19T23:13:19.047 に答える
1

頭のてっぺんから、ジョー・セルコの本の1つにこれが問題の例としてあったと思います。あなたはグーグルで利用可能な抜粋を見つけるかもしれません。

これはもっと近いかもしれません。あなたは実際にサブクエリを正しい方法で行っていなかったと思います。

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列を見落としていたので、エンドポイントが一致していないことを確認するよりも明らかに良いチェックです。同じタイプの2つの行にdtInがないと想定できる場合、解決策はおそらく簡単です。

ところで、サブクエリがまったく同じ仕事をするときにCROSSAPPLYを使用する理由はありません。

編集2 私はいくつかの簡単なテストを行いました、そして私のクエリはあなたの図のシナリオを処理すると思います。それがあなたが望むことをしないかもしれない1つのケースがあります。

特定のタイプについて、開始時間の順に最後の2つのセグメントS1とS2について考えます。S2はS1の後に開始しますが、S1が終了する前に終了することも想像してください。S2はS1の間隔に完全に含まれているため、無視できるか、2つのセグメントの情報を3番目のセグメントに分割する必要があり、そこで問題がより複雑になります。

したがって、このソリューションは、それらが無視できることを前提としています。


更新の組み合わせに関するコメントに基づいて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);

以下の2つの更新のいずれかが、上記の2つの更新を置き換える必要があります。

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
  )
);
于 2012-07-19T22:46:06.360 に答える