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

Программист 1С v8.3 (Управляемые формы)
← На главную

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

Рассмотрим несколько подходов к решению этой задачи в запросах 1С, анализируя их корректность и производительность, что обеспечит быстрый анализ данных.

Подход 1: Использование самосоединения (Self-Join) таблицы документов

Один из способов получить последний документ по контрагенту, учитывая МоментВремени, заключается в использовании самосоединения (SELF-JOIN) таблицы документов. Этот метод позволяет нам сравнить каждый документ со всеми остальными документами того же контрагента, чтобы определить, является ли он самым последним.

Принцип работы:

  1. Мы соединяем таблицу документов саму с собой (например, Документ.ВашДокумент КАК ВашДокумент с Документ.ВашДокумент КАК ВашДокумент1).
  2. Условие соединения будет включать равенство контрагентов (ВашДокумент.Контрагент = ВашДокумент1.Контрагент) и проверку на то, что МоментВремени второго документа (ВашДокумент1) больше или равен МоментВремени первого документа (ВашДокумент). То есть, мы ищем все документы, которые были созданы одновременно или позже для того же контрагента.
  3. После этого мы группируем результаты по Контрагент и Ссылка первого документа.
  4. В секции ИМЕЮЩИЕ мы проверяем, что КОЛИЧЕСТВО(ВашДокумент1.Ссылка) = 1. Это ключевое условие: если для данного ВашДокумент существует только один документ (сам этот ВашДокумент) с МоментВремени, который больше или равен его собственному МоментВремени среди документов того же контрагента, то это означает, что ВашДокумент и есть самый последний.

Рассмотрим пример такого запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВашДокумент.Контрагент КАК Контрагент,
    ВашДокумент.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВашДокумент1.Ссылка) КАК Номер
ИЗ
    Документ.ВашДокумент КАК ВашДокумент
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ВашДокумент КАК ВашДокумент1
        ПО ВашДокумент.МоментВремени <= ВашДокумент1.МоментВремени
        И ВашДокумент.Контрагент = ВашДокумент1.Контрагент
ГДЕ
    ВашДокумент.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    И ВашДокумент.Контрагент В(&МассивКонтрагентов)
    И ВашДокумент1.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    И ВашДокумент1.Контрагент В(&МассивКонтрагентов)
СГРУППИРОВАТЬ ПО
    ВашДокумент.Контрагент,
    ВашДокумент.Ссылка
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(ВашДокумент1.Ссылка) = 1
УПОРЯДОЧИТЬ ПО
    Контрагент,
    Номер

Этот подход является корректным, поскольку он напрямую использует МоментВремени для определения хронологии, что позволяет точно выявить последний документ даже при совпадении даты и времени. Однако, он может быть менее производительным на больших объемах данных, так как самосоединение потенциально требует сравнения каждой строки с каждой, что ведет к квадратичному росту сложности запроса.

Подход 2: Комбинированный подход с временными таблицами (ВТ) и ВЫБРАТЬ ПЕРВЫЕ 1 (Рекомендуемый)

Наиболее эффективным и корректным способом выбора последнего документа по контрагенту, особенно когда нам необходимо учесть МоментВремени для разрешения конфликтов при одинаковых датах, является комбинированный подход с использованием временных таблиц (ВТ) и подзапроса с ВЫБРАТЬ ПЕРВЫЕ 1. Этот метод позволяет нам решить проблему пошагово, а проверить результат поможет консоль запросов для управляемых форм 8.3.

Прежде чем перейти к решению, выясним некоторые важные моменты:

Теперь рассмотрим рекомендуемый запрос по шагам.

Шаг 1: Определяем максимальную дату для каждого контрагента и помещаем в временную таблицу

Сначала мы создаем временную таблицу, в которой для каждого контрагента будет храниться его максимальная дата документа. Сгенерировать каркас кода может консоль кода + ИИ-помощник. Это значительно сократит объем данных для последующей обработки.


ВЫБРАТЬ
    ПоступлениеТоваров.Контрагент КАК Контрагент,
    МАКСИМУМ(ПоступлениеТоваров.Дата) КАК Дата
ПОМЕСТИТЬ ВТ_МДАТА
ИЗ
    Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
СГРУППИРОВАТЬ ПО
    ПоступлениеТоваров.Контрагент
;

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

Шаг 2: Соединяем с основной таблицей документов и выбираем единственный "последний" документ по МоментВремени

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


////////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_МДАТА.Контрагент КАК Контрагент,
    ВТ_МДАТА.Дата КАК Дата,
    ПоступлениеТоваров.МоментВремени КАК МоментВремени,
    ПоступлениеТоваров.Ссылка КАК Ссылка
