В практике разработки на платформе 1С программисты часто сталкиваются с ситуацией, когда стандартная функция РАЗНОСТЬДАТ в запросе отказывается выводить результат или возвращает пустое поле, хотя на первый взгляд все параметры переданы верно. Особенно часто это проявляется при использовании параметров сеанса или внешних параметров в сочетании с левыми соединениями. Разберем эту ситуацию подробно на примере задачи по расчету срока работы по заявкам на подбор персонала.
Рассмотрим типичную ситуацию. Нам необходимо вычислить количество дней, в течение которых заявка находилась в работе. Логика запроса проста: если дата закрытия заявки не заполнена (пустая), мы считаем разницу между датой открытия и концом периода отчета. Если дата закрытия есть — считаем разницу между открытием и закрытием.
Проанализируем фрагмент кода, который часто вызывает затруднения:
ВЫБОР
КОГДА СтатусыДатаЗакрытия.Период = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
ТОГДА РАЗНОСТЬДАТ(СтатусыДатаОткрытия.Period, &КонПериода, ДЕНЬ)
ИНАЧЕ РАЗНОСТЬДАТ(СтатусыДатаОткрытия.Period, СтатусыДатаЗакрытия.Period, ДЕНЬ)
КОНЕЦ
Выясним причину, по которой расчет может выдавать пустой результат. Основная загвоздка в 90% случаев кроется в использовании ЛЕВОГО СОЕДИНЕНИЯ. Если в присоединяемой таблице (например, по статусам «Открыта» или «Закрыта») нет соответствующей записи, то поле Период будет содержать значение NULL.
Согласно правилам работы SQL и движка запросов 1С, любая математическая операция или функция (включая РАЗНОСТЬДАТ), в которую попадает NULL, возвращает NULL. В отчетах СКД это выглядит как абсолютно пустая ячейка, что и вводит разработчика в заблуждение.
Для того чтобы расчет стал корректным, необходимо гарантировать, что в функцию РАЗНОСТЬДАТ попадают только значения типа Дата. Мы должны обернуть каждое поле, которое может быть потенциально пустым из-за левого соединения, в функцию ЕСТЬNULL. Рассмотрим, как должен выглядеть исправленный блок запроса:
ВЫБОР
КОГДА ЕСТЬNULL(СтатусыДатаЗакрытия.Период, ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)) = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
ТОГДА РАЗНОСТЬДАТ(ЕСТЬNULL(СтатусыДатаОткрытия.Период, ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)), &КонПериода, ДЕНЬ)
ИНАЧЕ РАЗНОСТЬДАТ(ЕСТЬNULL(СтатусыДатаОткрытия.Период, ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)), СтатусыДатаЗакрытия.Период, ДЕНЬ)
КОНЕЦ КАК КоличествоДнейВРаботе
В данном примере мы принудительно заменяем NULL на пустую дату 01.01.0001. Это позволяет функции РАЗНОСТЬДАТ отработать без ошибки, хотя результат с пустой датой может быть огромным. Поэтому рекомендуется также добавлять дополнительную проверку на наличие даты открытия.
Если после обработки NULL проблема сохраняется, проанализируем сам параметр &КонПериода. В 1С существует объект типа Граница, который часто используется для получения остатков на конец периода (включая или исключая последнюю секунду). Однако функция РАЗНОСТЬДАТ крайне чувствительна к типам данных и ожидает строго тип Дата.
Важно: Если вы передаете в запрос Границу, функция не «поймет» это значение. Чтобы избежать ошибки, при установке параметров в коде программно передавайте значение границы:
// Неправильно:
Запрос.УстановитьПараметр("КонПериода", ГраницаПериода);
// Правильно:
Запрос.УстановитьПараметр("КонПериода", ГраницаПериода.Значение);
Обратите внимание на структуру вашего запроса. Если вы используете конструкцию ВЫБОР...КОНЕЦ в списке полей и при этом делаете группировку (СГРУППИРОВАТЬ ПО), вам необходимо дублировать всё выражение ВЫБОР в секции группировки. Если в одном месте вы обработали NULL, а в другом — нет, возникнет ошибка несоответствия полей или неверный расчет.
Проанализируем правильный подход к группировке сложных вычислений:
РАЗНОСТЬДАТ во вложенный запрос или временную таблицу.ВЫБОР в СГРУППИРОВАТЬ ПО.Разберем подробнее, как именно 1С считает разницу. Функция РАЗНОСТЬДАТ считает не фактическую длительность времени, а количество пересечений границ периодов. Рассмотрим примеры для параметра МЕСЯЦ:
При расчете дней (ДЕНЬ) ситуация аналогична: учитывается разница в календарных днях без учета времени, если только время не влияет на переход через полночь. Если вам нужна высокая точность, всегда проверяйте, не обрезается ли время в ваших исходных данных.
Посмотрим на ситуацию со стороны производительности СУБД. Использование РАЗНОСТЬДАТ в условиях фильтрации (в секции ГДЕ) крайне нежелательно. Если вы напишете ГДЕ РАЗНОСТЬДАТ(ДатаОткрытия, &ТекущаяДата, ДЕНЬ) > 10, система не сможет использовать индексы по полю ДатаОткрытия.
Рекомендуемый подход: перенесите вычисление на сторону сравнения даты. Вместо вычисления разницы, прибавьте дни к искомой дате:
// Вместо РАЗНОСТЬДАТ(...) > 10 используйте:
ГДЕ ДатаОткрытия < ДОБАВИТЬКДАТЕ(&ТекущаяДата, ДЕНЬ, -10)
Такой запрос отработает значительно быстрее на больших объемах данных, так как СУБД выполнит поиск по индексу (Index Seek), а не полное сканирование таблицы (Full Scan).
Подводя итог: если РАЗНОСТЬДАТ «не работает», первым делом проверяйте наличие NULL через ЕСТЬNULL и убедитесь, что параметры передаются в типе Дата, а не Граница или МоментВремени. Совместное использование этих правил гарантирует стабильную работу ваших отчетов.