К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как в запросе 1С выбрать максимальную дату

Как выбрать максимальную дату в запросе 1С: функция МАКСИМУМ, группировка, сортировка с ПЕРВЫЕ 1, типовые ошибки и проверка результата.

Задача выбрать максимальную дату в запросе 1С возникает постоянно: найти последний документ по контрагенту, определить дату последней продажи товара, получить актуальную цену из периодического регистра сведений. Язык запросов 1С предлагает для этого два основных подхода — агрегатную функцию МАКСИМУМ и сортировку по убыванию с ограничением ПЕРВЫЕ 1. У каждого подхода своя область применения и свои подводные камни.

Ключевое различие простое: МАКСИМУМ(Дата) возвращает только само значение даты (возможно, в разрезе группировок), а сортировка с ПЕРВЫЕ 1 позволяет получить всю строку целиком — документ, сумму, ответственного и любые другие поля записи с максимальной датой. Выбор зависит от того, что именно нужно дальше по логике программы.

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

Выбор максимальной даты функцией МАКСИМУМ

Агрегатная функция МАКСИМУМ — самый прямой способ. Она работает с любыми сравнимыми типами, включая дату, и возвращает наибольшее значение по выборке или по группе. Минимальный пример — найти дату последнего документа реализации по всей базе:

ВЫБРАТЬ

МАКСИМУМ(РеализацияТоваровУслуг.Дата) КАК ПоследняяДата

ИЗ

Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг

Запрос вернёт одну строку с одним полем. Если в таблице нет ни одной записи, результат будет содержать NULL — это важно учитывать при обработке в коде, иначе сравнение или присвоение может дать неожиданный результат.

Чаще требуется максимальная дата не по всей таблице, а в разрезе какого-то измерения — например, последняя продажа по каждому контрагенту. Тогда добавляется группировка:

ВЫБРАТЬ

РеализацияТоваровУслуг.Контрагент КАК Контрагент,

МАКСИМУМ(РеализацияТоваровУслуг.Дата) КАК ПоследняяДата

ИЗ

Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг

СГРУППИРОВАТЬ ПО

РеализацияТоваровУслуг.Контрагент

Получение всей записи с максимальной датой через ПЕРВЫЕ 1

Функция МАКСИМУМ отдаёт только дату. Но нередко нужен сам документ с последней датой, его номер, сумма или склад. В этом случае используют сортировку по убыванию и ограничение количества строк:

ВЫБРАТЬ ПЕРВЫЕ 1

РеализацияТоваровУслуг.Ссылка КАК Документ,

РеализацияТоваровУслуг.Дата КАК Дата,

РеализацияТоваровУслуг.СуммаДокумента КАК Сумма

ИЗ

Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг

ГДЕ

РеализацияТоваровУслуг.Контрагент = &Контрагент

УПОРЯДОЧИТЬ ПО

Дата УБЫВ

Здесь есть тонкость: у нескольких документов может быть одинаковая дата с точностью до секунды. Тогда «первый» из них определяется фактически случайно, и результат может меняться от выполнения к выполнению. Чтобы выбор был стабильным, добавляют второй критерий сортировки — например, ссылку:

УПОРЯДОЧИТЬ ПО

Дата УБЫВ,

Ссылка УБЫВ

Для документов вместо сортировки по ссылке корректнее использовать момент времени — он однозначно упорядочивает документы внутри одной секунды:

УПОРЯДОЧИТЬ ПО

РеализацияТоваровУслуг.МоментВремени УБЫВ

Сравнение вариантов

МАКСИМУМ(Дата)

Подходит, когда нужно только значение даты или дата в разрезе групп. Не возвращает остальные поля записи.

ПЕРВЫЕ 1 + УПОРЯДОЧИТЬ УБЫВ

Подходит, когда нужна вся строка с последней датой. Требует дополнительного критерия сортировки при совпадающих датах.

СрезПоследних

Только для периодических регистров сведений. Самый быстрый и правильный способ получить актуальные значения на дату.

Максимальная дата в периодических регистрах сведений

Для периодических регистров сведений (курсы валют, цены номенклатуры, ответственные лица) изобретать запрос с МАКСИМУМ не нужно — платформа предоставляет виртуальную таблицу СрезПоследних. Она возвращает записи с максимальным периодом на указанную дату по каждому сочетанию измерений:

ВЫБРАТЬ

ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,

ЦеныНоменклатурыСрезПоследних.Период КАК Период,

