web-dev-qa-db-ja.com

複数の日付範囲間のギャップを表示するSQLクエリ

SSRS/SQLプロジェクトに取り組んでいて、日付間のギャップを取得するためのクエリを作成しようとしていますが、これを作成する方法がまったくわかりません。基本的に、使用をスケジュールできるデバイスがいくつかあり、次のレポートが必要です。それらが使用されていないときに表示します。

デバイスID、EventStart、およびEventEndの時刻を含むテーブルがあります。クエリを実行して、各デバイスのこれらのイベント間の時刻を取得する必要がありますが、これを行う方法がよくわかりません。

例えば:

Device 1 Event A runs from `01/01/2012 08:00 - 01/01/2012 10:00`
Device 1 Event B runs from `01/01/2012 18:00 - 01/01/2012 20:00`    
Device 1 Event C runs from `02/01/2012 18:00 - 02/01/2012 20:00`    
Device 2 Event A runs from `01/01/2012 08:00 - 01/01/2012 10:00`
Device 2 Event B runs from `01/01/2012 18:00 - 01/01/2012 20:00`

私のクエリはその結果として持つべきです

`Device 1 01/01/2012 10:00 - 01/01/2012 18:00`
`Device 1 01/01/2012 20:00 - 02/01/2012 18:00`
`Device 2 01/01/2012 10:00 - 01/01/2012 18:00`

この表には、平均して約4〜5台のデバイスがあり、おそらく200〜300以上のイベントがあります。

更新:

わかりました。これを更新して、もう少し情報を提供してみます。これについてはあまりよく説明していないようです(申し訳ありません!)

私が扱っているのは、イベントの詳細が記載されたテーブルです。各イベントはフライトシミュレーターの予約です。多数のフライトシミュレーション(テーブルではデバイスと呼ばれます)があり、SSRSレポートを生成しようとしています。各シムが利用可能な曜日/時間を示すために顧客に与えることができます。

したがって、開始日/終了日のパラメーターを渡し、それらの日付の間のすべての可用性を選択します。結果は次のように表示されます。

