Как использовать переменные связывания для литерала DATE?

Есть такой запрос:

SELECT * FROM employee WHERE dob = DATE '1980-05-15'

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

SELECT * FROM employee WHERE dob = DATE :dateOfBirth

Однако этот запрос не работает:

ORA-00936: missing expression

Перепробовал все возможные форматы (1980-05-15, 15-MAY-80, 15-MAY-1980) для переменной :dateOfBirth, но безуспешно, всегда получаю одну и ту же ошибку.


PS1: Знаю о функции to_date(), которая может решить эту проблему, но я не могу ее использовать, потому что запрос формируется в другой системe, в которой нет возможности что-то изменить.

PS2: Формат даты nls_date_format=DD-MON-RR в БД, как указано в параметрах nls_session_parameters и nls_database_parameters.


Свободный перевод вопроса How to use bind variables for date literals? от участника @cdoe


Ответы (1 шт):

Автор решения: 0xdb

Правильное решение:

SELECT * FROM employee WHERE dob = :dateOfBirth

Причина ошибки:

Литерал даты DATE '1980-05-15'это законченая синтаксическая конструкция, которую нельзя поделить на части, то есть она не может быть результатом выражения из нескольких операндов. Такое работать не будет:

select date ''||'1980-05-15' from dual
/
ORA-01841: (full) year must be between -4713 and +9999, and not be 0

Парсер SQL, получая текст запроса, видит в этом фрагменте DATE :dateOfBirth два токена:

  • DATE, после которго он ожидает символьный литерал, отсутствие которого и приводит к ошибке
  • :dateOfBirth, переменные связывания он пропускает, что в них будет на этапе выполнения, ни тип данных, ни значение - ему неизвестно.

Что должно быть в переменной связывания, раз литерал даты с ней не возможен?

Определение и присвоение значения переменной связывания - задача клиентского приложения. Идеально использовать типы даты храняшие значение в бинарном виде. В любом ЯП есть соответствующие типы данных. Например, в JDBC переменная типа java.time будет передана в том же виде, в котором SQL DATE хранит своё значение:

select rawtohex (date'1980-05-15') hexvalue from dual
/
HEXVALUE
----------------
BC07050F00000000

Если по каким либо причинам возможен только символьный тип для переменной связывания, то обязательно надо изменить запрос на использование to_date() с явным указанием формата:

SELECT * FROM employee WHERE dob = to_date (:dateOfBirth, 'dd.mm.yyyy');
→ Ссылка