Про хрестовий похід дати моєї бази даних: Дійсно? Варто? Хтось ще відчуває це?


13

Я витрачаю багато часу, відповідаючи на питання SQL над SO. Я часто стикаюся із запитами цієї лайки:

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'

тобто або покладаючись на неявне перетворення з рядка в дату (погано), заданих параметрів, або спираючись на базу даних, що перетворює x мільйони значень рядків бази даних у рядок і робить порівняння рядків (гірше)

Я час від часу коментую, особливо якщо це високопоставлений користувач, який пише розумну відповідь, але, на кого я вважаю, він повинен бути менш неохайним / строго набраним зі своїми типами даних

Зауваження зазвичай має таку форму, що, мабуть, було б краще, якби вони явно перетворили свої рядки в дати, використовуючи to_date (Oracle), str_to_date (MySQL), конвертувати (SQLSERVER) або якийсь подібний механізм:

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

Мої технічні виправдання для цього полягають у тому, що це чітко щодо формату дати та гарантує, що декілька вихідних параметрів безумовно стануть типом даних цільового стовпця. Це запобігає будь-якій можливості, що база даних отримає неявну конверсію помилково (аргумент 3-го січня / 1-го березня в першому прикладі), і це перешкоджає db вирішувати перетворювати мільйони значень дати в таблиці в рядки (використовуючи певну дату для сервера форматування, яке може навіть не відповідати формату дати в параметрах рядка в sql) для того, щоб зробити порівняння - жахи мають багато

Моє соціальне / академічне виправдання для цього полягає в тому, що SO - це навчальний майданчик; люди на ньому здобувають знання або неявно, або прямо. Щоб відповісти на новачків із цим запитом як відповідь:

SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'

Можливо, спонукають їх вважати це розумним, коригуючи дату під потрібний формат:

SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'

Якщо вони принаймні побачили явну спробу перетворити дату, вони можуть почати робити це для свого дивного формату дати та вбити кілька навіки-помилок до їх появи. Зрештою, ми (я) намагаємось відмовити людей від потрапляння у звичку ін'єкцій SQL (і чи хотів би хто-небудь прихилити параметризацію запиту, а потім оголосити драйверу, що @pBirthdateце рядок, коли frontend має тип дати?)

Повернення до того, що станеться після того, як я висловлюю свою рекомендацію: я зазвичай отримую певний відклик до рекомендації "будь явний, використовуйте х", як "всі інші це роблять", "це завжди працює для мене", "покажіть мені якийсь посібник чи довідковий документ що говорить, що я повинен бути явним "або навіть" що ?? "

Я запитав у відповідь на деякі з них, чи шукають вони колонку int, WHERE age = '99'передаючи вік як рядок. "Не будьте дурними, нам не потрібно ставити" під час пошуку int ", приходить відповідь, тому десь зважають на різні типи даних десь у їхній свідомості, але, можливо, просто немає зв'язку з логічним стрибком, що шукає int стовпець, передаючи рядок (мабуть, нерозумно) і шукає стовпець дати, передаючи рядок (мабуть, розумний) - це лицемірство

Таким чином, у наших SQL є спосіб записати речі як числа (використовувати числові цифри, без роздільників), речі як рядкові рядки (використовувати що-небудь між роздільниками апострофа). Чому немає обмежувачів для дат? Це такий фундаментальний тип даних у більшості БД? Невже все це можна, можливо, вирішити лише шляхом того, щоб записати дату так само, як javascript дозволяє нам вказати регулярний вираз, додавши /обидві сторони деяких символів. /Hello\s+world/. Чому б не мати щось на побачення?

Насправді, наскільки мені відомо, (лише) Microsoft Access насправді має символи, які вказують "дата була записана між цими роздільниками", тому ми можемо отримати хороший ярлик, як-от, WHERE datecolumn = #somedate#але презентація дати все ще може викликати проблеми, наприклад, mm / di vs dd / мм, оскільки МС завжди грали швидко і вільно з речами, які натовп VB вважав гарною ідеєю


Назад до основного моменту: я стверджую, що розумно бути явним із цим носієм, який змушує нас передавати безліч різних типів даних у вигляді рядків.

Чи справедливе твердження?

Чи варто продовжувати цей хрестовий похід? Чи справедливо те, що строго набравши текст - це сучасний ні-ні? Або всі RDBMS (включаючи стародавні версії) там, коли запит WHERE datecolumn = 'string value'абсолютно безумовно правильно перетворить рядок у дату і здійснить пошук, не перетворюючи дані таблиці / втрачаючи використання індексів? Я підозрюю, що ні, принаймні з особистого досвіду Oracle 9. Я підозрюю також, що можуть бути якісь сценарії відключення, якщо рядки завжди записуються у певному стандартному форматі ISO, а стовпець - якийсь аромат дати, то Параметр string завжди буде правильно неявно перетворений. Це робить це правильно?

