日付範囲の比較


116

MySQLでは、日付範囲(範囲開始と範囲終了)のリストがある場合。例えば

10/06/1983 to 14/06/1983
15/07/1983 to 16/07/1983
18/07/1983 to 18/07/1983

そして、別の日付範囲にすでにリストにある範囲のいずれかが含まれているかどうかを確認したいのですが、どうすればよいですか?

例えば

06/06/1983 to 18/06/1983 = IN LIST
10/06/1983 to 11/06/1983 = IN LIST
14/07/1983 to 14/07/1983 = NOT IN LIST

回答:


439

これは古典的な問題であり、実際にはロジックを逆にすると簡単です。

例を挙げましょう。

ここでは1つの期間と、何らかの方法で重複する他の期間のさまざまなバリエーションをすべて掲載します。

           |-------------------|          compare to this one
               |---------|                contained within
           |----------|                   contained within, equal start
                   |-----------|          contained within, equal end
           |-------------------|          contained within, equal start+end
     |------------|                       not fully contained, overlaps start
                   |---------------|      not fully contained, overlaps end
     |-------------------------|          overlaps start, bigger
           |-----------------------|      overlaps end, bigger
     |------------------------------|     overlaps entire period

一方、重複しないものはすべて投稿させてください。

           |-------------------|          compare to this one
     |---|                                ends before
                                 |---|    starts after

したがって、単純に比較を次のように減らすと、

starts after end
ends before start

次に、重複しないものをすべて見つけ、一致しない期間をすべて見つけます。

最後のNOT IN LISTの例では、この2つのルールに一致していることがわかります。

次の期間が範囲内か範囲外かを判断する必要があります。

           |-------------|
   |-------|                       equal end with start of comparison period
                         |-----|   equal start with end of comparison period

テーブルにrange_endおよびrange_startという列がある場合、一致するすべての行を取得する簡単なSQLを次に示します。

SELECT *
FROM periods
WHERE NOT (range_start > @check_period_end
           OR range_end < @check_period_start)

そこにないことに注意してください。2つの単純なルールはすべての一致しない行を検出するため、単純なNOTはそれを逆にして、一致しない行の1つではない場合、一致する行の1つでなければなりません

ここで単純な反転ロジックを適用してNOTを取り除くと、次のようになります。

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start

45
回答に「ACII図が含まれている」フラグが必要です。これにより、複数回回答できるようになります
Jonny Buchanan

29
おそらく、私がSOで見た5つのベストアンサーの1つです。問題の素晴らしい説明、解決策の素晴らしいウォークスルー、そして...写真!
davidavr 2008

10
もし私がこれを二回以上投票できるとしたら、私はそうします。発生する一般的な問題についての素晴らしい、明確で簡潔な説明、私がめったに見たことのない解決策を十分に説明しています!
ConroyP 2008

2
正解です。エンドポイントが含まれているかどうかの判断に関して、私が追加する唯一のことは、片側に間隔を閉じ、もう片方に間隔を開いた場合、すべてがより適切に機能します。たとえば、範囲の開始はポイントに含まれ、範囲の終了はポイントに含まれません。特に、日付とさまざまな解像度の時刻の組み合わせを扱う場合、すべてが簡単になります。
Eclipseの

1
いい答えだ。これは、アレンの区間代数とも呼ばれます。私も同じような答えをしていて、1人のコメンテーターとの比較がいくつあるのかという激しい戦いに巻き込まれました。
Jonathan Leffler、2011年

8

範囲の例として、06/06/1983から18/06/1983の範囲を使用し、範囲にstartおよびendと呼ばれる列があると仮定すると、次のような句を使用できます。

where ('1983-06-06' <= end) and ('1983-06-18' >= start)

つまり、テスト範囲の開始がデータベース範囲の終了の前であり、テスト範囲の終了がデータベース範囲の開始の後または開始であることを確認します。


4

RDBMSがOVERLAP()関数をサポートしている場合、これは取るに足らないことになります。自家製のソリューションは必要ありません。(Oracleでは見事に動作しますが、文書化されていません)。


