При работе с запросами в системе «1С:Предприятие 8» разработчики часто сталкиваются с необходимостью фильтрации данных по датам. Типичная задача — выбрать документы, договоры или записи регистров, дата которых меньше определенного значения (например, меньше 01.01.2017) либо вовсе не заполнена (является пустой). На первый взгляд задача кажется тривиальной, но здесь кроется несколько важных нюансов, связанных с оптимизацией работы СУБД, использованием индексов и особенностями левых соединений. Проанализируем эти моменты подробно и разберем наиболее эффективные способы решения задачи.
Прежде чем переходить к составлению запроса, выясним, как платформа 1С хранит и интерпретирует «пустую дату». В отличие от некоторых других систем программирования, в 1С пустая дата — это вполне конкретное минимальное значение временной шкалы, а именно: 01.01.0001 00:00:00. В языке запросов 1С это значение можно получить с помощью конструкции ДАТАВРЕМЯ(1, 1, 1).
Когда нам требуется отобрать договоры, созданные ранее определенной даты (например, &ДатаПараметр, равный 01.01.2017), или договоры без указанной даты, многие разработчики пытаются объединить эти два условия через логический оператор ИЛИ. Типичный пример такого условия выглядит так:
ГДЕ Договор.ДатаЗаключения < &ДатаПараметр ИЛИ Договор.ДатаЗаключения = ДАТАВРЕМЯ(1, 1, 1)
Давайте разберем, почему такая конструкция избыточна. Поскольку пустая дата (01.01.0001) хронологически находится гораздо раньше любой реальной рабочей даты (включая наш параметр отбора), она автоматически удовлетворяет условию Договор.ДатаЗаключения < &ДатаПараметр. Любая заполненная корректная дата, которая меньше установленного лимита, также попадет в этот диапазон. Таким образом, дополнительное сравнение на равенство значению ДАТАВРЕМЯ(1, 1, 1) абсолютно не требуется.
Рассмотрим подробнее влияние логического оператора ИЛИ на производительность запросов. Базы данных под управлением современных СУБД (MS SQL, PostgreSQL и др.) строят планы выполнения запросов на основе имеющихся индексов. Поле даты (например, дата документа или договора) в 1С практически всегда является индексированным.
Если мы используем простое условие сравнения:
ГДЕ Договор.ДатаЗаключения < &ДатаПараметр
СУБД быстро находит нужные записи, используя поиск по индексу (Index Seek). Но как только в секции ГДЕ появляется логическое условие ИЛИ для одного и того же поля, оптимизатор базы данных может отказаться от использования индекса и выполнить полное сканирование таблицы (Table Scan / Index Scan). На больших объемах данных это гарантированно приведет к существенному замедлению работы системы и потенциальным блокировкам таблиц. Отказ от избыточного ИЛИ позволяет базе данных отрабатывать запрос максимально быстро.
Разберем по шагам ситуацию, когда простое условие сравнения дат может привести к потере данных. Это происходит, когда мы соединяем таблицы с помощью левого соединения (ЛЕВОЕ СОЕДИНЕНИЕ).
Представим сценарий: мы выбираем всех контрагентов и хотим присоединить к ним договоры, дата заключения которых меньше определенной даты (или пустая). Если у какого-то контрагента вообще нет ни одного договора, то в результате левого соединения поле Договор.ДатаЗаключения примет системное значение NULL (означающее полное отсутствие значения, что не эквивалентно пустой дате 1С).
Если применить обычное условие:
ГДЕ Договор.ДатаЗаключения < &ДатаПараметр
то все строки, где дата равна NULL, будут отсеяны, так как любое сравнение значения с NULL возвращает ложь. Контрагенты без договоров просто исчезнут из выборки. Для корректной обработки таких ситуаций мы должны использовать функцию ЕСТЬNULL, заменяя отсутствующие значения на пустую дату 1С:
ГДЕ ЕСТЬNULL(Договор.ДатаЗаключения, ДАТАВРЕМЯ(1, 1, 1)) < &ДатаПараметр
В данном случае, если договора физически нет, функция ЕСТЬNULL подставит вместо значения NULL дату 01.01.0001. Она успешно пройдет проверку «меньше» и строка с контрагентом попадет в результат отчета.
Посмотрим на пример, который иногда ошибочно предлагают для решения подобных задач:
ГДЕ Договор.ДатаЗаключения В(&ДатаПараметр, ДАТАВРЕМЯ(1, 1, 1))
Выясним причину, почему данный вариант не подходит. Оператор В (в SQL преобразуемый в оператор списка IN) осуществляет проверку на строгое соответствие указанным элементам. Это означает, что запрос выберет только те договоры, дата которых *точно равна* переданному параметру (например, строго равна 01.01.2017) или *точно равна* пустой дате. Все реальные даты договоров в промежутке между этими точками (например, за 2015 или 2016 годы) будут полностью проигнорированы. Оператор В не предназначен для фильтрации интервалов и диапазонов значений.
Хотя конструкция ДАТАВРЕМЯ(1, 1, 1) полностью поддерживается внутри текста запросов, стандарты разработки платформы «1С:Предприятие 8» рекомендуют передавать любые граничные или пустые значения извне в качестве параметров. Это делает код более гибким и читаемым.
Ниже представлен пример правильной реализации запроса в модуле конфигурации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Договоры.Ссылка КАК Договор,
| Договоры.ДатаЗаключения КАК ДатаЗаключения
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
| Договоры.ДатаЗаключения < &ДатаПараметр";
// Передаем дату границы отбора
Запрос.УстановитьПараметр("ДатаПараметр", Дата(2017, 1, 1));
РезультатЗапроса = Запрос.Выполнить();
Если же логика задачи требует обязательной явной проверки именно на пустое значение отдельно от основного условия (например, при сложных вложенных условиях или специфических группировках данных), передавайте пустую дату в параметрах аналогичным образом:
Запрос.УстановитьПараметр("ПустаяДата", Дата(1, 1, 1));
Соблюдение этих простых правил логического сравнения типов данных и оптимизации СУБД позволит вам писать чистый, быстродействующий и отказоустойчивый код запросов в системе «1С:Предприятие 8».