Це гідне завдання?

Багато людей, схоже, не розуміють цього, або не хвилюються, або виявляють деяке лицемірство, оскільки їхні інти є внутрішніми, але їхні дати - це рядки. Загальним для більшості є те, що мало хто колись розвернувся і сказав: "Ви знаєте що, я погоджуюся з вашою точкою. Відтепер я буду чітко розповідати про свої дати ".


Я навіть бачив, як у когось виникають проблеми з WHERE datecolumn = 01/02 / 12'`, де можливо, вони просять рік 1912, 2012, 2001, 1901, 12 або 1. Це також проблема поза світом баз даних, числом Програмістів, які не можуть зрозуміти, чому перетворення "09"на int викликає збій - легіон, 9 не є дійсною восьмеричною цифрою, а провідна 0 робить рядковий восьмерик у багатьох системах
Стів Барнс,

2
Я думав над тим, щоб розширити свій приклад, щоб запитати, чи WHERE age = '0x0F'є коректним способом сподіватися, що база даних шукатиме п'ятнадцятирічних дітей ...
Caius Jard

1
Я видалив тут питання, яке є поза темою - ми не робимо запитів на ресурси. З цієї причини був наданий один із 2 закритих голосів. В іншому випадку я вважаю, що це питання є валідним, хоча це може бути межею занадто широким. Я сподіваюся, що зняття поза тематичного питання допоможе трохи звузити речі.
Томас Оуенс

TL; DR, але у виробничих системах я б очікував, що такі дати майже завжди будуть у параметрах. Дати жорсткого кодування в запитах є більшою проблемою, ніж використання непрямих конверсій. Якщо я пишу запит на викидання, він або працює, або не працює. Я ніколи цього не роблю (бо не можу згадати формат дати за замовчуванням), але не впевнений, що це має велике значення.
JimmyJames

1
Життя полягає в тому, щоб вибрати свої битви. На мою думку, з цим просто не варто боротися ...
Роббі Ді

Відповіді:


7

Ти написав:

це параметри 1 січня по 3 січня або 1 березня.

Це справді потенційне джерело помилок. Вказівка ​​цього на запитання запитувача може допомогти іншим читачам, так що так, це важлива проблема. Однак, щоб бути конструктивним, я хотів би

  • зверніться до ANSI SQL та використовуйте літери DATE або DATETIME з цього стандарту

  • використовувати звичайний, однозначний формат дати конкретної СУБД (і згадати, який діалект SQL використовується)

На жаль, не кожна СУБД підтримує літерали дати ANSI SQL точно подібним чином (якщо вони взагалі підтримують її), тому це, як правило, призводить до варіанту другого підходу. Те, що "стандарт" жорстко не реалізується різними постачальниками БД, ймовірно, є частиною проблеми тут.

Зауважимо далі, що для багатьох систем реального світу люди можуть насправді покладатися на певну, фіксовану локаль на сервері баз даних, навіть якщо клієнтські програми локалізовані, оскільки існує лише один вид сервера, завжди налаштований однаково. Отже, '01 / 03/2017 'часто можна вважати фіксованим форматом' dd / mm / yyyy 'або' mm / dd / yyyy 'для будь-якої SQL, що використовується в конкретній системі, з якою вони працюють. Тож якщо хтось скаже вам "це завжди працює для мене", це, можливо, справді розумна відповідь для його оточення . Якщо це так, це робить менш вартим обговорення цієї теми.

Якщо говорити про "причини ефективності": доки не існує вимірних проблем ефективності, це доволі забобонно сперечатися з "потенційними проблемами ефективності". Якщо база даних здійснює мільйон поточних перетворень або, мабуть, не має значення, коли різниця у часі становить лише 1/1000 секунди, а справжнє вузьке місце - це мережа, яка спричиняє запит тривати 10 секунд. Тож краще відкладіть ці проблеми убік, доки хтось прямо попросить міркувань щодо ефективності.

Чи варто продовжувати цей хрестовий похід?

Кажу вам секрет: Я ненавиджу релігійні війни. Вони не призводять до нічого корисного. Отже, якщо неоднозначні параметри часу / часу в SQL можуть призвести до проблем, згадайте про них, але не намагайтеся примушувати людей бути більш жорсткими, якщо це не приносить їм користі в їх нинішньому контексті.


Це не стільки питання щодо неоднозначності форматів дат Америки проти Sensible. Йдеться про те, чи розумно передавати дати в операторі SQL як рядок і покладатися на неявне перетворення на сьогоднішній день. Питання про базу даних, що повинна робити мільйон перетворень дати-> рядки для всіх мільйонів рядків, є одним аспектом продуктивності, і це може зайняти лише 1/1000-ту секунду для одного запиту, але тепер уявіть це в контексті тисячі одночасних користувачів. Проблема більшої продуктивності полягає в тому, що перетворення даних означає, що індекси більше не можна використовувати, і це може бути справді серйозно
Caius Jard

