O mojej bazie danych data krucjata typu danych: Ważna? Wart? Czy ktoś to czuje?


13

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 @pBirthdatejest ł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 ".


Widziałem nawet, że ktoś ma problemy z 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 systemach
Steve Barnes

2
Zastanawiałem się nad rozszerzeniem mojego przykładu, by zapytać, czy WHERE age = '0x0F'jest to dobry sposób, by mieć nadzieję, że baza danych wyszuka piętnastolatków.
Caius Jard,

1
Usunąłem tutaj pytanie nie na temat - nie wykonujemy próśb o zasoby. Z tego powodu oddano jeden z 2 bliskich głosów. W przeciwnym razie uważam, że jest to ważne pytanie, choć może być zbyt szerokie. Mam nadzieję, że usunięcie pytania nie na temat pomoże trochę zawęzić sytuację.
Thomas Owens

TL; DR, ale w systemach produkcyjnych oczekiwałbym, że takie daty prawie zawsze będą miały parametry. Zakodowanie dat w zapytaniach jest większym problemem niż to, czy używasz konwersji niejawnych. Jeśli piszę jakieś zapytanie o odrzucenie, to albo działa, albo nie. I tak nigdy tego nie robię (ponieważ nigdy nie pamiętam domyślnego formatu daty), ale nie jestem pewien, czy to ma duże znaczenie.
JimmyJames

1
Życie polega na wybieraniu bitew. Moim zdaniem ten nie jest po prostu wart walki…
Robbie Dee,

Odpowiedzi:


7

Napisałeś:

to parametry od 1 stycznia do 3 stycznia lub 1 marca.

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.

Czy powinienem kontynuować tę krucjatę?

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.


To jednak nie tyle pytanie o dwuznaczność formatów dat amerykańskich i rozsądnych. Chodzi o to, czy rozsądnie jest przekazywać daty w instrukcji SQL jako ciąg znaków i polegać na niejawnej konwersji na datę. Pytanie o to, czy baza danych musi wykonać milion konwersji data-> str dla wszystkich milionów wierszy, jest jednym aspektem wydajności i może zająć tylko 1/1000 sekundy dla jednego zapytania, ale teraz wyobraź sobie to w kontekście równoczesnych zbiorów użytkownicy. Większy problem z wydajnością polega na tym, że konwersja danych oznacza, że indeksy nie mogą być dłużej używane, a to może być naprawdę poważne
Caius Jard,

@CaiusJard: moja odpowiedź brzmi: czasem jest sensowna, a czasem nie, zależy od kontekstu. I szczerze, odmawiam „… wyobrażenia sobie…” czegokolwiek tutaj. Jeśli chodzi o wydajność, omawianie hipotetycznych przypadków nie jest przydatne. Kiedy występują mierzalne problemy z wydajnością, nadszedł czas na optymalizację, a czasem na mikrooptymalizację, nie wcześniej.
Doc Brown,

Interesujące jest to, że postrzegasz to jako hipotetyczne; Widzę, że poleganie na zachowaniu niejawnym jest wyraźną okazją do pojawienia się błędów i komplikacji wydajnościowych (z dobrze udokumentowanych powodów: indeksy nie działają, jeśli cała kolumna jest przekształcana przed przeszukaniem), a przy wyraźnych instrukcjach nie może się to zdarzyć
Kajusz Jard

@CaiusJard: nie baw się słowami - z „hipotetycznym” nie mam na myśli „mało prawdopodobnego”, użyłem tego terminu dla każdego wyobrażonego scenariusza, w przeciwieństwie do „rzeczywistej istniejącej sytuacji”, w której można zmierzyć, co się dzieje.
Doc Brown,

1
@CaiusJard: jeśli chcesz zaimponować innym specjalistom z branży, powinieneś dokładnie wiedzieć, dlaczego „optymalizacja wydajności” bardzo różni się od „optymalizacji bezpieczeństwa”, i właśnie o to mi chodzi - problemy z wydajnością można rozwiązać po ich wystąpieniu, co rzadko za późno. Kwestie bezpieczeństwa nie, należy ich całkowicie unikać, zanim się pojawią. Więc proszę nie porównywać jabłek z pomarańczami. Jeśli lubisz krucjaty, argumenty bezpieczeństwa są o wiele lepiej dostosowane do tego ;-)
Doc Brown

5

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

  • nie porównuj kolumn typu łańcuchowego z wartościami nie łańcuchowymi.
  • używaj tylko formatów daty typu ISO.

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.


Prawdziwe. Być może powinienem wskazać z tej perspektywy, że najbardziej sensownym rozwiązaniem jest zapewnienie, aby operand kolumny danych i operand wartości miały ten sam typ danych (ciąg znaków, datę, cokolwiek innego). Szczególnie zalecam to tylko w pytaniach, w których wiem, że kolumna tabeli to DATETIME, a ich przykładową odpowiedzią jest użycie argumentu łańcuchowego z niejawną konwersją.
Caius Jard

Coś jest nie tak ze mną w tej odpowiedzi. Robisz kilka interesujących uwag, ale wydaje mi się, że wniosek jest idealistyczny. Z punktu widzenia projektowania, tak, formaty daty non-ISO są niejednoznaczne dla oka ludzkiego, ale jeśli za pomocą wyraźnej konwersji, składniowo to nie dwuznaczny do parsera. Podobnie wiele procesów ETL związanych z datami będzie wymagać pewnego porównania (w postaci importu pliku) ciągu znaków z formatem daty bazy danych. Próba wyeliminowania dotychczasowych porównań wydaje mi się nierealistyczna.
DanK

@ DanK: ETL to inny problem - jeśli czytasz dane z pliku CSV lub czegoś innego, oczywiście musisz przetworzyć dane jako ciągi znaków i jawnie parsować na wpisane wartości. Ale nie taki scenariusz opisuje OP.
JacquesB

Może to jednak być punkt, który opisuję; nie ma nic specjalnego w ciągu liczb przechowywanych w pliku csv, który wymaga jawnego zadeklarowania formatu podczas analizowania i staje się odpowiedni dla argumentu, który przedstawiam, jeśli nowicjusz czyta jakieś odpowiedzi w SO, gdzie pro nie podejmuje żadnego wysiłku, aby jawnie zadeklaruj format daty, prowadząc początkującego do założenia, że ​​nie musi się o to martwić (lub że db będzie go cały czas analizował poprawnie)
Caius Jard

@CaiusJard: Uważam, że są to bardzo różne scenariusze. Mówiąc o SQL w normalnych scenariuszach, zakładam, że kolumny mają odpowiednie typy - tj. Kolumny liczb całkowitych są typami liczb całkowitych, kolumny dat są typami danych i tak dalej. Jeśli nie masz poprawnych typów w tabelach (tzn. Przechowujesz daty jako ciągi), masz poważne kłopoty, a wyraźna konwersja literałów daty w zapytaniach cię nie uratuje , o co mi chodzi.
JacquesB

3

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.


1

Twierdzę, ż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.

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


Zgadzam się, chociaż ten sam problem można ponownie wymusić za pomocą sparametryzowanych zapytań, wykonując, WHERE datecolumn = @dateParametera następnie w kodzie frontonu, informując sterownik bazy @dateParameterdanych 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ąda WHERE 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)
Caius Jard,
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.