ИЗ
    ВТ_МДАТА КАК ВТ_МДАТА
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
        ПО ВТ_МДАТА.Контрагент = ПоступлениеТоваров.Контрагент
        И ВТ_МДАТА.Дата = ПоступлениеТоваров.Дата
        И (ПоступлениеТоваров.Ссылка В
            (ВЫБРАТЬ ПЕРВЫЕ 1
                ПоступлениеТоваров.Ссылка КАК Ссылка
            ИЗ
                Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
            ГДЕ
                ПоступлениеТоваров.Дата = ВТ_МДАТА.Дата
                И ПоступлениеТоваров.Контрагент = ВТ_МДАТА.Контрагент
            УПОРЯДОЧИТЬ ПО
                ПоступлениеТоваров.МоментВремени УБЫВ))

Разберем этот запрос подробнее:

  1. Мы соединяем ВТ_МДАТА с Документ.ПоступлениеТоваров по Контрагент и Дата. На этом этапе мы получаем все документы с максимальной датой для каждого контрагента. Если для одного контрагента есть несколько таких документов, они все будут включены.
  2. Для того чтобы выбрать только один, самый последний документ из группы с одинаковой максимальной датой, мы используем в условии соединения подзапрос:
    
    (ПоступлениеТоваров.Ссылка В
        (ВЫБРАТЬ ПЕРВЫЕ 1
            ПоступлениеТоваров.Ссылка КАК Ссылка
        ИЗ
            Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
        ГДЕ
            ПоступлениеТоваров.Дата = ВТ_МДАТА.Дата
            И ПоступлениеТоваров.Контрагент = ВТ_МДАТА.Контрагент
        УПОРЯДОЧИТЬ ПО
            ПоступлениеТоваров.МоментВремени УБЫВ))
    
  3. Этот подзапрос для каждой комбинации Контрагент и Дата из ВТ_МДАТА находит одну единственную ссылку. Он делает это, выбирая ПЕРВЫЕ 1 Ссылка из исходной таблицы документов, но предварительно сортируя их по МоментВремени в убывающем порядке (УБЫВ). Таким образом, документ с самым "поздним" МоментВремени (который включает и дату, и уникальную ссылку для разрешения миллисекундных конфликтов) будет выбран как первый.
  4. В результате, наш основной запрос возвращает уникальный список последних документов по каждому контрагенту, точно определенный по МоментВремени.

Сравнение производительности

Как показали практические тесты, проведенные в рамках обсуждения, подход с временными таблицами и ВЫБРАТЬ ПЕРВЫЕ 1 значительно превосходит по производительности метод самосоединения. В одном из тестов было зафиксировано, что решение на ВТ было быстрее в 230 раз, чем решение с самосоединением. Это делает его предпочтительным для больших баз данных и высоконагруженных систем.

Эти цифры убедительно демонстрируют эффективность использования временных таблиц для разбиения сложной логики на более простые и оптимизированные шаги.

Ключевые особенности и рекомендации

Подводя итог, давайте обобщим ключевые моменты, которые следует учитывать при выборе последнего документа по контрагенту:

  1. МоментВремени – ваш лучший друг для хронологии: Для точного определения "последнего" документа, особенно когда документы могут иметь одинаковую дату и время, всегда используйте поле МоментВремени. Это виртуальное поле, которое формируется платформой 1С автоматически и является уникальным для каждого документа, учитывая дату и ссылку.
  2. Ограничения агрегатных функций: Помните, что МоментВремени нельзя напрямую использовать в агрегатных функциях типа МАКСИМУМ(). Однако, его можно и нужно использовать в секции УПОРЯДОЧИТЬ ПО.
  3. Не полагайтесь на Ссылка для хронологии: Избегайте использования МАКСИМУМ(Ссылка) или сортировки по полю Ссылка для определения последнего документа. Механизм генерации GUID (которыми являются ссылки) не гарантирует хронологической последовательности, что может привести к некорректным результатам.
  4. Временные таблицы (ВТ) для производительности: Для сложных запросов, требующих многоэтапной обработки или разрешения конфликтов, активно используйте временные таблицы. Они позволяют разделить логику на управляемые части, значительно повышая читаемость и производительность запроса.
  5. ВЫБРАТЬ ПЕРВЫЕ 1 + УПОРЯДОЧИТЬ ПО МоментВремени УБЫВ: Это комбинация является наиболее надежным способом выбрать один единственный "последний" элемент из группы, когда есть несколько кандидатов с одинаковыми основными критериями (например, одинаковой датой).

Таким образом, для выбора последнего документа по контрагенту в 1С мы рекомендуем использовать комбинированный подход с временными таблицами, который сначала определяет максимальную дату, а затем с помощью подзапроса и сортировки по МоментВремени выбирает наиболее актуальный документ. Этот метод обеспечивает как корректность, так и высокую производительность.

← На главную