@CaiusJard: моя відповідь стоїть: це часом розумно, а іноді ні, це залежить від контексту. І якщо чесно, я відмовляюся тут "... уявити ..." . Що стосується виконання, обговорення будь-якого гіпотетичного випадку не є корисним. Коли виникають вимірювані проблеми ефективності, тоді прийшов час оптимізувати, а іноді і мікрооптимізувати, не заздалегідь.
Док Браун

Цікаво, що ви бачите це як гіпотетичне; Я бачу, покладаючись на неявну поведінку, як на чітку можливість виникнення помилок та ускладнень у виконанні (з добре задокументованих причин: індекси не працюють, якщо цілі дані даних стовпців трансформуються до їх пошуку), і з чіткими інструкціями цього не може відбутися
Caius Джард

@CaiusJard: не грайте зі словами - з "гіпотетичним" я не маю на увазі "навряд чи", я використовував цей термін для будь-якого уявного сценарію, на відміну від "реальної існуючої ситуації", де можна виміряти те, що відбувається.
Док Браун

1
@CaiusJard: якщо ви хочете вразити інших професіоналів галузі, ви повинні точно знати, чому "оптимізація продуктивності" сильно відрізняється від "оптимізації безпеки", і це якраз моя думка - проблеми з продуктивністю можна вирішувати після їх виникнення, тобто рідко. запізно. Проблем із безпекою немає, їх слід ретельно уникати до їх виникнення. Тому, будь ласка, не порівнюйте яблука з апельсинами. Якщо вам подобаються хрестові походи, для цього набагато краще підходять аргументи безпеки ;-)
Doc Brown

5

Ваш хрестовий похід не вирішує проблеми.

Є два окремих питання:

  • неявне перетворення типу в SQL

  • неоднозначні формати дати, як-от 05/06/07

Я бачу, звідки ви походите з хрестовим походом, але не думаю, що явна конверсія насправді вирішує проблему:

  • Неявне перетворення все ж відбувається у випадку невідповідності між типами у порівнянні. Якщо рядок порівнюється з датою, SQL спробує спершу перетворити рядок у дату. Отже, порівняння стовпця типу дати з явно перетвореним значенням дати точно таке ж, як порівняння з датою у строковому форматі. Я бачу лише різницю, якщо ви порівнюєте значення дати зі стовпцем, який насправді не містить дат, а рядки - але це буде помилкою у будь-якому випадку.

  • Використання явного перетворення не вирішує неоднозначність у форматах дати, що не відповідають ISO.

Єдине рішення, яке я бачу:

  • не порівнюйте стовпці типу рядків з не рядковими значеннями.
  • використовувати будь-коли формати дат типу ISO.

І звичайно, ніколи не зберігайте дати в стовпці типу рядка. Але знову ж таки, явне перетворення літералів дат цього не завадить.

Можливо, неявні перетворення були помилкою у SQL, але враховуючи, як створена мова, я не бачу користі від явного перетворення. Це все одно не уникне неявного перетворення, а лише ускладнить читання та запис коду.


Правда. Можливо, я маю зазначити з цієї точки зору, що найрозумніше, що потрібно зробити, - це забезпечити, щоб операнд стовпця стовпців і операнд значення мали однаковий тип даних (будь то рядок, дата і будь-який інший). Я спеціально даю цю рекомендацію лише у питаннях, де я знаю, що стовпець таблиці DATETIME, і їх прикладом відповіді є використання рядкового операнда з неявним перетворенням.
Caius Jard,

Щось не відповідає мені на цю відповідь. Ви зазначаєте кілька цікавих моментів, але я вважаю, що висновок ідеалістичний. Так, з точки зору дизайну, так, формати дат, що не відповідають ISO, неоднозначні для людського ока, але якщо використовувати явне перетворення, синтаксично це не неоднозначно для синтаксичного аналізу. Так само багато процесів ETL, що включають дати, потребують певного порівняння (у вигляді імпорту файлу) рядка до формату дати бази даних. Намагання усунути рядкові порівняння на сьогоднішній день здається мені нереальним.
DanK

@DanK: ETL - інша проблема - якщо ви читаєте дані з CSV-файлу чи чогось іншого, очевидно, вам доведеться обробляти дані у вигляді рядків і явно розбирати на введені значення. Але це не той сценарій, який описує ОП.
ЖакБ

