О моей базе данных Дата Тип данных крестовый поход: Действительный? Стоящий? Кто-нибудь еще чувствует это?


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), convert (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 марта самого первого примера), и предотвращает решение БД о преобразовании миллиона значений даты в таблице в строки (используя некоторую специфическую для сервера дату форматирование, которое может даже не совпадать с форматом даты в строковых параметрах в 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'

Если они хотя бы увидели какую-то явную попытку конвертировать дату, они могли бы начать делать это для своего странного формата даты и убить несколько вечных ошибок до того, как они появятся. В конце концов, мы (I) пытаемся отговорить людей от пристрастия к SQL-инъекциям (и будет ли кто-либо выступать за параметризацию запроса, а затем объявлять драйверу, который @pBirthdateявляется строкой, когда у интерфейса есть тип datetime?)

Возвращаясь к тому, что происходит после того, как я сделаю свою рекомендацию: я обычно получаю некоторый откат к рекомендации «будь явным, используй x», как «все остальные делают это», «она всегда работает для меня», «покажи мне какое-нибудь руководство или справочный документ что говорит, что я должен быть явным "или даже" что ?? "

В ответ на некоторые из них я спросил, будут ли они искать в столбце int, WHERE age = '99'передавая значение age в виде строки. «Не будь глупым, нам не нужно ставить« при поиске int », приходит ответ, так что в их сознании где-то есть понимание различных типов данных, но, возможно, просто нет связи с логическим скачком, который ищет int столбец, передавая строку (очевидно глупо) и ища столбец даты, передавая строку (очевидно разумно), является лицемерием

Таким образом, в наших SQL у нас есть способ записывать вещи в виде чисел (использовать числа без разделителей), вещи в виде строковых строк (использовать что-либо между разделителями апострофов). Почему нет разделителей для дат? Это такой фундаментальный тип данных в большинстве БД? Может быть, все это может быть решено просто путем написания даты таким же образом, как javascript позволяет нам указать регулярное выражение, поместив /любую сторону некоторых символов. /Hello\s+world/, Почему бы не иметь что-то для свиданий?

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


Возвращаясь к основному вопросу: я утверждаю, что разумно быть явным с этим средством, которое заставляет нас передавать множество различных типов данных в виде строк.

Это правильное утверждение?

Должен ли я продолжить этот крестовый поход? Является ли верным утверждение о том, что строгая типизация является современной нет-нет? Или все РСУБД (включая древние версии) будут там, когда отправляют запрос WHERE datecolumn = 'string value'абсолютно точно правильно, преобразуют строку в дату и выполняют поиск без преобразования табличных данных / потери использования индексов? Я подозреваю, что нет, по крайней мере, из личного опыта Oracle 9. Я также подозреваю, что могут существовать некоторые сценарии, в которых можно с этим справиться, если строки всегда пишутся в каком-то стандартном формате ISO, а в столбце указан некоторый вариант даты, тогда Строковый параметр всегда будет правильно неявно преобразован. Это делает это правильно?

Это стоящая задача?

Многие люди, кажется, не понимают, или не заботятся, или демонстрируют какое-то лицемерие в том, что их целые числа - это целые, но их даты - строки. Хотя для большинства характерно, что мало кто когда-либо поворачивался и говорил: «Вы знаете, Что, я согласен с вашей точкой зрения. Я буду прямо сейчас о моих датах ".


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

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 могут привести к проблемам, упомяните их, но не пытайтесь заставить людей быть более жесткими, если это не принесет им никакой пользы в их текущем контексте.


Это не столько вопрос о неоднозначности форматов даты «американский против разумных». Речь идет о том, имеет ли смысл передавать даты в операторе SQL в виде строки и полагаться на неявное преобразование в дату. Вопрос о том, что базе данных необходимо выполнить миллион преобразований date-> str для всех миллионов строк, является одним из аспектов производительности, и для одного запроса может потребоваться только 1/1000 секунды, но теперь представьте это в контексте таким образом одновременных пользователи. Большая проблема производительности заключается в том, что преобразование данных означает, что индексы больше не могут использоваться, и это может быть очень серьезным
Caius Jard

@CaiusJard: мой ответ стоит: иногда это разумно, а иногда нет, это зависит от контекста. И если честно, я не хочу «... представить ...» что - нибудь здесь. Когда речь заходит о производительности, обсуждение любого гипотетического случая бесполезно. Когда возникают измеримые проблемы с производительностью, тогда пора оптимизировать, а иногда и микрооптимизировать, а не заранее.
Док Браун

Интересно, что вы считаете это гипотетическим; Я полагаюсь на неявное поведение как на явную возможность возникновения ошибок и проблем с производительностью (по хорошо задокументированным причинам: индексы не работают, если все данные столбца преобразуются до их поиска), а с явными инструкциями этого не может быть
Caius Jard

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

1
@CaiusJard: если вы хотите произвести впечатление на других профессионалов отрасли, вы должны точно знать, почему «оптимизация производительности» сильно отличается от «оптимизации безопасности», и в этом-то и заключается моя точка зрения - проблемы с производительностью можно решать после их возникновения, что редко поздно. Проблем с безопасностью нет, их следует тщательно избегать, прежде чем они возникнут. Поэтому, пожалуйста, не сравнивайте яблоки с апельсинами. Если вам нравятся крестовые походы, аргументы безопасности гораздо лучше подходят для этого ;-)
Док Браун