Device   Available_From       Available_To
 1       01/01/2012 10:00    01/01/2012 18:00`
 1       01/01/2012 20:00    02/01/2012 18:00`
 2       01/01/2012 10:00    01/01/2012 18:00`

また、イベントが重複することもありますが、これは非常にまれであり、データが不良であるため、各デバイスの可用性を個別に知る必要があるため、あるデバイスのイベントが別のデバイスのイベントと重複することは問題ではありません。

16
Purplegoldfish

クエリ:

間隔を含むフィールドの名前がStartおよびFinishであり、テーブルの名前がYOUR_TABLEであるとすると、クエリ...

SELECT Finish, Start
FROM
    (
        SELECT DISTINCT Start, ROW_NUMBER() OVER (ORDER BY Start) RN
        FROM YOUR_TABLE T1
        WHERE
            NOT EXISTS (
                SELECT *
                FROM YOUR_TABLE T2
                WHERE T1.Start > T2.Start AND T1.Start < T2.Finish
            )
        ) T1
    JOIN (
        SELECT DISTINCT Finish, ROW_NUMBER() OVER (ORDER BY Finish) RN
        FROM YOUR_TABLE T1
        WHERE
            NOT EXISTS (
                SELECT *
                FROM YOUR_TABLE T2
                WHERE T1.Finish > T2.Start AND T1.Finish < T2.Finish
            )
    ) T2
    ON T1.RN - 1 = T2.RN
WHERE
    Finish < Start

...テストデータに次の結果が表示されます。

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000

このクエリの重要な特性は、重複間隔でも機能することです。


アルゴリズム:

1.重複する間隔をマージする

サブクエリT1は、他の間隔以外である間隔開始のみを受け入れます。サブクエリT2は、インターバル終了に対して同じことを行います。これが重複を取り除くものです。

DISTINCTは、他の間隔の外側に両方ある2つの同一の間隔の開始(または終了)がある場合に重要です。 WHERE Finish < Startは、空の間隔(つまり、期間0)を単に削除します。

また、次のステップで必要になる時間的順序に関連する行番号を添付します。

T1は次のようになります。

Start                       RN
2012-01-01 08:00:00.000     1
2012-01-01 18:00:00.000     2

T2は次のようになります。

Finish                      RN
2012-01-01 10:00:00.000     1
2012-01-01 20:00:00.000     2

2.結果を再構築します

これで、「アクティブ」または「非アクティブ」の間隔を再構築できます。

inactive間隔は、previous間隔の終わりと次の間隔の始まりを組み合わせることによって再構築されます。したがって、- 1ON句内。事実上、私たちは...

Finish                      RN
2012-01-01 10:00:00.000     1

...そして...

Start                       RN
2012-01-01 18:00:00.000     2

...一緒に、結果:

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000

(アクティブな間隔は、T1の行をT2の行と並べて、JOIN ... ON T1.RN = T2.RNを使用してWHEREを元に戻すことで再構築できます。 )


例:

これはもう少し現実的な例です。次のテストデータ:

Device      Event      Start                      Finish
Device 1    Event A    2012-01-01 08:00:00.000    2012-01-01 10:00:00.000
Device 2    Event B    2012-01-01 18:00:00.000    2012-01-01 20:00:00.000
Device 3    Event C    2012-01-02 11:00:00.000    2012-01-02 15:00:00.000
Device 4    Event D    2012-01-02 10:00:00.000    2012-01-02 12:00:00.000
Device 5    Event E    2012-01-02 10:00:00.000    2012-01-02 15:00:00.000
Device 6    Event F    2012-01-03 09:00:00.000    2012-01-03 10:00:00.000

次の結果が得られます。

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000
2012-01-01 20:00:00.000     2012-01-02 10:00:00.000
2012-01-02 15:00:00.000     2012-01-03 09:00:00.000
21

最初の回答-ただし、OPによって追加の制約が追加された最後の回答については、以下を参照してください。

-最新のendTimeの後に次のstartTimeを取得し、重複を回避したい場合は、次のようなものが必要です。

select
    distinct
    e1.deviceId,
    e1.EventEnd,
    e3.EventStart
from Events e1 
join Events e3 on e1.eventEnd < e3.eventStart     /* Finds the next start Time */
and e3.eventStart = (select min(eventStart) from Events e5
                     where e5.eventStart > e1.eventEnd)
and not exists (select *                          /* Eliminates an e1 rows if it is overlapped */
                from Events e5 
                where e5.eventStart < e1.eventEnd
                    and e5.eventEnd > e1.eventEnd)

3行の場合:

INSERT INTO Events VALUES (1, '01/01/2012 08:00', '01/01/2012 10:00')
INSERT INTO Events VALUES (2, '01/01/2012 18:00', '01/01/2012 20:00')
insert into Events values (2, '01/01/2012 09:00', '01/01/2012 11:00')

これにより、1つの結果が得られます。

January, 01 2012 11:00:00-0800  January, 01 2012 18:00:00-0800

ただし、おそらくDeviceIdでも一致させたいと思います。その場合、結合時にe1.DeviceId = e3.DeviceIde1.deviceId = e5.deviceIdを追加します

SQL Fiddle here: http://sqlfiddle.com/#!3/3899c/8

-

OK、最終編集。これは、deviceIdsを追加し、同時に終了するイベントを説明するために個別に追加するクエリです。

SELECT distinct
    e1.DeviceID,
    e1.EventEnd as LastEndTime,
    e3.EventStart as NextStartTime
FROM Events e1 
join Events e3 on e1.eventEnd < e3.eventStart
     and e3.deviceId = e1.deviceId
     and e3.eventStart = (select min(eventStart) from Events e5
                     where e5.eventStart > e1.eventEnd
                    and e5.deviceId = e3.deviceId)
where not exists (select * from Events e7 
                    where e7.eventStart < e1.eventEnd
                      and e7.eventEnd > e1.eventEnd
                      and e7.deviceId = e1.deviceId)
order by e1.deviceId, e1.eventEnd

E3への参加は、次のスタートを見つけます。 e5に参加すると、これが現在の終了時刻の後の最も早い開始時刻であることが保証されます。検討対象の行の終了時刻が別の行と重複している場合、e7への結合によって行が削除されます。

このデータの場合:

INSERT INTO Events VALUES (1, '01/01/2012 08:00', '01/01/2012 10:00')
INSERT INTO Events VALUES (2, '01/01/2012 18:00', '01/01/2012 20:00')
insert into Events values (2, '01/01/2012 09:00', '01/01/2012 11:00')
insert into Events values (2, '01/02/2012 11:00', '01/02/2012 15:00')
insert into Events values (1, '01/02/2012 10:00', '01/02/2012 12:00')
insert into Events values (2, '01/02/2012 10:00', '01/02/2012 15:00')
insert into Events values (2, '01/03/2012 09:00', '01/03/2012 10:00')

この結果が得られます:

1   January, 01 2012 10:00:00-0800  January, 02 2012 10:00:00-0800
2   January, 01 2012 11:00:00-0800  January, 01 2012 18:00:00-0800
2   January, 01 2012 20:00:00-0800  January, 02 2012 10:00:00-0800
2   January, 02 2012 15:00:00-0800  January, 03 2012 09:00:00-0800

SQL Fiddle here: http://sqlfiddle.com/#!3/db0fa/

4
Mike Ryan

これはそれほど単純ではないと思いますが、シナリオについての私の現在の理解に基づいたクエリを次に示します。

DECLARE @Events TABLE (
    DeviceID INT,
    EventStart DATETIME,
    EventEnd DATETIME
)

INSERT INTO @Events VALUES (1, '01/01/2012 08:00', '01/01/2012 10:00')
INSERT INTO @Events VALUES (2, '01/01/2012 18:00', '01/01/2012 20:00')

SELECT
    e1.DeviceID,
    e1.EventEnd,
    e2.EventStart
FROM 
    @Events e1 
    JOIN @Events e2 
        ON e2.EventStart = (
            SELECT MIN(EventStart)
            FROM @Events
            WHERE EventStart > e1.EventEnd
        )
3

これで問題は解決しますか?

2番目のものはより関連性があるようです

'2つの列がDateFromとDateToであるテーブルがあります。両方の列に日付と時刻の値が含まれています。欠落している日付範囲、つまり、テーブルのどのエントリでもカバーされていないすべての日付範囲をどのように見つけますか?.

2
bPratik

これが私が今行ったPostgresソリューションで、ストアドプロシージャは含まれていません。

SELECT minute, sum(case when dp.id is null then 0 else 1 end) as s 
FROM generate_series( 
   '2017-12-28'::timestamp,
   '2017-12-30'::timestamp,
   '1 minute'::interval
) minute 
left outer join device_periods as dp
on minute >= dp.start_date and minute < dp.end_date 
group by minute order by minute

Generate_series関数は、日付範囲の1分ごとに1つの行を持つテーブルを生成します。より正確に言うと、間隔を1秒に変更できます。これはpostgres固有の機能ですが、おそらく他のエンジンにも同様の機能が存在します。

このクエリは、入力されたすべての分と空白のすべての分を提供します。このクエリを外部クエリでラップできます。外部クエリでは、時間、日ごとにグループ化するか、ウィンドウ関数操作を実行して、必要に応じて正確な出力を取得できます。私の目的では、空白があるかどうかだけを数える必要がありました。

2
Martin Taleski

私はあなたの質問を本当に理解していません:

「デバイスID、EventStart、EventEndの時刻が記載されたテーブルがあります」

に続く

「これらのイベント間の時間を取得するには、クエリを実行する必要があります」

"例えば:

Device 1 Event A runs from01/01/2012 08:00-01/01/2012 10:00 "

これを取得するには、あなたがする必要があるだけです

select Device_ID,EventStart,EventEnd from Device 

各デバイスの開始日と終了日の時間差を取得する場合は、DATEDIFFを使用できます。

select Device_ID,DATEDIFF(EventEnd - EventStart) from Device
0
Shivan Dragon