Sporo czasu poświęcam na odpowiedzi na pytania SQL dotyczące SO. Często spotykam się z zapytaniami tego rodzaju:
SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'
SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'
SELECT * FROM person WHERE birthdate BETWEEN 'some string' AND 'other string'
tzn. albo polegając na domniemanej konwersji ciągów na datę (zły), podanych parametrów lub polegając na bazie danych, która konwertuje x milionów wartości wierszy bazy danych na ciąg i dokonuje porównania łańcucha (gorzej)
Od czasu do czasu robię komentarz, szczególnie jeśli jest to użytkownik z wysokim przedstawicielem, który pisze mądrą odpowiedź, ale moim zdaniem powinien być mniej niechlujny / rygorystycznie wpisywany przy użyciu swoich typów danych
Komentarz zwykle przyjmuje postać, że prawdopodobnie lepiej byłoby, gdyby jawnie przekonwertowali swoje ciągi na daty, używając to_date (Oracle), str_to_date (MySQL), convert (SQLSERVER) lub podobny mechanizm:
--oracle
SELECT * FROM person WHERE birthdate BETWEEN TO_DATE('20170101', 'YYYYMMDD') AND TO_DATE('20170301', 'YYYYMMDD')
--mysql
SELECT * FROM person WHERE birthdate BETWEEN STR_TO_DATE('20170101', '%Y%m%d') AND STR_TO_DATE('20170301', '%Y%m%d')
--SQLS, ugh; magic numbers
SELECT * FROM person WHERE birthdate BETWEEN CONVERT(datetime, '20170101', 112) AND CONVERT(datetime, '20170301', 112)
Moim technicznym uzasadnieniem jest to, że jest jawne co do formatu daty i zapewnia, że kilka parametrów źródłowych zdecydowanie stanie się typem danych kolumny docelowej. Zapobiega to wszelkim możliwym błędom niejawnej konwersji bazy danych (argument z 3 stycznia / 1 marca pierwszego przykładu) i uniemożliwia dbowi o przekonwertowaniu miliona wartości daty w tabeli na ciągi znaków (używając pewnej daty specyficznej dla serwera formatowanie, które może nawet nie pasować do formatu daty w parametrach ciągu w sql) w celu wykonania porównania - mnóstwo horrorów
Moim społecznym / akademickim uzasadnieniem jest to, że SO jest witryną do nauki; ludzie na nim zdobywają wiedzę w sposób dorozumiany lub jawny. Aby trafić nowicjusza za pomocą tego zapytania jako odpowiedzi:
SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'
Może ich przekonać, że jest to rozsądne, dostosowując datę dla preferowanego formatu:
SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'
Jeśli przynajmniej zobaczyli jakąś wyraźną próbę konwersji daty, mogliby zacząć to robić dla swojego dziwnego formatu daty i zabić kilka błędów, zanim się pojawią. W końcu (I) staramy się odwieść ludzi od przyzwyczajania się do iniekcji SQL (i czy ktokolwiek zalecałby sparametryzowanie zapytania, a następnie zadeklarowanie sterownika, który @pBirthdate
jest łańcuchem, gdy frontend ma typ godziny / godziny?)
Wróć do tego, co dzieje się po tym, jak wydam zalecenie: zazwyczaj otrzymuję komunikat zwrotny do zalecenia „bądź wyraźny, użyj x”, na przykład „wszyscy to robią”, „zawsze działa dla mnie”, „pokaż mi instrukcję lub dokument referencyjny to mówi, że powinienem wyraźnie powiedzieć „a nawet„ co? ”
W odpowiedzi na niektóre z nich zapytałem, czy przeszukaliby kolumnę int, WHERE age = '99'
przekazując wiek jako ciąg znaków. „Nie bądź głupi, nie musimy umieszczać słowa„ podczas wyszukiwania int ”, więc doceniamy różne typy danych w ich umyśle, ale być może nie ma żadnego związku z logicznym skokiem, który polega na wyszukiwaniu int kolumna, przekazując ciąg znaków (pozornie głupie) i przeszukując kolumnę daty, przekazując ciąg znaków (pozornie rozsądny), jest hipokryzją
Zatem w naszych SQL-ach mamy sposób na zapisywanie rzeczy jako liczb (używaj liczb, bez ograniczników), rzeczy jako ciągów znaków (używaj czegokolwiek między ogranicznikami apostrofów) .. Dlaczego nie ma ograniczników dla dat? To taki podstawowy typ danych w większości baz danych? Czy cała ta sprawa mogłaby zostać rozwiązana poprzez zapisanie daty w taki sam sposób, w jaki javascript pozwala nam określić wyrażenie regularne, umieszczając /
dowolną stronę niektórych znaków. /Hello\s+world/
. Dlaczego nie mieć czegoś na randki?
W rzeczywistości, o ile mi wiadomo, (tylko) Microsoft Access faktycznie ma symbole wskazujące, że „między tymi ogranicznikami została zapisana data”, dzięki czemu możemy uzyskać dobry skrót, WHERE datecolumn = #somedate#
ale prezentacja daty wciąż może powodować problemy, np. Mm / di vs dd / mm, ponieważ stwardnienie rozsiane zawsze grało szybko i luźno z rzeczami, które tłum VB uważał za dobry pomysł
Wracając do głównego punktu: argumentuję, że mądrze jest mówić wprost o tym medium, które zmusza nas do przekazywania wielu różnych typów danych jako ciągów ..
Czy to prawidłowe stwierdzenie?
Czy powinienem kontynuować tę krucjatę? Czy to prawda, że ciągłe pisanie na klawiaturze jest nowoczesnym „nie”? Czy też wszystkie RDBMS (w tym starożytne wersje) będą dostępne, gdy zapytanie zadaje WHERE datecolumn = 'string value'
absolutnie prawidłową konwersję ciągu na datę i wykonuje wyszukiwanie bez konwersji danych tabeli / utraty użycia indeksów? Podejrzewam, że nie, przynajmniej z własnego doświadczenia Oracle 9. Podejrzewam również, że mogą istnieć pewne scenariusze ucieczki, jeśli ciągi znaków są zawsze zapisywane w jakimś standardowym formacie ISO, a kolumna ma smak daty, to parametr ciągu będzie zawsze poprawnie niejawnie konwertowany. Czy to dobrze?
Czy to jest opłacalne zadanie?
Wydaje się, że wiele osób tego nie rozumie, nie przejmuje się nimi ani nie wykazuje hipokryzji, ponieważ ich intrygi są intami, ale ich daty są ciągami. Jednak większość z nich jest taka, że niewielu ludzi kiedykolwiek się odwróciło i powiedziało „wiesz co, zgadzam się z twoją tezą. Będę odtąd wyrażał się jasno o moich datach ".
źródło
WHERE datecolumn =
01/02/12 '', gdzie jest możliwe, że prosi o rok 1912, 2012, 2001, 1901, 12 lub 1. Jest to również problem poza światem bazy danych, liczbą programistów, którzy nie mogą zrozumieć, dlaczego konwersja"09"
na int jest przyczyną awarii są legion, 9 nie jest poprawnym cyfra ósemkowa oraz wiodącym 0 czyni ósemkowy ciąg w wielu systemachWHERE age = '0x0F'
jest to dobry sposób, by mieć nadzieję, że baza danych wyszuka piętnastolatków.Odpowiedzi:
Napisałeś:
To rzeczywiście potencjalne źródło błędów. Wskazanie tego pytającemu może być pomocne dla innych czytelników, więc tak, jest to ważny problem. Byłbym jednak konstruktywny
odnoszą się do ANSI SQL i używają literałów DATE lub DATETIME z tego standardu
użyj zwykłego, jednoznacznego formatu daty i godziny określonego DBMS (i podaj, który dialekt SQL jest używany)
Niestety, nie każdy DBMS obsługuje dosłowne daty ANSI SQL w dokładnie podobny sposób (jeśli w ogóle je obsługują), więc zazwyczaj prowadzi to do wariantu drugiego podejścia. Fakt, że „standard” nie jest sztywno wdrażany przez różnych dostawców DB, jest prawdopodobnie częścią tego problemu.
Uwaga: w przypadku wielu systemów w świecie rzeczywistym ludzie mogą faktycznie polegać na określonej, ustalonej lokalizacji na serwerze bazy danych, nawet jeśli aplikacje klienckie są zlokalizowane, ponieważ istnieje tylko jeden rodzaj serwera, zawsze skonfigurowany w ten sam sposób. Można więc założyć, że „01/03/2017” ma ustalony format „dd / mm / rrrr” lub „mm / dd / rrrr” dla dowolnego kodu SQL używanego w konkretnym systemie, z którym pracują. Więc jeśli ktoś powie ci „to zawsze działa dla mnie”, może to rzeczywiście rozsądna odpowiedź dla jego otoczenia . W takim przypadku dyskusja na ten temat jest mniej opłacalna.
Mówiąc o „powodach wydajności”: dopóki nie ma mierzalnych problemów z wydajnością, przesądem jest argumentowanie „potencjalnymi problemami z wydajnością”. Jeśli baza danych wykonuje milion konwersji ciągów lub prawdopodobnie nie ma znaczenia, kiedy różnica czasu wynosi zaledwie 1/1000 sekundy, a prawdziwym wąskim gardłem jest sieć, która powoduje, że zapytanie trwa 10 sekund. Lepiej więc odłóż te obawy na bok, o ile ktoś wyraźnie poprosi o względy wydajności.
Mówię ci sekret: nienawidzę wojen religijnych. Nie prowadzą do niczego przydatnego. Tak więc, jeśli niejednoznaczne specyfikacje daty / godziny w SQL mogą prowadzić do problemów, wspomnij o nich, ale nie próbuj zmuszać ludzi do większej sztywności, jeśli tak naprawdę nie przyniosą im żadnych korzyści w ich obecnym kontekście.
źródło
Twoja krucjata nie rozwiązuje problemu.
Istnieją dwa oddzielne problemy:
niejawna konwersja typu w SQL
niejednoznaczne formaty dat, takie jak 05/06/07
Widzę, skąd przybywasz ze swoją krucjatą, ale nie sądzę, aby wyraźne nawrócenie rzeczywiście rozwiązało problem:
Niejawna konwersja nadal występuje w przypadku niedopasowania między typami w porównaniu. Jeśli ciąg zostanie porównany z datą, SQL spróbuje najpierw przekonwertować ciąg na datę. Zatem porównanie kolumny typu daty z jawnie przekonwertowaną wartością daty jest dokładnie takie samo jak porównanie z datą w formacie łańcuchowym. Jedyną różnicą, którą widzę, jest porównanie wartości daty z kolumną, która tak naprawdę nie zawiera dat, ale ciągów - ale i tak byłby to błąd.
Użycie jawnej konwersji nie rozwiązuje niejednoznaczności w formatach danych innych niż ISO.
Jedyne rozwiązanie, jakie widzę:
I oczywiście, nigdy nie przechowuj dat w kolumnie typu ciąg. Ale znowu wyraźna konwersja literałów daty nie zapobiegnie temu.
Prawdopodobnie niejawne konwersje były błędem w SQL, ale biorąc pod uwagę sposób zaprojektowania języka, nie widzę korzyści z jawnej konwersji. I tak nie uniknie niejawnej konwersji, a jedynie utrudni odczyt i zapis kodu.
źródło
Przede wszystkim masz rację. Daty nie powinny być ułożone w ciągi. Mechanizmy baz danych to złożone bestie, w których nigdy nie masz 100% pewności, co dokładnie wydarzy się pod maską, biorąc pod uwagę dowolne zapytanie. Konwersja na daty sprawia, że wszystko jest jednoznaczne i może zwiększyć wydajność.
ALE
Dla większości ludzi nie jest to problem wart dodatkowego wysiłku do rozwiązania. Gdyby w zapytaniu łatwo było użyć literałów daty, łatwo byłoby bronić swojej pozycji. Ale tak nie jest. Najczęściej używam SQL Servera, więc próba zapamiętania tego bałaganu do konwersji daty po prostu się nie dzieje.
Dla większości ludzi wzrost wydajności jest znikomy. „Dlaczego tak, panie szefie, spędziłem dodatkowe 10 minut na naprawie tego prostego błędu (musiałem google przekonwertować daty, ponieważ ta składnia jest… specjalna…). Ale zaoszczędziłem dodatkowe 0,00001 sekundy na rzadko wykonywane zapytanie ”. To nie poleci w większości miejsc, w których pracowałem.
Ale usuwa niejednoznaczności w formatach dat, które mówisz. Ponownie, w przypadku wielu aplikacji (aplikacje wewnętrzne firmy, sprawy samorządu lokalnego itp.) Nie jest to tak naprawdę problemem. A w przypadku aplikacji, w których jest to problem (duże, międzynarodowe lub korporacyjne), albo staje się to problemem dla interfejsu użytkownika / warstwy biznesowej, albo te firmy mają już zespół dobrze zorientowanych DBA, którzy już to wiedzą. TL / DR: jeśli problem dotyczy internacjonalizacji, ktoś już o tym myśli i zrobił już to, co sugerujesz (lub w inny sposób złagodził ten problem).
Co teraz?
Jeśli masz ochotę, kontynuuj walkę w dobrej walce. Ale nie zdziw się, jeśli większość ludzi nie uważa, że jest to wystarczająco ważne, aby się martwić. To, że istnieją sytuacje, w których ma to znaczenie, nie oznacza, że taka jest sytuacja każdego (i prawdopodobnie nie jest). Więc nie zdziw się, gdy dostaniesz trochę odpowiedzi za coś, co jest technicznie poprawne i lepsze, ale nie tak naprawdę istotne.
źródło
Zakładając, że „daty” są przekazywane „w” ciągach, to tak; I całkowicie zgadzam się , że masz rację to zrobić.
Kiedy jest „01/04/07”?
* 4 stycznia?
* 1 kwietnia?
* 7 kwietnia [2001]?
Dowolne lub wszystkie z nich mogą być poprawne, w zależności od tego, jak „komputer” zdecyduje się je zinterpretować.
Jeśli masz do budowy dynamicznego SQL z literałów w nich, wówczas data formatowania musi być dobrze zdefiniowane, a korzystnie maszyna niezależne (miałem dziwne jeden na Windows Server, gdzie data oparte na przetwarzanie ramach usługi Windows wyszło ponieważ operator zalogował się do konsoli z różnymi preferencjami formatu daty!). Osobiście używam wyłącznie [d] formatu „rrrr-mm-dd”.
Jednak ...
Najlepszym rozwiązaniem jest użycie zapytania parametryzowane które zmuszają typ danych do konwersji przed SQL angażuje - uzyskanie wartości „Data” w Data siły parametrów konwersji typu wcześnie (co czyni go wyłącznie do kodowania problemu, a nie jeden SQL) .
źródło
WHERE datecolumn = @dateParameter
a następnie w kodzie frontonu, informując sterownik bazy@dateParameter
danych typu varchar i pozostając"01/04/07"
w nim. Oryginalną inspiracją mojego pytania jest to, że podejrzewam, że każdy, kto powiedziałby mi, że zwariowałem na punkcie sparametryzowanego zapytania, podałby następnie jednym tchem odpowiedź SO, która wyglądaWHERE datecol = 'some string that looks like a date'
(i oczekuje, że początkujący powinien wiedzieć to tylko podpowiedź / sparametryzuj to, aby uniknąć problemów)