SQL „między” nie obejmuje


136

Mam takie zapytanie:

SELECT * FROM Cases WHERE created_at BETWEEN '2013-05-01' AND '2013-05-01'

Ale to nie daje żadnych wyników, mimo że istnieją dane z 1.

created_atwygląda na to 2013-05-01 22:25:19, podejrzewam, że ma to związek z czasem? Jak można to rozwiązać?

Działa dobrze, jeśli robię większe zakresy dat, ale powinno (włącznie) działać również z jedną datą.


23
Ile liczb mieści się w przedziale od 1 do 1? Czy 1,5 powinno być między 1 a 1? Po prostu nie używaj opcji BETWEEN dla zakresów dat / godzin. Zawsze. I uważaj, jak oceniasz „działa dobrze” - czy dokładnie sprawdziłeś wyniki z ostatniego dnia w zakresie? Uwzględniłbyś wszystkie wiersze tylko wtedy, gdyby nie były z nimi związane.
Aaron Bertrand

Zaktualizowany adres URL dla Aarona: sqlblog.org/2011/10/19/…
JayRizzo

Odpowiedzi:


294

To jest w cenie. Porównujesz czas dat z datami. Druga data jest interpretowana jako północ, gdy zaczyna się dzień .

Jednym ze sposobów rozwiązania tego problemu jest:

SELECT *
FROM Cases
WHERE cast(created_at as date) BETWEEN '2013-05-01' AND '2013-05-01'

Innym sposobem rozwiązania tego problemu są jawne porównania binarne

SELECT *
FROM Cases
WHERE created_at >= '2013-05-01' AND created_at < '2013-05-02'

Aaron Bertrand ma na blogu długi wpis dotyczący dat ( tutaj ), w którym omawia ten i inne kwestie związane z datami.


15
Ze względu na kompletność tej dobrej odpowiedzi sugerowałbym użycie, dateaddaby uzyskać następny dzień.
Tim Lehner

7
@TimLehner Ze względu na dobrą odpowiedź użyłbym formatu ISO-8601 „20130501”. Dla osób spoza Stanów Zjednoczonych z datformatem dmy otrzymujesz:set dateformat dmy;select month(cast('2013-05-01' as datetime)); =1
RichardTheKiwi

2
@RichardTheKiwi - Uważam, że jawny bit używa interwału zamkniętego na jednym końcu ( >=) i otwartego na drugim ( <) w połączeniu ze sprawdzaniem jednego dnia po określonej dacie zakończenia.
HABO

3
@scottb Osobiście denerwowałoby mnie konwertowanie na czasy danych za każdym razem, gdy chciałem wyświetlić, wyeksportować, zaimportować lub napisać klasę z datą i godziną. Myślę, że SQL Server ma wiele wbudowanych funkcji do manipulowania i porównywania dat dla większości celów.
Tim Lehner

10
Nigdy nie należy rzutować kolumny w whereklauzuli, ponieważ utraci ona wszelkie posiadane indeksy. To naprawdę zły wzór.
Buzinas

49

Założono, że drugie odniesienie do daty w BETWEENskładni jest magicznie uważane za „koniec dnia”, ale to nieprawda .

czyli oczekiwano:

SELECT * FROM Cases 
GDZIE utworzono_at MIĘDZY początkiem „2013-05-01” a końcem „2013-05-01”

ale to, co naprawdę się wydarzyło, to:

SELECT * FROM Cases 
GDZIE utworzono_at BETWEEN „2013-05-01 00: 00: 00 + 00000 ” A „2013-05-01 00: 00: 00 + 00000

Co staje się odpowiednikiem:

SELECT * FROM Cases WHERE created_at = '2013-05-01 00: 00: 00 + 00000 '

Problemem jest to jedna z percepcji / oczekiwań o BETWEENktórych nie obejmują zarówno dolną i górną wartość wartości w przedziale, ale nie magicznie zrobić data „początek” i „koniec”.

BETWEEN należy unikać filtrowania według zakresów dat.

Zawsze używaj >= AND <zamiast tego

SELECT * FROM Cases 
WHERE (created_at > = '20130501' AND created_at < '20130502')

nawiasy są tutaj opcjonalne, ale mogą być ważne w bardziej złożonych zapytaniach.


2
Nie jestem pewien sensu publikowania tego dobrze po udzieleniu odpowiedzi na pytanie, ale chciałem podkreślić nieco inny punkt
Used_By_Already

5
Redaktorom; proszę nie próbować przekształcać pseudo SQL w bloki kodu, po prostu nie działa, ponieważ komentarz opiera się na BOLD.
Used_By_Already

2
Do redaktorów (ponownie) proszę nie dodawać fałszywych cudzysłowów za pośrednictwem pseudokodu; NIE pomagają w zrozumieniu.
Used_By_Already

18

Musisz zrobić jedną z tych dwóch opcji:

  1. Uwzględnij składnik czasu w swoim betweenstanie: ... where created_at between '2013-05-01 00:00:00' and '2013-05-01 23:59:59'(niezalecane ... zobacz ostatni akapit)
  2. Zamiast tego użyj nierówności between. Zauważ, że wtedy będziesz musiał dodać jeden dzień do drugiej wartości:... where (created_at >= '2013-05-01' and created_at < '2013-05-02')

Moje osobiste preferencje to druga opcja. Również Aaron Bertrand ma bardzo jasne wyjaśnienie, dlaczego należy go stosować.


6
+1 za 2. Ale -1 za 1. Ten hack na koniec dnia jest całkowicie zawodny i zły pomysł.
Aaron Bertrand

1
@AaronBertrand Wolę również opcję 2 (często z niej korzystam). Ale dlaczego mówisz, że opcja 1 jest „całkowicie zawodna”?
Barranka

5
przeczytaj to w całości . Nigdy nie należy używać BETWEENdo zapytań dotyczących zakresu dat, które obejmują czas; zbyt wiele może się nie udać.
Aaron Bertrand


7

Uważam, że najlepszym rozwiązaniem do porównywania pola daty i godziny z polem daty jest:

DECLARE @StartDate DATE = '5/1/2013', 
        @EndDate   DATE = '5/1/2013' 

SELECT * 
FROM   cases 
WHERE  Datediff(day, created_at, @StartDate) <= 0 
       AND Datediff(day, created_at, @EndDate) >= 0 

Jest to równoważne z instrukcją włączającą między, ponieważ zawiera zarówno datę początkową, jak i końcową, a także te, które przypadają między.


3
cast(created_at as date)

To zadziała tylko w 2008 i nowszych wersjach SQL Server

Jeśli używasz starszej wersji, użyj

convert(varchar, created_at, 101)

2
convert(varchar, created_at, 101)wynik jest podobny, dd/MM/yyyya stringi OP są yyyy-MM-dd, myślę, że ta odpowiedź nie zadziała;).
shA.t

2

Możesz użyć date()funkcji, która wyodrębni datę z daty i godziny i poda wynik jako datę włącznie:

SELECT * FROM Cases WHERE date(created_at)='2013-05-01' AND '2013-05-01'

0

Data dyamic BETWEEN zapytanie sql

var startDate = '2019-08-22';
var Enddate = '2019-10-22'
     let sql = "SELECT * FROM Cases WHERE created_at BETWEEN '?' AND '?'";
     const users = await mysql.query( sql, [startDate, Enddate]);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.