Membandingkan rentang tanggal


116

Di MySQL, Jika saya memiliki daftar rentang tanggal (range-start dan range-end). misalnya

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

Dan saya ingin memeriksa apakah rentang tanggal lain berisi SALAH SATU rentang yang sudah ada dalam daftar, bagaimana cara melakukannya?

misalnya

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

Jawaban:


439

Ini adalah masalah klasik, dan sebenarnya lebih mudah jika Anda membalik logika.

Izinkan saya memberi Anda sebuah contoh.

Saya akan memposting satu periode waktu di sini, dan semua variasi berbeda dari periode lain yang tumpang tindih dalam beberapa cara.

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

di sisi lain, izinkan saya memposting semua yang tidak tumpang tindih:

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

Jadi, jika Anda sederhana, kurangi perbandingannya menjadi:

starts after end
ends before start

lalu Anda akan menemukan semua yang tidak tumpang tindih, lalu Anda akan menemukan semua periode yang tidak cocok.

Untuk contoh NOT IN LIST terakhir Anda, Anda dapat melihat bahwa itu cocok dengan dua aturan tersebut.

Anda harus memutuskan apakah periode-periode berikut ini DI DALAM atau DI LUAR rentang Anda:

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

Jika tabel Anda memiliki kolom yang disebut range_end dan range_start, berikut ini beberapa SQL sederhana untuk mengambil semua baris yang cocok:

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

Perhatikan TIDAK di sana. Karena dua aturan sederhana menemukan semua baris yang tidak cocok , NOT sederhana akan membalikkannya untuk mengatakan: jika bukan salah satu baris yang tidak cocok, itu harus salah satu baris yang cocok .

Menerapkan logika pembalikan sederhana di sini untuk menghilangkan NOT dan Anda akan berakhir dengan:

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

45
Kami memerlukan bendera "berisi diagram ACII" untuk jawaban yang memungkinkan Anda memberi suara positif lebih dari sekali
Jonny Buchanan

29
Mungkin salah satu dari 5 jawaban terbaik yang pernah saya lihat di SO. Penjelasan bagus tentang masalah, penelusuran solusi yang bagus, dan ... gambar!
davidavr

10
Jika saya bisa memilih ini lebih dari sekali, saya akan. Penjelasan yang bagus, jelas, dan ringkas tentang masalah umum yang muncul, solusi yang jarang saya lihat dijelaskan dengan baik!
ConroyP

2
Jawaban yang bagus! Satu-satunya hal yang saya tambahkan - mengacu pada memutuskan apakah titik akhir disertakan atau tidak - semuanya bekerja lebih bersih jika Anda menggunakan interval tertutup di satu sisi dan interval terbuka di sisi lain. Misalnya awal rentang termasuk dalam titik, dan akhir rentang tidak. Terutama ketika Anda berurusan dengan kombinasi tanggal, dan waktu dari berbagai resolusi, semuanya menjadi lebih sederhana.
Gerhana

1
Jawaban yang bagus. Ini juga dijelaskan sebagai Aljabar Interval Allen . Saya memiliki jawaban yang serupa dan terlibat pertarungan sengit tentang berapa banyak perbandingan berbeda yang ada dengan satu komentator.
Jonathan Leffler

8

Mengambil contoh rentang Anda dari 06/06/1983 hingga 18/06/1983 dan mengasumsikan Anda memiliki kolom yang disebut awal dan akhir untuk rentang Anda, Anda dapat menggunakan klausa seperti ini

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

yaitu, periksa awal rentang pengujian Anda sebelum akhir rentang database, dan bahwa akhir rentang pengujian Anda adalah setelah atau di awal rentang database.


4

Jika RDBMS Anda mendukung fungsi OVERLAP () maka ini menjadi sepele - tidak perlu solusi buatan sendiri. (Di Oracle tampaknya berfungsi tetapi tidak terdokumentasi).


1
Solusi epik. Bekerja dengan baik. Ini adalah sintaks untuk 2 rentang tanggal (s1, e1) dan (s2, e2) di Oracle: pilih 1 dari dual di mana (s1, e1) tumpang tindih (s2, e2);
ihebiheb

0

Dalam hasil yang Anda harapkan, Anda katakan

06/06/1983 sampai 18/06/1983 = DI DAFTAR

Namun, periode ini tidak mengandung atau terkandung oleh salah satu periode di tabel Anda (bukan daftar!) Dari periode. Namun demikian, periode tersebut tumpang tindih antara periode 10/06/1983 hingga 14/06/1983.

Anda mungkin menemukan buku Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) berguna: buku ini mendahului mysql tetapi konsep waktu tidak berubah ;-)


0

Saya membuat fungsi untuk mengatasi masalah ini di MySQL. Cukup ubah tanggal menjadi detik sebelum digunakan.

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

Perhatikan contoh berikut. Ini akan membantu Anda.

    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

Coba Ini di 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
CREATE FUNCTION overlap_date(s DATE, e DATE, a DATE, b DATE)
RETURNS BOOLEAN DETERMINISTIC
RETURN s BETWEEN a AND b or e BETWEEN a and b or  a BETWEEN s and e;

0

Metode lain dengan menggunakan pernyataan ANTARA sql

Periode termasuk:

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

Periode dikecualikan:

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) )

Selamat datang di Stack Overflow! Terima kasih atas cuplikan kode ini, yang mungkin memberikan beberapa bantuan langsung. Penjelasan yang tepat akan sangat meningkatkan nilai pendidikannya dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah tersebut, dan akan membuatnya lebih berguna bagi pembaca di masa mendatang dengan pertanyaan yang serupa, tetapi tidak identik. Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku.
Toby Speight
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.