ЦеныНоменклатурыСрезПоследних.Цена КАК Цена

ИЗ

РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатурыСрезПоследних

Если параметр &Период не задан, срез строится на текущую дату. Вторым параметром передаётся условие отбора — например, по типу цен. Фильтровать лучше именно через параметр виртуальной таблицы, а не через ГДЕ: так срез рассчитывается только по нужным записям, что заметно быстрее на больших регистрах.

Срез последних эквивалентен группировке с МАКСИМУМ(Период) и последующему соединению с исходной таблицей, но выполняется оптимальнее и читается проще. Ручной вариант с МАКСИМУМ оправдан только в нестандартных сценариях — например, когда нужен срез не по всем измерениям сразу.

Порядок действий для периодического регистра
  1. 1
    Откройте конструктор запроса и выберите виртуальную таблицу СрезПоследних нужного регистра
  2. 2
    Передайте дату среза параметром &Период или оставьте пустым для среза на текущую дату
  3. 3
    Добавьте условие отбора вторым параметром виртуальной таблицы, а не в секции ГДЕ
  4. 4
    Выберите нужные ресурсы и измерения, выполните запрос и проверьте поле Период в результате

Максимальная дата с условиями и соединениями

На практике запрос редко ограничивается одной таблицей. Типовой сценарий — найти дату последней продажи по каждому товару и присоединить к ней реквизиты номенклатуры. Здесь удобно использовать вложенный запрос или временную таблицу:

ВЫБРАТЬ

Продажи.Номенклатура КАК Номенклатура,

МАКСИМУМ(Продажи.Период) КАК ПоследняяПродажа

ПОМЕСТИТЬ ВТ_ПоследниеПродажи

ИЗ

РегистрНакопления.Продажи.Обороты(, , День, ) КАК Продажи

СГРУППИРОВАТЬ ПО

Продажи.Номенклатура

;

ВЫБРАТЬ

Номенклатура.Наименование КАК Товар,

ВТ_ПоследниеПродажи.ПоследняяПродажа КАК ДатаПоследнейПродажи

ИЗ

ВТ_ПоследниеПродажи КАК ВТ_ПоследниеПродажи

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура

ПО ВТ_ПоследниеПродажи.Номенклатура = Номенклатура.Ссылка

При соединениях помните: если основная таблица — справочник, а данные о датах присоединяются, то для товаров без продаж поле даты будет NULL. Чтобы не обрабатывать NULL в коде, оберните поле в ЕСТЬNULL(ПоследняяПродажа, ДАТАВРЕМЯ(1,1,1)) — пустая дата дальше легко фильтруется и сравнивается.

Условия отбора (ГДЕ) применяйте до группировки, если они относятся к строкам таблицы, и через ИМЕЮЩИЕ, если условие накладывается на результат агрегации — например, «только контрагенты, у которых последняя продажа была раньше заданной даты»:

ИМЕЮЩИЕ

МАКСИМУМ(РеализацияТоваровУслуг.Дата) < &ГраницаДаты

Конструкция ИМЕЮЩИЕ с МАКСИМУМ — удобный способ найти «забытых» контрагентов или залежавшийся товар: отберите записи, у которых максимальная дата старше заданного порога, и получите готовый список для обзвона или распродажи.
ПЗ
Практическое замечание

Проверка результата и типичные ошибки

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

Отдельно проверьте граничные случаи: пустая таблица, единственная запись, несколько записей с одинаковой датой. Именно на этих случаях запросы с максимальной датой ломаются чаще всего — особенно ПЕРВЫЕ 1 без дополнительного критерия сортировки.

Типичные ошибки
01
Использование МАКСИМУМ и попытка выбрать рядом неагрегированные поля — запрос не выполнится или вернёт несвязанные данные
02
Сортировка только по дате без момента времени или ссылки — при одинаковых датах результат нестабилен
03
Отбор по измерениям среза последних через ГДЕ вместо параметра виртуальной таблицы — резкое падение скорости на больших регистрах
04
Игнорирование NULL в результате МАКСИМУМ по пустой таблице — ошибки сравнения и приведения типов в коде
05
Сравнение даты документа с параметром без учёта времени — документы за граничный день выпадают из выборки

Ещё одна частая проблема — производительность. Группировка по большой таблице документов без индексированного отбора может выполняться долго. Если запрос тормозит, сначала сократите выборку условием ГДЕ (период, организация, склад), а уже потом считайте МАКСИМУМ по оставшимся строкам.

