Разработка и Конфигурация · 12 мин
Как выбрать последний документ в запросе 1С
Как выбрать последний документ в запросе 1С: ПЕРВЫЕ 1 с сортировкой УБЫВ, МАКСИМУМ по дате, срез последних. Примеры кода, ошибки и проверка результата.
Задача получить последний документ — одна из самых частых при разработке на платформе 1С:Предприятие. Она возникает при поиске последнего поступления товара, последнего приказа сотрудника, последнего установленного ценообразованием документа или последней операции по контрагенту. Способов несколько, и выбор зависит от того, нужен один документ по всей базе, последний документ по каждому объекту (контрагенту, номенклатуре, сотруднику) или последний по состоянию на конкретную дату.
Ключевые инструменты языка запросов для этой задачи: конструкция ПЕРВЫЕ 1 в сочетании с сортировкой УПОРЯДОЧИТЬ ПО ... УБЫВ, агрегатная функция МАКСИМУМ() по дате или моменту времени, а для периодических регистров сведений — виртуальная таблица СрезПоследних. Каждый из вариантов имеет свои ограничения и подводные камни, например неоднозначность при одинаковых датах документов.
Ниже разобраны рабочие примеры запросов с пояснениями, типичные ошибки и способы проверить, что запрос действительно возвращает нужный документ. Примеры даны на встроенном языке запросов 1С и применимы к типовым и самописным конфигурациям.
Что считать «последним» документом: дата, момент времени, номер
Прежде чем писать запрос, определите критерий «последности». У документа в 1С есть несколько кандидатов на эту роль: реквизит Дата, стандартный реквизит МоментВремени и Номер. Они не эквивалентны, и путаница между ними — частый источник ошибок.
Поле Дата хранит дату и время с точностью до секунды. Два документа, созданные в одну секунду или вручную с одинаковой датой, по этому полю неразличимы. МоментВремени — это составное значение (дата плюс ссылка на документ), которое гарантированно уникально и отражает реальную последовательность документов внутри одной секунды. Именно по нему платформа упорядочивает документы при проведении.
Поле Номер использовать как критерий последности не рекомендуется: нумерация может сбиваться при перенумерации, обменах, ручном изменении номера и смене префиксов. Номер — это представление для пользователя, а не технический порядок.
Способ 1. ПЕРВЫЕ 1 с сортировкой по убыванию
Самый простой вариант, когда нужен один последний документ по всей таблице или по отбору. Идея: отсортировать документы по дате в обратном порядке и взять первую строку результата.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| РеализацияТоваровУслуг.Ссылка КАК Ссылка,
| РеализацияТоваровУслуг.Дата КАК Дата
|ИЗ
| Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
|ГДЕ
| РеализацияТоваровУслуг.Контрагент = &Контрагент
| И РеализацияТоваровУслуг.Проведен
|
|УПОРЯДОЧИТЬ ПО
| Дата УБЫВ";
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Результат = Запрос.Выполнить().Выбрать();
Если Результат.Следующий() Тогда
ПоследнийДокумент = Результат.Ссылка;
КонецЕсли;
Обратите внимание на отбор Проведен. Если нужны только проведённые документы, условие обязательно — иначе запрос может вернуть помеченный на удаление или непроведённый документ, созданный позже. Часто дополнительно исключают пометку удаления: И НЕ РеализацияТоваровУслуг.ПометкаУдаления.
Если в базе возможны документы с одинаковой датой, добавьте в сортировку второй уровень — по ссылке или по моменту времени. Это делает выбор детерминированным: при каждом выполнении запрос вернёт один и тот же документ, а не случайный из «одинаковых».
Способ 2. МАКСИМУМ по дате с группировкой
Когда нужен последний документ по каждому объекту — например, последняя реализация по каждому контрагенту или последнее поступление по каждой номенклатуре — конструкция ПЕРВЫЕ 1 не подходит. Здесь работает связка МАКСИМУМ(Дата) с группировкой и последующим соединением.
ВЫБРАТЬ
Реализации.Контрагент КАК Контрагент,
МАКСИМУМ(Реализации.Дата) КАК ПоследняяДата
ПОМЕСТИТЬ МаксимальныеДаты
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализации
ГДЕ
Реализации.Проведен
СГРУППИРОВАТЬ ПО
Реализации.Контрагент
;
ВЫБРАТЬ
Реализации.Контрагент КАК Контрагент,
Реализации.Ссылка КАК ПоследнийДокумент
ИЗ
МаксимальныеДаты КАК МаксимальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализации
ПО МаксимальныеДаты.Контрагент = Реализации.Контрагент
И МаксимальныеДаты.ПоследняяДата = Реализации.Дата
У этого способа есть известная особенность: если у одного контрагента два документа с одинаковой максимальной датой, соединение вернёт оба. Для большинства отчётных задач это приемлемо или даже желательно, но если нужна строго одна строка на объект, добавьте вложенный запрос или дополнительную группировку с МАКСИМУМ(Ссылка).
Способ 3. Срез последних для регистров сведений
Если «последний документ» нужен как регистратор, установивший актуальное значение в периодическом регистре сведений (цены, курсы валют, кадровые данные), не ищите его в таблице документов вручную. Используйте виртуальную таблицу СрезПоследних — она уже содержит актуальные записи и их регистраторов.
ВЫБРАТЬ
Срез.Номенклатура КАК Номенклатура,
Срез.Цена КАК Цена,
Срез.Регистратор КАК ДокументУстановки
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаСреза, ) КАК Срез
Параметр &ДатаСреза позволяет получить состояние на любой момент в прошлом, а поле Регистратор — это и есть последний документ, изменивший значение. Это самый производительный и корректный путь для периодических данных: платформа использует индексы регистра, а не полный перебор таблицы документов.
Последний документ на заданную дату
Частая вариация задачи — найти последний документ не «вообще», а на конец прошлого периода: например, последний приказ сотрудника на конец квартала. Для этого добавьте условие по дате в отбор, остальная логика не меняется.
ВЫБРАТЬ ПЕРВЫЕ 1
Приказы.Ссылка КАК Ссылка
ИЗ
Документ.КадровыйПриказ КАК Приказы
ГДЕ
Приказы.Сотрудник = &Сотрудник
И Приказы.Дата <= &КонецПериода
И Приказы.Проведен
УПОРЯДОЧИТЬ ПО
Приказы.Дата УБЫВ
Помните о границах периода: если дата документа содержит время, условие Дата <= &КонецПериода с параметром-полуночью отсечёт документы, созданные в течение последнего дня. Используйте функцию КонецДня() при формировании параметра или условие Дата < &НачалоСледующегоДня.
Как выбрать действие
Производительность и проверка результата
Запрос с ПЕРВЫЕ 1 и сортировкой по убыванию обычно работает быстро, если по полю даты есть индекс (для документов он есть по умолчанию). Проблемы начинаются, когда в сортировку или отбор попадают вычисляемые выражения, функции над полями или соединения без индексов — тогда СУБД вынуждена перебирать всю таблицу.
Для способа с МАКСИМУМ и соединением по дате оценивайте объём таблицы документов: на больших базах (сотни тысяч документов одного вида) такой запрос может выполняться заметное время. Временная таблица с максимальными датами должна быть проиндексирована — добавьте ИНДЕКСИРОВАТЬ ПО Контрагент при помещении во временную таблицу.
Проверить результат проще всего в консоли запросов: выполните запрос, затем откройте журнал документов с тем же отбором, отсортируйте по дате по убыванию и сравните первую строку со ссылкой из запроса. Дополнительно проверьте поведение на объекте, у которого документов нет вообще: запрос должен вернуть пустой результат, а код — корректно обработать ситуацию через Если Выборка.Следующий().
Как проверить результат
Ссылка из запроса совпадает с первой строкой журнала, отсортированного по дате по убыванию
Запрос возвращает более ранний документ или документ другого объекта
Выборка пустая, код не вызывает исключение
Ошибка обращения к полю или необработанное пустое значение
Запрос стабильно возвращает один и тот же документ при повторных выполнениях
Результат меняется от выполнения к выполнению
Помеченные на удаление и непроведённые документы не попадают в результат
Последним считается помеченный на удаление документ
Типичные ошибки и ограничения
Самая распространённая ошибка — забыть сортировку УБЫВ. Запрос с ПЕРВЫЕ 1 и сортировкой по возрастанию вернёт не последний, а первый документ, и при небольшом объёме тестовых данных это легко не заметить. Вторая классическая ошибка — отсутствие отбора по проведённости и пометке удаления, из-за чего «последним» оказывается технически существующий, но недействительный документ.
Ещё одна группа проблем связана с датами: сравнение без учёта времени внутри даты, использование номера вместо даты, игнорирование часовых поясов в распределённых базах. В базах с обменами документ может прийти «задним числом» с датой раньше уже существующих — сортировка по дате корректно поместит его в историю, но если ваша логика опиралась на порядок физического создания, результат может отличаться от ожиданий.
Наконец, не используйте обход таблицы документов в цикле со сравнением дат в коде вместо запроса — это антипаттерн, который на больших объёмах работает на порядки медленнее и нагружает сервер. Любая из описанных конструкций запроса решает задачу одним обращением к базе.
Что важно запомнить
Для одного последнего документа используйте ПЕРВЫЕ 1 с сортировкой по дате УБЫВ, для последнего по каждому объекту — МАКСИМУМ(Дата) с группировкой и соединением, для периодических регистров — СрезПоследних. Всегда фильтруйте по проведённости и пометке удаления, учитывайте время в датах и проверяйте результат на объектах без документов.
Частые вопросы
Как получить последний документ по каждому контрагенту одним запросом?
Сгруппируйте таблицу документов по контрагенту с функцией МАКСИМУМ(Дата), поместите результат во временную таблицу, затем соедините её с таблицей документов по контрагенту и дате. Если возможны совпадающие даты, добавьте второй уровень через МАКСИМУМ(Ссылка), чтобы получить строго один документ на контрагента.
Чем МоментВремени лучше поля Дата для определения последнего документа?
МоментВремени объединяет дату и ссылку, поэтому уникален для каждого документа. Два документа с одинаковой датой по полю Дата неразличимы, а по МоментВремени — всегда упорядочены однозначно. Это исключает нестабильные результаты при совпадающих датах.
Почему запрос с ПЕРВЫЕ 1 иногда возвращает разные документы?
Потому что при совпадающих значениях поля сортировки порядок строк не определён, и СУБД может возвращать их в любом порядке. Добавьте в УПОРЯДОЧИТЬ второй уровень — например, по ссылке — и результат станет детерминированным.
Как найти последний проведённый документ, исключив помеченные на удаление?
Достаточно условия Проведен = Истина: помеченный на удаление документ не может быть проведённым, поэтому отбор по проведённости автоматически исключает и удалённые. Если нужны все «живые» документы независимо от проведения, используйте условие НЕ ПометкаУдаления.
Можно ли получить последний документ через встроенный язык без запроса?
Да, через метод Выбрать() менеджера документа с обходом выборки, но это потребует перебора всех подходящих документов и работает существенно медленнее запроса. Такой подход оправдан только в редких случаях, когда нужна дополнительная логика на каждой итерации; для простого поиска последнего документа всегда используйте запрос.
Как получить последний документ, установивший цену номенклатуры?
Цены в типовых конфигурациях хранятся в периодическом регистре сведений, поэтому используйте виртуальную таблицу СрезПоследних с отбором по номенклатуре. Поле Регистратор в результате — это и есть последний документ, установивший цену. Искать его в таблице документов по максимальной дате не нужно.