5

Ваш крестовый поход не решает проблему.

Есть две отдельные проблемы:

  • неявное преобразование типов в SQL

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

Я вижу, откуда вы идете с вашим крестовым походом, но я не думаю, что явное преобразование фактически решает проблему под рукой:

  • Неявное преобразование все еще происходит в случае несоответствия между типами в сравнении. Если строка сравнивается с датой, SQL сначала попытается преобразовать строку в дату. Таким образом, сравнение столбца типа даты с явно преобразованным значением даты точно такое же, как сравнение с датой в строковом формате. Единственное отличие, которое я вижу, заключается в том, что вы сравниваете значение даты со столбцом, который на самом деле содержит не даты, а строки - но в любом случае это будет ошибкой.

  • Использование явного преобразования не решает неоднозначность в форматах даты, отличных от ISO.

Единственное решение, которое я вижу:

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

И, конечно же, никогда не храните даты в столбце строкового типа. Но опять же, явное преобразование литералов даты не помешает этому.

Можно утверждать, что неявные преобразования были ошибкой в ​​SQL, но, учитывая, как устроен язык, я не вижу преимущества явного преобразования. В любом случае это не предотвратит неявное преобразование, а только усложнит чтение и запись кода.


Правда. Возможно, я должен отметить это с этой точки зрения, что наиболее разумно сделать так, чтобы операнд datecolumn и операнд значения имели один и тот же тип данных (будь то строка, дата, что угодно). Я специально делаю эту рекомендацию только в вопросах, где я знаю, что столбец таблицы - DATETIME, и в качестве примера ответа он использует строковый операнд с неявным преобразованием.
Caius Jard

Что-то не так со мной в этом ответе. Вы делаете некоторые интересные замечания, но я чувствую, что заключение является идеалистическим. С точки зрения дизайна, да, форматы даты, отличные от ISO, являются неоднозначными для человеческого глаза, но если использовать явное преобразование, синтаксически это не является неоднозначным для анализатора. Аналогичным образом, для многих процессов ETL, связанных с датами, требуется некоторое сравнение (в форме импорта файлов) строки с форматом даты в базе данных. Попытка устранить сравнение строк с датой кажется мне нереальной.
Данк

@DanK: ETL - это другая проблема - если вы читаете данные из CSV-файла или чего-то еще, очевидно, что вам нужно обрабатывать данные как строки и явно разбирать типизированные значения. Но это не тот сценарий, который описывает OP.
JacquesB

