При разработке сложных отчетов или механизмов анализа данных в 1С часто возникает ситуация, когда в одной строке результата запроса есть несколько колонок с датами (например, дата последнего заказа, дата последней оплаты, дата последнего звонка и так далее), и нам необходимо вычислить самую позднюю из них. В стандартном SQL для этих целей иногда существует функция GREATEST, однако в языке запросов 1С такой функции «из коробки» нет. Функция МАКСИМУМ() в 1С является агрегатной и работает по вертикали (выбирает максимум из одной колонки по всем строкам в группе), а не по горизонтали.
В этой статье мы разберем несколько эффективных способов решения этой задачи, проанализируем их преимущества и недостатки, а также выясним, как избежать типичных ошибок, связанных с пустыми значениями дат.
Этот метод считается наиболее архитектурно правильным и масштабируемым. Его суть заключается в том, чтобы «развернуть» пять горизонтальных колонок в пять вертикальных строк, а затем применить стандартную агрегатную функцию МАКСИМУМ() с группировкой по ключевому полю (идентификатору записи).
Рассмотрим этот процесс по шагам. Предположим, у нас есть таблица, где для каждого контрагента указано пять различных дат событий, и нам нужно найти дату самого последнего события.
Для начала подготовим временную таблицу, в которую поместим все наши даты, объединив их в одну колонку. Не забудем сохранить ссылку на основной объект (например, Контрагент), чтобы потом «схлопнуть» данные обратно.
// Шаг 1: Собираем все даты в одну временную таблицу
ВЫБРАТЬ
Т.Контрагент КАК Идентификатор,
Т.ДатаСобытия1 КАК ДатаДляСравнения
ПОМЕСТИТЬ ВТ_ВсеДаты
ИЗ
Справочник.Контрагенты КАК Т
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Т.Контрагент,
Т.ДатаСобытия2
ИЗ
Справочник.Контрагенты КАК Т
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Т.Контрагент,
Т.ДатаСобытия3
ИЗ
Справочник.Контрагенты КАК Т
// ... и так далее для всех пяти колонок
;
// Шаг 2: Получаем максимум через группировку
ВЫБРАТЬ
ВТ.Идентификатор КАК Контрагент,
МАКСИМУМ(ВТ.ДатаДляСравнения) КАК МаксимальнаяДата
ИЗ
ВТ_ВсеДаты КАК ВТ
СГРУППИРОВАТЬ ПО
ВТ.Идентификатор
Преимущества данного метода:
ОБЪЕДИНИТЬ ВСЕ.МАКСИМУМ() автоматически игнорирует значения NULL, что избавляет нас от лишних проверок.ВТ_ВсеДаты, чтобы убедиться в правильности данных.Если по каким-то причинам использование временных таблиц нежелательно (например, внутри очень простого запроса в динамическом списке), можно прибегнуть к сравнению «в лоб» через ВЫБОР. Однако здесь кроется ловушка: прямое сравнение пяти значений сразу невозможно, поэтому нам придется строить вложенную логику.
Разберем пример последовательного сравнения. Сначала сравним первую и вторую дату, результат сравним с третьей и так далее. Проанализируем, как это выглядит на практике:
ВЫБРАТЬ
ВЫБОР
КОГДА Дата1 >= Дата2 И Дата1 >= Дата3 И Дата1 >= Дата4 И Дата1 >= Дата5 ТОГДА Дата1
КОГДА Дата2 >= Дата1 И Дата2 >= Дата3 И Дата2 >= Дата4 И Дата2 >= Дата5 ТОГДА Дата2
КОГДА Дата3 >= Дата1 И Дата3 >= Дата2 И Дата3 >= Дата4 И Дата3 >= Дата5 ТОГДА Дата3
КОГДА Дата4 >= Дата1 И Дата4 >= Дата2 И Дата4 >= Дата3 И Дата4 >= Дата5 ТОГДА Дата4
ИНАЧЕ Дата5
КОНЕЦ КАК МаксимальнаяДата
ИЗ
Справочник.Контрагенты
Важное замечание: такая конструкция очень громоздка и трудночитаема. Более того, при работе с типом Дата в 1С крайне важно учитывать наличие пустых дат. В языке запросов 1С NULL не равен пустой дате (01.01.0001), и результат сравнения с NULL может привести к непредсказуемым результатам.
При поиске максимума из нескольких колонок мы должны гарантировать, что пустые поля не «задавят» полезные данные. Выясним причину, почему это важно: если одна из колонок содержит NULL, а мы используем ВЫБОР КОГДА без предварительной обработки, все условие может вернуть ЛОЖЬ.
Чтобы избежать этой проблемы, используйте функцию ЕСТЬNULL() или ВЫБОР для замены NULL на минимально возможную дату (01.01.0001). Посмотрим на пример безопасного сравнения:
ВЫБРАТЬ
ЕСТЬNULL(Т.Дата1, ДАТАВРЕМЯ(1, 1, 1)) КАК Дата1_Испр,
// ... обработка остальных полей
ПОМЕСТИТЬ ВТ_ПодготовленныеДанные
ИЗ
Источник КАК Т
Помните: агрегатная функция МАКСИМУМ() (из первого способа) сама отсекает NULL, но она не отсекает пустые даты (01.01.0001). Если все пять дат в строке будут пустыми, результатом будет пустая дата, что логично.
Если ваш запрос обрабатывает сотни тысяч строк, метод с ОБЪЕДИНИТЬ ВСЕ может создать значительную нагрузку на сервер СУБД, так как фактически количество строк увеличивается в 5 раз. В таких ситуациях проанализируем следующие рекомендации:
Идентификатор (или Ссылка) перед выполнением группировки. Это ускорит операцию СГРУППИРОВАТЬ ПО.Если вам не нужно сортировать результат запроса по этой максимальной дате или использовать её в условиях отбора в этом же запросе, рассмотрим вариант вычисления в коде 1С. Это особенно удобно, если результат выводится в форму или используется для последующих расчетов.
// Пример вычисления максимума при обходе выборки
Пока Выборка.Следующий() Цикл
МассивДат = Новый Массив;
МассивДат.Добавить(Выборка.Дата1);
МассивДат.Добавить(Выборка.Дата2);
МассивДат.Добавить(Выборка.Дата3);
МассивДат.Добавить(Выборка.Дата4);
МассивДат.Добавить(Выборка.Дата5);
МаксДата = '00010101';
Для Каждого ТекущаяДата Из МассивДат Цикл
Если ТекущаяДата > МаксДата Тогда
МаксДата = ТекущаяДата;
КонецЕсли;
КонецЦикла;
// Используем МаксДата...
КонецЦикла;
Подведем итог: для большинства задач в 1С наиболее надежным и гибким способом получения максимума из колонок является использование временной таблицы с оператором ОБЪЕДИНИТЬ ВСЕ и последующей группировкой с функцией МАКСИМУМ(). Это решение обеспечивает чистоту кода и корректную работу с данными. Для проверки и отладки таких пакетных конструкций с временными таблицами можно использовать консоль запросов для управляемых форм с возможностью отладки.