Це міг легко бути моментом, який я описую, хоча; немає нічого особливого в рядку чисел, що зберігаються в csv, що вимагає явного оголошення формату при синтаксичному розборі, і це стає відповідним аргументу, який я викладаю, якщо новачок читає якусь відповідь в ТА, де професіонал не докладає жодних зусиль, щоб явно оголосити формат дати, що спонукає новачків припустити, що їм не потрібно про це турбуватися (або що db буде його правильно аналізувати весь час)
Caius Jard

@CaiusJard: Я вважаю, що це дуже різні сценарії. Якщо говорити про SQL у звичайних сценаріях, я припускаю, що стовпці мають відповідні типи - тобто цілі стовпці мають цілий тип, стовпці дати - це тип даних тощо. Якщо у вас немає правильних типів у таблицях (тобто зберігати дати як рядки), ви відчуваєте глибокі проблеми, і явна конвертація літеральних дат у запитах не врятує вас , що є моєю суттю.
ЖакБ

3

Перш за все, у вас є пункт. Дати не слід ставити на рядки. Двигуни бази даних - це складні звіри, де ви ніколи не знаєте, що саме відбудеться під кришкою, задавши довільний запит. Перетворення дат робить речі однозначними та може підвищити ефективність.

АЛЕ

Це не проблема, яку варто вирішити для більшості людей. Якби у запиті було просто використовувати літерали дат, було б легко відстояти свою позицію. Але це не так. Я в основному використовую SQL Server, тому намагаюся запам'ятати той безлад для перетворення дати просто не відбувається.

Для більшості людей приріст продуктивності незначний. "Чому так, містере Босс-людина, я витратив додаткові 10 хвилин на виправлення цієї простої помилки (мені довелося google, як конвертувати дати, тому що цей синтаксис ... особливий ...). Але я заощадив додаткові 0,00001 секунди на рідко виконуваний запит. " Це не пролетить більшість місць, де я працював.

Але це знімає неоднозначність у форматі дат, про які ви говорите. Знову ж таки, для багатьох застосувань (внутрішні додатки компанії, речі місцевого самоврядування тощо) це насправді не викликає занепокоєння. І для тих застосувань, де це викликає занепокоєння (великі, міжнародні або корпоративні додатки), що або стає концерном інтерфейсу / бізнес-рівня, або ті компанії вже мають команду добре обізнаних DBA, які вже знають це. TL / DR: якщо інтернаціоналізація викликає занепокоєння, хтось уже замислюється над цим і вже зробив, як ви пропонуєте (або іншим чином пом’якшив це питання).

І що тепер?

Якщо ви відчуваєте таке схильність, продовжуйте вести добрий бій. Але не дивуйтеся, якщо більшість людей не вважають, що це досить важливо, щоб турбуватися. Тільки тому, що є ситуації, коли це має значення, не означає, що це ситуація кожного (а це, швидше за все, не так). Тож не дивуйтеся, коли ви отримаєте певний поштовх для чогось технічно-правильного-і-кращого, але-не-справді актуального.


1

Я стверджую, що розумно бути явним із цим носієм, який змушує нас передавати безліч різних типів даних у вигляді рядків.

Якщо припустити, що "дати" передаються навколо "в" Струни, то так; Я абсолютно згоден, що ти маєш право це робити.

Коли це «01/04/07»?
* 4 січня?
* 1 квітня?
* 7 квітня [2001]?

Будь-яке або все це може бути правильним, залежно від того, як "комп'ютер" вирішить їх інтерпретувати.

Якщо вам потрібно створити динамічний SQL з літералами в них, то ваше форматування дат має бути чітко визначеним і, бажано, незалежним від машин (у мене був дивний на Windows Server, де обробка даних на основі дати в службі Windows пішла на зміну тому що оператор увійшов на консоль з різними налаштуваннями формату дати!). Особисто я використовую виключно [d] формат "yyyy-mm-dd".

Однак ...

Кращим рішенням є використання параметризованих запитів , які змушують тип даних , які будуть перетворені , перш ніж SQL втягується - отримання «дата» значення в Дату сили параметрів перетворення типу на ранніх стадіях (роблячи це виключно проблема кодування, а НЕ SQL один) .


Я погоджуюся, хоча цю ж проблему можна повторно застосувати за допомогою параметризованих запитів, виконавши, WHERE datecolumn = @dateParameterа потім у коді переднього кінця, повідомивши драйверу БД @dateParameterтипу varchar і дотримуючись "01/04/07"його. Оригінальним натхненням на моє запитання є те, що я підозрюю, що хтось скаже мені, що я божевільний за те, щоб зробити це на параметризований запит, тоді, на одному диханні, дав би якийсь один рядок ТАК відповідь, який виглядає так WHERE datecol = 'some string that looks like a date'(і очікую, що новачок повинен знати це лише підказка / параметризуйте це, щоб уникнути проблем)
Caius Jard
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.