Tarih aralıklarını karşılaştırma


116

MySQL'de, tarih aralıklarının bir listesine sahipsem (aralık-başlangıç ​​ve aralık-bitiş). Örneğin

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

Ve başka bir tarih aralığının listede zaten mevcut olan aralıklardan HERHANGİ BİRİNİ içerip içermediğini kontrol etmek istiyorum, bunu nasıl yapacağım?

Örneğin

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

Yanıtlar:


439

Bu klasik bir sorundur ve mantığı tersine çevirirseniz aslında daha kolaydır.

Sana bir örnek vereyim.

Burada bir dönemi ve bir şekilde çakışan diğer dönemlerin tüm farklı varyasyonlarını yayınlayacağım.

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

Öte yandan, çakışmayanların hepsini göndermeme izin verin:

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

Dolayısıyla, basitçe karşılaştırmayı şu şekilde azaltın:

starts after end
ends before start

daha sonra çakışmayanların hepsini bulacaksınız ve ardından eşleşmeyen tüm dönemleri bulacaksınız.

Son olarak LİSTEDE DEĞİL örneğiniz için, bu iki kuralla eşleştiğini görebilirsiniz.

Aşağıdaki dönemlerin aralıklarınızın İÇİNDE veya DIŞINDA olup olmadığına karar vermeniz gerekecektir:

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

Tablonuzda range_end ve range_start adlı sütunlar varsa, işte tüm eşleşen satırları almak için bazı basit SQL'ler:

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

Not DEĞİL içeride. İki basit kural tüm eşleşmeyen satırları bulduğundan , basit bir DEĞİL bunu tersine çevirerek şunu söyleyecektir: eğer eşleşmeyen satırlardan biri değilse, eşleşen olanlardan biri olmalıdır .

NOT'dan kurtulmak için burada basit tersine çevirme mantığı uyguladığınızda şununla sonuçlanacaksınız:

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

45
Yanıtlar için, onları birden fazla kez yükseltmenize olanak tanıyan bir "ACII diyagramları içerir" bayrağına ihtiyacımız var
Jonny Buchanan

29
Muhtemelen SO'da gördüğüm en iyi 5 cevaptan biri. Sorunun harika açıklaması, çözümün güzel bir şekilde gözden geçirilmesi ve ... resimler!
davidavr

10
Buna birden fazla oy verebilseydim, yapardım. Ortaya çıkan ortak bir sorunun harika, net ve öz açıklaması, nadiren bu kadar iyi açıklandığını gördüğüm bir çözüm!
ConroyP

2
Mükemmel cevap! Eklediğim tek şey - uç noktaların dahil edilip edilmeyeceğine karar vermekle ilgili olarak - bir tarafta kapalı bir aralık ve diğer tarafta açık bir aralıkla giderseniz her şey daha kolay sonuçlanır. Örneğin, bir aralığın başlangıcı noktayı içerir ve aralığın sonu değildir. Özellikle tarihlerin ve çeşitli çözünürlüklerin zamanlarının birleşimiyle uğraşırken, her şey daha basit hale gelir.
Eclipse

1
İyi cevap. Bu aynı zamanda Allen'ın Aralık Cebiri olarak da tanımlanır . Benzer bir cevabım var ve bir yorumcu ile kaç farklı karşılaştırma olduğu konusunda şiddetli bir savaşa girdim.
Jonathan Leffler

8

06/06/1983 ile 18/06/1983 arasındaki örnek aralığınızı alıp aralıklarınız için başlangıç ve bitiş adlı sütunlarınız olduğunu varsayarak , bunun gibi bir cümle kullanabilirsiniz

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

yani, test aralığınızın başlangıcının veritabanı aralığının sonundan önce olduğunu ve test aralığınızın sonunun veritabanı aralığından sonra mı yoksa başında mı olduğunu kontrol edin.


4

RDBMS'niz OVERLAP () işlevini destekliyorsa, bu önemsiz hale gelir - evde üretilen çözümlere gerek yoktur. (Oracle'da görünüşe göre çalışır ancak belgelenmemiş).


1
Destansı çözüm. İyi çalışıyor. Bu, Oracle'daki 2 tarih aralığı (s1, e1) ve (s2, e2) için sözdizimidir: (s1, e1) örtüşme (s2, e2) olan dual'den 1'i seçin;
ihebiheb

0

Beklediğin sonuçlarda diyorsun

06/06/1983 - 18/06/1983 = LİSTEDE

Ancak, bu dönem, dönem tablonuzdaki (listeniz değil!) Dönemlerin hiçbirini içermez ve içermez. Ancak, 10/06/1983 - 14/06/1983 dönemiyle örtüşüyor.

Snodgrass kitabını ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) yararlı bulabilirsiniz: mysql'den önce tarihlenir, ancak zaman kavramı değişmedi ;-)


0

MySQL'de bu sorunu çözmek için bir fonksiyon yarattım. Tarihleri, kullanmadan önce saniyelere dönüştürün.

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

Aşağıdaki örneğe bakın. Size yardımcı olacaktır.

    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

Bunu MS SQL'de Deneyin


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

BETWEEN sql deyimini kullanarak başka bir yöntem

Dahil edilen dönemler:

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

Hariç tutulan dönemler:

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'a hoş geldiniz! Hemen yardımcı olabilecek bu kod parçacığı için teşekkür ederiz. Uygun bir açıklama , bunun neden soruna iyi bir çözüm olduğunu göstererek eğitimsel değerini büyük ölçüde artıracak ve benzer, ancak aynı olmayan sorulara sahip gelecekteki okuyucular için daha yararlı hale getirecektir. Lütfen açıklama eklemek için cevabınızı düzenleyin ve hangi sınırlamaların ve varsayımların geçerli olduğuna dair bir gösterge verin.
Toby Speight
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.