При работе с базами данных 1С часто возникает задача, когда нам необходимо получить список последних документов для каждого контрагента. Казалось бы, это простая задача, решаемая с помощью функции МАКСИМУМ(Дата) и группировки по контрагенту. Однако, на практике мы сталкиваемся с тем, что несколько документов могут быть созданы в один и тот же день, и тогда выбор "последнего" документа по одной только дате становится некорректным. В таких случаях нам необходим более точный критерий, такой как МоментВремени документа.
Рассмотрим несколько подходов к решению этой задачи в запросах 1С, анализируя их корректность и производительность, что обеспечит быстрый анализ данных.
Один из способов получить последний документ по контрагенту, учитывая МоментВремени, заключается в использовании самосоединения (SELF-JOIN) таблицы документов. Этот метод позволяет нам сравнить каждый документ со всеми остальными документами того же контрагента, чтобы определить, является ли он самым последним.
Принцип работы:
Документ.ВашДокумент КАК ВашДокумент с Документ.ВашДокумент КАК ВашДокумент1).ВашДокумент.Контрагент = ВашДокумент1.Контрагент) и проверку на то, что МоментВремени второго документа (ВашДокумент1) больше или равен МоментВремени первого документа (ВашДокумент). То есть, мы ищем все документы, которые были созданы одновременно или позже для того же контрагента.Контрагент и Ссылка первого документа.ИМЕЮЩИЕ мы проверяем, что КОЛИЧЕСТВО(ВашДокумент1.Ссылка) = 1. Это ключевое условие: если для данного ВашДокумент существует только один документ (сам этот ВашДокумент) с МоментВремени, который больше или равен его собственному МоментВремени среди документов того же контрагента, то это означает, что ВашДокумент и есть самый последний.Рассмотрим пример такого запроса:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВашДокумент.Контрагент КАК Контрагент,
ВашДокумент.Ссылка КАК Ссылка,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВашДокумент1.Ссылка) КАК Номер
ИЗ
Документ.ВашДокумент КАК ВашДокумент
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ВашДокумент КАК ВашДокумент1
ПО ВашДокумент.МоментВремени <= ВашДокумент1.МоментВремени
И ВашДокумент.Контрагент = ВашДокумент1.Контрагент
ГДЕ
ВашДокумент.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И ВашДокумент.Контрагент В(&МассивКонтрагентов)
И ВашДокумент1.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И ВашДокумент1.Контрагент В(&МассивКонтрагентов)
СГРУППИРОВАТЬ ПО
ВашДокумент.Контрагент,
ВашДокумент.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(ВашДокумент1.Ссылка) = 1
УПОРЯДОЧИТЬ ПО
Контрагент,
Номер
Этот подход является корректным, поскольку он напрямую использует МоментВремени для определения хронологии, что позволяет точно выявить последний документ даже при совпадении даты и времени. Однако, он может быть менее производительным на больших объемах данных, так как самосоединение потенциально требует сравнения каждой строки с каждой, что ведет к квадратичному росту сложности запроса.
Наиболее эффективным и корректным способом выбора последнего документа по контрагенту, особенно когда нам необходимо учесть МоментВремени для разрешения конфликтов при одинаковых датах, является комбинированный подход с использованием временных таблиц (ВТ) и подзапроса с ВЫБРАТЬ ПЕРВЫЕ 1. Этот метод позволяет нам решить проблему пошагово, а проверить результат поможет консоль запросов для управляемых форм 8.3.
Прежде чем перейти к решению, выясним некоторые важные моменты:
МАКСИМУМ(Дата) недостаточно? Как мы уже упоминали, документы могут быть созданы в одну и ту же дату. МАКСИМУМ(Дата) вернет нам все такие документы, если их несколько, а нам нужен только один, самый последний.
МАКСИМУМ(МоментВремени) напрямую? Поле МоментВремени является виртуальным и не может быть использовано в агрегатных функциях типа МАКСИМУМ() или СУММА(). Попытка использования вызовет ошибку запроса. Однако, по полю МоментВремени можно выполнять сортировку (УПОРЯДОЧИТЬ ПО).
Ссылка (GUID) для разрешения конфликтов? Возникли дискуссии о том, можно ли использовать МАКСИМУМ(Ссылка) или сортировку по Ссылка для определения "последнего" документа, когда Дата совпадает. Общий консенсус состоит в том, что полагаться на последовательность GUID для хронологической сортировки не стоит. GUIDы (уникальные идентификаторы) генерируются пулами на сеанс. Это означает, что документ, созданный позже в одном сеансе, может иметь меньший GUID, чем документ, созданный раньше в другом сеансе, если пул GUIDов второго сеанса был получен позднее. Также, существуют сценарии ручного создания GUID или загрузки документов из других баз, что полностью нарушает предполагаемую хронологию по ссылке. Поэтому, для точной хронологии всегда предпочтительнее использовать МоментВремени.
Теперь рассмотрим рекомендуемый запрос по шагам.
Сначала мы создаем временную таблицу, в которой для каждого контрагента будет храниться его максимальная дата документа. Сгенерировать каркас кода может консоль кода + ИИ-помощник. Это значительно сократит объем данных для последующей обработки.
ВЫБРАТЬ
ПоступлениеТоваров.Контрагент КАК Контрагент,
МАКСИМУМ(ПоступлениеТоваров.Дата) КАК Дата
ПОМЕСТИТЬ ВТ_МДАТА
ИЗ
Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
СГРУППИРОВАТЬ ПО
ПоступлениеТоваров.Контрагент
;
В этом запросе мы выбираем Контрагент и его МАКСИМУМ(Дата) из нашей таблицы документов и группируем по контрагенту. Результат помещается во временную таблицу ВТ_МДАТА.
После получения максимальных дат, мы соединяем нашу временную таблицу ВТ_МДАТА с исходной таблицей документов. Условие соединения будет включать равенство контрагентов и равенство дат. Здесь мы можем столкнуться с ситуацией, когда для одного контрагента есть несколько документов с одной и той же максимальной датой. Для разрешения этой неопределенности мы используем подзапрос с ВЫБРАТЬ ПЕРВЫЕ 1 и сортировкой по МоментВремени УБЫВ.
////////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_МДАТА.Контрагент КАК Контрагент,
ВТ_МДАТА.Дата КАК Дата,
ПоступлениеТоваров.МоментВремени КАК МоментВремени,
ПоступлениеТоваров.Ссылка КАК Ссылка
ИЗ
ВТ_МДАТА КАК ВТ_МДАТА
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
ПО ВТ_МДАТА.Контрагент = ПоступлениеТоваров.Контрагент
И ВТ_МДАТА.Дата = ПоступлениеТоваров.Дата
И (ПоступлениеТоваров.Ссылка В
(ВЫБРАТЬ ПЕРВЫЕ 1
ПоступлениеТоваров.Ссылка КАК Ссылка
ИЗ
Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
ГДЕ
ПоступлениеТоваров.Дата = ВТ_МДАТА.Дата
И ПоступлениеТоваров.Контрагент = ВТ_МДАТА.Контрагент
УПОРЯДОЧИТЬ ПО
ПоступлениеТоваров.МоментВремени УБЫВ))
Разберем этот запрос подробнее:
ВТ_МДАТА с Документ.ПоступлениеТоваров по Контрагент и Дата. На этом этапе мы получаем все документы с максимальной датой для каждого контрагента. Если для одного контрагента есть несколько таких документов, они все будут включены.
(ПоступлениеТоваров.Ссылка В
(ВЫБРАТЬ ПЕРВЫЕ 1
ПоступлениеТоваров.Ссылка КАК Ссылка
ИЗ
Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
ГДЕ
ПоступлениеТоваров.Дата = ВТ_МДАТА.Дата
И ПоступлениеТоваров.Контрагент = ВТ_МДАТА.Контрагент
УПОРЯДОЧИТЬ ПО
ПоступлениеТоваров.МоментВремени УБЫВ))
Контрагент и Дата из ВТ_МДАТА находит одну единственную ссылку. Он делает это, выбирая ПЕРВЫЕ 1 Ссылка из исходной таблицы документов, но предварительно сортируя их по МоментВремени в убывающем порядке (УБЫВ). Таким образом, документ с самым "поздним" МоментВремени (который включает и дату, и уникальную ссылку для разрешения миллисекундных конфликтов) будет выбран как первый.МоментВремени.Как показали практические тесты, проведенные в рамках обсуждения, подход с временными таблицами и ВЫБРАТЬ ПЕРВЫЕ 1 значительно превосходит по производительности метод самосоединения. В одном из тестов было зафиксировано, что решение на ВТ было быстрее в 230 раз, чем решение с самосоединением. Это делает его предпочтительным для больших баз данных и высоконагруженных систем.
Эти цифры убедительно демонстрируют эффективность использования временных таблиц для разбиения сложной логики на более простые и оптимизированные шаги.
Подводя итог, давайте обобщим ключевые моменты, которые следует учитывать при выборе последнего документа по контрагенту:
МоментВремени – ваш лучший друг для хронологии: Для точного определения "последнего" документа, особенно когда документы могут иметь одинаковую дату и время, всегда используйте поле МоментВремени. Это виртуальное поле, которое формируется платформой 1С автоматически и является уникальным для каждого документа, учитывая дату и ссылку.
МоментВремени нельзя напрямую использовать в агрегатных функциях типа МАКСИМУМ(). Однако, его можно и нужно использовать в секции УПОРЯДОЧИТЬ ПО.
Ссылка для хронологии: Избегайте использования МАКСИМУМ(Ссылка) или сортировки по полю Ссылка для определения последнего документа. Механизм генерации GUID (которыми являются ссылки) не гарантирует хронологической последовательности, что может привести к некорректным результатам.
ВЫБРАТЬ ПЕРВЫЕ 1 + УПОРЯДОЧИТЬ ПО МоментВремени УБЫВ: Это комбинация является наиболее надежным способом выбрать один единственный "последний" элемент из группы, когда есть несколько кандидатов с одинаковыми основными критериями (например, одинаковой датой).
Таким образом, для выбора последнего документа по контрагенту в 1С мы рекомендуем использовать комбинированный подход с временными таблицами, который сначала определяет максимальную дату, а затем с помощью подзапроса и сортировки по МоментВремени выбирает наиболее актуальный документ. Этот метод обеспечивает как корректность, так и высокую производительность.