私の質問は これ に似ていますが、(私は)かなりの違いがあります。ベース範囲があり、それと相互に競合する他のすべての範囲をテーブル内で検索したい。つまり、範囲を形成するアイテムですが、実際には違いはありません。
アスタリスク付きの行が開始範囲です。範囲1、2、および3は、それを拡張することになっている範囲です。結果の範囲はXである必要があります。
1 |
3 | ===1==== ====
5 | ==2== ====*==== ====
6 | ====3==== =====
--+-------------------------------------
| |<--------X-------->|
私はこれを書いた:
WITH cte
AS (
SELECT DATA1.ID, DATA1.STARTDATE, DATA1.ENDDATE
FROM DATA1
WHERE
DATA1.ID = @PARID AND
DATA1.STARTDATE > @STARTDATE AND
DATA1.ENDDATE < @ENDDATE
UNION ALL
SELECT DATA1.ID, DATA1.STARTDATE, DATA1.ENDDATE
FROM cte
INNER JOIN DATA1 ON (DATA1.ID = cte.ID)
WHERE
DATA1.ID = @PARID AND
(cte.STARTDATE < DATA1.ENDDATE AND cte.ENDDATE > DATA1.ENDDATE)
)
SELECT *
FROM cte
しかし、2番目のSELECTはSTARTDATE
とENDDATE
がcte
から何を使用する必要があるかわからないため、いじくり回しても実際には機能しないことに気付きました。また、再帰的なSQLではサブクエリを使用できません。
これは、以前に一連の再帰関数を介して.NETのクライアントで計算しましたが、O(N ^ 2)では、非常に低速でした。ここでの目的は、計算をサーバーに移動し、計算を最適化することです。
私がここでやろうとしていることはすべて実行可能ですか?
SQLフィドル 。 (単純なデータ構造を使用していない)マシンで実行した場合と同じようにクエリを実行するのに問題がありますが、少なくともサンプルデータを追加しました。私はそれを自分のマシンで得たのと同じ結果を生み出すようにしていきます。
入力範囲が2018-08-24 12:00:00
から2018-08-31 12:00:00
の場合、正しい出力はID 2, 3, 4, 6
になります。
単純な再帰的ソリューションは、タスクを2つの部分に分割します。
詳細は次のとおりです。
コード例:
DECLARE @STARTDATE DATETIME = '2018-08-24 12:00:00';
DECLARE @ENDDATE DATETIME = '2018-08-31 12:00:00';
WITH MinStart AS
(
-- Starting range
SELECT TOP (1)
D.ID,
D.STARTDATE
FROM dbo.DATA1 AS D
WHERE
D.STARTDATE >= @STARTDATE
AND D.ENDDATE <= @ENDDATE
ORDER BY
D.ID ASC
UNION ALL
SELECT
P1.ID,
P1.STARTDATE
FROM
(
-- Overlapping ranges with an earlier start date
-- Numbered starting with the highest start date
-- Tie-break dates using ID
SELECT
RN = ROW_NUMBER() OVER (
ORDER BY D.STARTDATE DESC, D.ID ASC),
D.ID,
D.STARTDATE
FROM MinStart AS R
JOIN dbo.DATA1 AS D
ON D.ENDDATE >= R.STARTDATE
AND D.STARTDATE < R.STARTDATE
) AS P1
WHERE
-- Highest start date only
P1.RN = 1
)
SELECT
MS.ID,
MS.STARTDATE
FROM MinStart AS MS
OPTION (MAXRECURSION 0);
コード例:
DECLARE @STARTDATE DATETIME = '2018-08-24 12:00:00';
DECLARE @ENDDATE DATETIME = '2018-08-31 12:00:00';
WITH MaxEnd AS
(
-- Starting range
SELECT TOP (1)
D.ID,
D.ENDDATE
FROM dbo.DATA1 AS D
WHERE
D.STARTDATE >= @STARTDATE
AND D.ENDDATE <= @ENDDATE
ORDER BY
D.ID ASC
UNION ALL
SELECT
P1.ID,
P1.ENDDATE
FROM
(
-- Overlapping ranges with a later end date
-- Numbered starting with the lowest end date
-- Tie-break dates using ID
SELECT
RN = ROW_NUMBER() OVER (
ORDER BY D.ENDDATE ASC, D.ID ASC),
D.ID,
D.ENDDATE
FROM MaxEnd AS R
JOIN dbo.DATA1 AS D
ON D.ENDDATE > R.ENDDATE
AND D.STARTDATE < R.ENDDATE
) AS P1
WHERE
-- Lowest end date only
P1.RN = 1
)
SELECT
ME.ID,
ME.ENDDATE
FROM MaxEnd AS ME
OPTION (MAXRECURSION 0);
適切なインデックス付けにより、両方のクエリは次のような実行プランを使用します。
提供されたサンプルデータの結果は次のとおりです。
╔════╦═════════════════════════╗ ║ID║STARTDATE║ ╠════╬═════════════════════════╣ ║3║2018-08-24 12:00 :00.000║ ║6║2018-08-23 12:00:00.000║ ║2║2018-08-22 12:00:00.000║ ╚═══ ═╩═════════════════════════╝ ╔════╦═══════════ ══════════════╗ ║ID║ENDDATE║ ╠════╬══════════════ ═══════════╣ ║3║2018-08-29 12:00:00.000║ ║6║2018-08-30 12:00:00.000║ ║4║2018-09-02 12:00:00.000║ ╚════╩════════════════════ ═════╝
したがって、ID 2、3、4、および6が使用されました。最終的な範囲は2018-08-22 12:00:00.000
(見つかった最も低い開始日)から2018-09-02 12:00:00.000
(見つかった最高の終了日)。
注:上記のコードには、サンプルデータの重複による日付のタイブレーク(ID
列を使用)が含まれています。これは、実際の問題を解決するために必要な場合とそうでない場合があります。
使用されたインデックスは次のとおりです。
CREATE TABLE dbo.DATA1
(
ID integer PRIMARY KEY,
STARTDATE datetime NOT NULL,
ENDDATE datetime NOT NULL,
);
CREATE UNIQUE INDEX i1 ON dbo.DATA1 (STARTDATE DESC, ID ASC) INCLUDE (ENDDATE);
CREATE UNIQUE INDEX i2 ON dbo.DATA1 (ENDDATE ASC, ID DESC) INCLUDE (STARTDATE);
デモ:db <> fiddle
これらのタイプのクエリには本当に最適なインデックス作成 難しい場合があります であることに注意してください。それでも、上記のアプローチは、この領域の多くの実用的なタスクによく機能します。
間隔クエリと述語の適切なサポートがSQL Serverに追加されるまで、より高いパフォーマンスのソリューションでは、かなりの追加作業が必要になります。見る:
T-SQLのソリューションを紹介します。
問題:
少なくとも1つのアイテムが要求された期間と重複している時間に接続されている入力テーブルのすべてのレコードを検索します。
解決策:
ステップ1:すべての関連アイテムが何らかの方法で(オーバーラップまたは満たすことによって)時間的に関連付けられている日付範囲を決定します。
with packed as (
select min(StartDate) StartDate, max(EndDate) EndDate from (
select StartDate, EndDate, sum(GroupStart) over (order by StartDate) Grp from (
select StartDate, EndDate
, case when max(EndDate)
over (order by EndDate, StartDate rows between unbounded preceding and 1 preceding)
> StartDate
then 0 else 1 end GroupStart
from dbo.DATA1
) p0
) p1
group by Grp
)
ステップ2:要求された期間に関連する日付範囲を決定します
relevant as (
select * from packed
where StartDate < @ENDDATE
and @STARTDATE < EndDate
)
ステップ3:関連する日付範囲で接続されているすべてのアイテムを出力します
select d.* from dbo.DATA1 d
inner join relevant r on d.StartDate < r.EndDate and r.StartDate < d.EndDate
where d.StartDate < @ENDDATE and @STARTDATE < d.EndDate
order by d.StartDate, d.EndDate, d.Id;
セットアップ:
CREATE TABLE dbo.DATA1
(
ID integer PRIMARY KEY,
STARTDATE datetime NOT NULL,
ENDDATE datetime NOT NULL,
);
CREATE UNIQUE INDEX i1 ON dbo.DATA1 (STARTDATE DESC, ID ASC) INCLUDE (ENDDATE);
CREATE UNIQUE INDEX i2 ON dbo.DATA1 (ENDDATE ASC, ID DESC) INCLUDE (STARTDATE);
INSERT INTO DATA1 (ID, STARTDATE, ENDDATE) VALUES
(1, '2018-08-17 12:00:00', '2018-08-20 12:00:00'),
(2, '2018-08-22 12:00:00', '2018-08-25 12:00:00'),
(3, '2018-08-24 12:00:00', '2018-08-29 12:00:00'),
(4, '2018-08-26 12:00:00', '2018-09-02 12:00:00'),
(5, '2018-09-05 12:00:00', '2018-09-25 12:00:00'),
(6, '2018-08-23 12:00:00', '2018-08-30 12:00:00'),
(7, '2018-08-17 12:00:00', '2018-08-20 12:00:00'),
(8, '2018-08-22 12:00:00', '2018-08-25 12:00:00'),
(9, '2018-08-24 12:00:00', '2018-08-29 12:00:00'),
(10, '2018-08-26 12:00:00', '2018-09-02 12:00:00'),
(11, '2018-09-05 12:00:00', '2018-09-25 12:00:00'),
(12, '2018-08-23 12:00:00', '2018-08-30 12:00:00');
完全なクエリ:
DECLARE @STARTDATE DATETIME = '2018-08-24 12:00:00';
DECLARE @ENDDATE DATETIME = '2018-08-31 12:00:00';
with packed as (
select min(StartDate) StartDate, max(EndDate) EndDate from (
select StartDate, EndDate, sum(GroupStart) over (order by StartDate) Grp from (
select StartDate, EndDate
, case when max(EndDate)
over (order by EndDate, StartDate rows between unbounded preceding and 1 preceding)
> StartDate
then 0 else 1 end GroupStart
from dbo.DATA1
) p0
) p1
group by Grp
)
, relevant as (
select * from packed
where StartDate < @ENDDATE
and @STARTDATE < EndDate
)
select d.* from dbo.DATA1 d
inner join relevant r on d.StartDate < r.EndDate and r.StartDate < d.EndDate
where d.StartDate < @ENDDATE and @STARTDATE < d.EndDate
order by d.StartDate, d.EndDate, d.Id;
SQLを待ち望んでいますFiddleテストを許可するために、 http://sqlfiddle.com/#!18/10ab4/5 をご覧ください。
DECLARE @STARTDATE DATETIME = '2018-08-24 12:00:00';
DECLARE @ENDDATE DATETIME = '2018-08-29 12:00:00';
declare @PARID int = 3;
with ghp as (
SELECT DATA1.ID, DATA1.STARTDATE, DATA1.ENDDATE
from DATA1
where ID = @PARID ),
ghp2 as (
select 'A' class, min(data1.startdate) minstart, max(data1.enddate) maxend
FROM DATA1, ghp
WHERE data1.enddate >= ghp.startdate
and data1.startdate <= ghp.enddate
union all
select 'B', data1.startdate, data1.enddate
from data1, ghp2
WHERE ( data1.enddate >= ghp2.minstart
and data1.startdate < ghp2.minstart )),
ghp3 as (
select 'C' class, min(data1.startdate) minstart, max(data1.enddate) maxend
FROM DATA1, ghp
WHERE data1.enddate >= ghp.startdate
and data1.startdate <= ghp.enddate
union all
select 'D', data1.startdate, data1.enddate
from data1, ghp2
WHERE ( data1.enddate > ghp2.maxend
and data1.startdate <= ghp2.maxend )
)
select * from ghp2
union all
select * from ghp3