Это может легко быть точкой, которую я описываю, хотя; нет ничего особенного в строке чисел, хранящейся в csv, которая требует явного объявления формата при разборе, и это становится актуальным для аргумента, который я делаю, если новичок читает какой-то ответ в SO, где профессионал не прилагает никаких усилий, чтобы явно объявляйте формат даты, заставляя новичка предполагать, что им не нужно об этом беспокоиться (или что БД будет все время правильно его анализировать)
Caius Jard

@CaiusJard: Я считаю, что это очень разные сценарии. Говоря о SQL в нормальных сценариях, я предполагаю, что столбцы имеют соответствующие типы - то есть целочисленные столбцы имеют целочисленный тип, столбцы даты имеют тип данных и так далее. Если у вас нет правильных типов в таблицах (т. Е. Храните даты в виде строк), у вас большие проблемы, и явное преобразование литералов даты в запросах не спасет вас , что является моей точкой зрения.
JacquesB

3

Прежде всего, у вас есть точка зрения. Даты не должны быть приведены в строки. Механизмы баз данных - это сложные звери, в которых вы никогда не уверены на 100%, что именно произойдет под капотом при произвольном запросе. Преобразование в даты делает вещи однозначными и может повысить производительность.

НО

Для большинства людей эта проблема не стоит дополнительных усилий. Если бы в запросе было легко использовать литералы даты, было бы легко защитить вашу позицию. Но это не так. Я в основном использую SQL Server, поэтому попытки запомнить этот беспорядок для преобразования даты просто не происходят.

Для большинства людей прирост производительности незначителен. «Да, мистер Босс-мэн, я потратил дополнительные 10 минут на исправление этой простой ошибки (мне пришлось поискать, как конвертировать даты, потому что этот синтаксис особенный…). Но я сэкономил лишние 0,00001 секунды на редко выполняемый запрос. " Это не полетит в большинстве мест, где я работал.

Но это устраняет двусмысленность в форматах даты, которые вы говорите. Опять же, для многих приложений (внутренних приложений компании, местных органов власти и т. Д. И т. Д.) Это не является проблемой. А для тех приложений, для которых это важно (большие, международные или корпоративные приложения), это либо становится проблемой пользовательского интерфейса / бизнес-уровня, либо у тех компаний уже есть команда опытных администраторов баз данных, которые уже знают это. TL / DR: если интернационализация является проблемой, кто-то уже думает об этом и уже сделал, как вы предлагаете (или иным образом смягчил проблему).

И что теперь?

Если вы чувствуете себя таким склонным, продолжайте бороться за хороший бой. Но не удивляйтесь, если большинство людей не считают, что это достаточно важно для беспокойства. То, что есть ситуации, когда это важно, не означает, что это ситуация каждого (и, вероятно, это не так). Так что не удивляйтесь, когда вас подталкивают к чему-то технически правильному и лучше, но не очень актуальному.


1

Я утверждаю, что разумно быть явным с этим средством, которое заставляет нас передавать множество различных типов данных в виде строк.

Предполагая, что «даты» передаются «в» строках, тогда да; Я абсолютно согласен, что вы правы в этом.

Когда это «01/04/07»?
* 4 января?
* 1 апреля?
* 7 апреля [2001]?

Любые или все из них могут быть правильными, в зависимости от того, как «компьютер» решает их интерпретировать.

Если вам нужно построить динамический SQL с литералами в них, то форматирование даты должно быть четко определено и, предпочтительно, независимо от компьютера (у меня был странный пример на Windows Server, где обработка на основе даты в службе Windows шла не так, как надо) потому что оператор вошел в консоль с другими настройками формата даты!). Лично я исключительно использую [d] формат "гггг-мм-дд".

Тем не мение ...

Лучшим решением является использование параметризованных запросов , которые заставляют тип данных , которые будут преобразованы , прежде чем 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.