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