Контрольные точки

Как проверить результат

Количество строк результата
Норма

Одна строка без группировки либо по строке на каждое значение группировки

Тревожный признак

Пустой результат при заведомо заполненной таблице — проверьте условия ГДЕ

Значение максимальной даты
Норма

Совпадает с датой последнего документа при ручной сортировке списка

Тревожный признак

NULL или дата старше ожидаемой — проверьте отборы и права доступа (RLS)

Стабильность ПЕРВЫЕ 1
Норма

Повторные выполнения дают одну и ту же запись

Тревожный признак

Запись меняется — добавьте сортировку по моменту времени или ссылке

Обработка результата в коде модуля

После выполнения запроса результат обычно обрабатывают через Выбрать() или Выгрузить(). Для запроса с МАКСИМУМ без группировки гарантированно возвращается одна строка, поэтому можно смело обращаться к ней без цикла:

Результат = Запрос.Выполнить().Выбрать();

Если Результат.Следующий() Тогда

ПоследняяДата = Результат.ПоследняяДата;

Если ПоследняяДата = NULL Тогда

ПоследняяДата = Дата(1, 1, 1);

КонецЕсли;

КонецЕсли;

Для запроса с ПЕРВЫЕ 1 выборка может оказаться пустой — метод Следующий() вернёт Ложь, и это нужно обработать, иначе обращение к полям выборки вызовет ошибку. Если запрос используется в цикле по многим объектам, вынесите его за пределы цикла: посчитайте максимальные даты одним запросом с группировкой, выгрузите в таблицу значений и ищите по ней через Найти(). Запрос в цикле — классическая причина медленных обработок.

Частые вопросы

Чем МАКСИМУМ отличается от ПЕРВЫЕ 1 с сортировкой по убыванию?

МАКСИМУМ — агрегатная функция, возвращающая только значение даты, причём её можно группировать по измерениям. ПЕРВЫЕ 1 с сортировкой УБЫВ возвращает целую строку со всеми полями, но не работает в разрезе групп без дополнительных ухищрений. Если нужна только дата — берите МАКСИМУМ, если нужна запись — сортировку.

Почему МАКСИМУМ возвращает NULL, хотя документы в базе есть?

Чаще всего срабатывает условие в секции ГДЕ, которое отсекает все записи, либо ограничение доступа на уровне записей (RLS) скрывает документы от текущего пользователя. Проверьте запрос без условий и под пользователем с полными правами.

Как получить максимальную дату по каждому документу из табличной части?

Сгруппируйте по ссылке на документ: ВЫБРАТЬ Ссылка, МАКСИМУМ(ДатаПоле) ИЗ Документ.ИмяДокумента.ИмяТабличнойЧасти СГРУППИРОВАТЬ ПО Ссылка. Табличные части доступны в запросах как отдельные таблицы через точку.

Как учесть, что дата документа содержит время?

При сравнении с границей дня используйте функции НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) и КОНЕЦПЕРИОДА(Дата, ДЕНЬ) либо сравнивайте с границей вида < ДатаСледующегоДня. Иначе документы, созданные в течение граничного дня после полуночи, выпадут из выборки.

Что быстрее: МАКСИМУМ или сортировка с ПЕРВЫЕ 1?

На индексированном поле даты сортировка с ПЕРВЫЕ 1 обычно быстрее, так как СУБД может взять одну запись по индексу без полного сканирования. МАКСИМУМ без группировки тоже оптимизируется, но с группировкой требует обработки всех строк. На больших таблицах измеряйте оба варианта на реальных данных.

Как выбрать максимальную дату из двух разных полей одной записи?

Используйте конструкцию ВЫБОР: ВЫБОР КОГДА Дата1 > Дата2 ТОГДА Дата1 ИНАЧЕ Дата2 КОНЕЦ. Функции МАКСИМУМ для двух полей одной строки в языке запросов 1С нет — она работает только по множеству строк.

Итог

Что важно запомнить

Для значения максимальной даты используйте МАКСИМУМ с группировкой по нужным измерениям, для получения всей записи — сортировку по убыванию с ПЕРВЫЕ 1 и дополнительным критерием порядка. Для периодических регистров сведений применяйте СрезПоследних с отбором в параметрах. Всегда обрабатывайте NULL и проверяйте запрос на пустых и граничных данных до внедрения в рабочий код.