1
壮大なソリューション。正常に動作します。これは、Oracleの2つの日付範囲(s1、e1)と(s2、e2)の構文です。
ihebiheb 2015年

0

あなたが期待する結果であなたは言う

1983年6月6日から1983年6月18日=リスト

ただし、この期間には、期間のテーブル(リストではありません!)の期間が含まれていないか、含まれていません。ただし、1983年10月6日から1983年6月14日までの期間は重複しています。

Snodgrassの本(http://www.cs.arizona.edu/people/rts/tdbbook.pdf)は役に立つかもしれません:それはmysqlよりも古いものですが、時間の概念は変わっていません;-)


0

MySQLでこの問題を処理する関数を作成しました。日付を使用前に秒に変換するだけです。

DELIMITER ;;

CREATE FUNCTION overlap_interval(x INT,y INT,a INT,b INT)
RETURNS INTEGER DETERMINISTIC
BEGIN
DECLARE
    overlap_amount INTEGER;
    IF (((x <= a) AND (a < y)) OR ((x < b) AND (b <= y)) OR (a < x AND y < b)) THEN
        IF (x < a) THEN
            IF (y < b) THEN
                SET overlap_amount = y - a;
            ELSE
                SET overlap_amount = b - a;
            END IF;
        ELSE
            IF (y < b) THEN
                SET overlap_amount = y - x;
            ELSE
                SET overlap_amount = b - x;
            END IF;
        END IF;
    ELSE
        SET overlap_amount = 0;
    END IF;
    RETURN overlap_amount;
END ;;

DELIMITER ;

0

次の例を見てください。それはあなたのために役立ちます。

    SELECT  DISTINCT RelatedTo,CAST(NotificationContent as nvarchar(max)) as NotificationContent,
                ID,
                Url,
                NotificationPrefix,
                NotificationDate
                FROM NotificationMaster as nfm
                inner join NotificationSettingsSubscriptionLog as nfl on nfm.NotificationDate between nfl.LastSubscribedDate and isnull(nfl.LastUnSubscribedDate,GETDATE())
  where ID not in(SELECT NotificationID from removednotificationsmaster where Userid=@userid) and  nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo

0

MS SQLでこれを試してください


WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, [ending date]) - DATEDIFF(DAY, [start date], [ending date]), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range 
WHERE DATEADD(DAY, 1, calc_date) <= [ending date])
SELECT  P.[fieldstartdate], P.[fieldenddate]
FROM date_range R JOIN [yourBaseTable] P on Convert(date, R.calc_date) BETWEEN convert(date, P.[fieldstartdate]) and convert(date, P.[fieldenddate]) 
GROUP BY  P.[fieldstartdate],  P.[fieldenddate];


0

BETWEEN SQLステートメントを使用した別の方法

含まれる期間:

SELECT *
FROM periods
WHERE @check_period_start BETWEEN range_start AND range_end
  AND @check_period_end BETWEEN range_start AND range_end

除外される期間:

SELECT *
FROM periods
WHERE (@check_period_start NOT BETWEEN range_start AND range_end
  OR @check_period_end NOT BETWEEN range_start AND range_end)

-2
SELECT * 
FROM tabla a 
WHERE ( @Fini <= a.dFechaFin AND @Ffin >= a.dFechaIni )
  AND ( (@Fini >= a.dFechaIni AND @Ffin <= a.dFechaFin) OR (@Fini >= a.dFechaIni AND @Ffin >= a.dFechaFin) OR (a.dFechaIni>=@Fini AND a.dFechaFin <=@Ffin) OR
(a.dFechaIni>=@Fini AND a.dFechaFin >=@Ffin) )

Stack Overflowへようこそ!このコードスニペットをお寄せいただきありがとうございます。すぐに役立つ情報が得られる場合があります。適切な説明は、なぜこれが問題の優れた解決策であるを示すことにより、教育的価値を大幅に向上させ、同様の、しかし同一ではない質問を持つ将来の読者にとってより有用になるでしょう。回答を編集して説明を追加し、適用される制限と前提を示してください。
Toby Speight 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.