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

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

При работе в 1С часто возникает задача, когда необходимо найти документы, содержимое табличной части которых в точности соответствует некоторому заданному списку. Классический пример – отбор документов реализации, где присутствуют только те товары, которые выбрал пользователь, и ни одного другого. При этом важно не только убедиться, что все выбранные товары присутствуют, но и что в документе нет никаких «лишних» позиций.

Рассмотрим ситуацию: пользователь передает массив номенклатуры, например, {"Иголка", "Нитка"}. Наша задача – найти документы реализации, в табличной части которых есть только «Иголка» и «Нитка» и нет ничего другого (например, «Наперстка» или «Катушки»). Простой запрос ГДЕ РеализацияТоваровУслугТовары.Номенклатура В (&Массив) не подойдет, так как он найдет документы, содержащие «Иголку» и «Нитку», даже если там есть и другие товары.

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

Решение 1: Использование временных таблиц и левого соединения с условием «ЕСТЬ NULL»

Первый подход, который является одним из наиболее распространенных способов реализации логики «NOT EXISTS» в 1С, основан на создании двух временных таблиц и их последующем левом соединении.

  1. Выявляем потенциально подходящие документы: Сначала мы отбираем все документы, у которых хотя бы одна строка табличной части содержит номенклатуру, входящую в наш массив &Массив. Это формирует наш начальный набор документов-кандидатов.

  2. Исключаем документы с «лишней» номенклатурой: Затем мы выявляем документы, у которых есть номенклатура, НЕ входящая в наш &Массив. Эти документы являются нежелательными, и их нам нужно отсеять.

  3. Объединяем и фильтруем: Мы выполняем левое соединение первого набора документов со вторым. Документы, для которых во втором наборе не найдется соответствия (т.е., которые не содержат «лишней» номенклатуры), будут иметь NULL в полях из второй таблицы. Именно эти документы нам и нужны.

Разберем это по шагам с примерами кода:

Шаг 1: Создаем временную таблицу для документов с номенклатурой из массива

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


ВЫБРАТЬ РАЗЛИЧНЫЕ
    РеализацияТоваровУслугТовары.Ссылка
ПОМЕСТИТЬ ДокументыСНоменклатуройИзМассива
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
    РеализацияТоваровУслугТовары.Номенклатура В (&Массив)
;

Здесь мы используем РАЗЛИЧНЫЕ, чтобы получить уникальные ссылки на документы, так как в документе может быть несколько строк с номенклатурой из массива.

Шаг 2: Создаем временную таблицу для документов с номенклатурой НЕ из массива

Эта таблица будет содержать ссылки на все документы, в которых есть хотя бы одна позиция номенклатуры, которая НЕ входит в наш &Массив. Это те документы, которые нам точно не подходят, и мы будем их исключать.


ВЫБРАТЬ РАЗЛИЧНЫЕ
    РеализацияТоваровУслугТовары.Ссылка
ПОМЕСТИТЬ ДокументыСЛишнейНоменклатурой
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
    НЕ РеализацияТоваровУслугТовары.Номенклатура В (&Массив)
;

Так же используем РАЗЛИЧНЫЕ, чтобы получить уникальные ссылки на документы, содержащие "лишнюю" номенклатуру.

Шаг 3: Выполняем левое соединение и фильтруем результат

Теперь мы соединяем первую таблицу со второй. Если для какого-либо документа из ДокументыСНоменклатуройИзМассива не найдется соответствия в ДокументыСЛишнейНоменклатурой, это будет означать, что в этом документе нет номенклатуры, не входящей в наш массив. Именно такие документы нам и нужны.


ВЫБРАТЬ
    ДокументыСНоменклатуройИзМассива.Ссылка
ИЗ
    ДокументыСНоменклатуройИзМассива КАК ДокументыСНоменклатуройИзМассива
        ЛЕВОЕ СОЕДИНЕНИЕ ДокументыСЛишнейНоменклатурой КАК ДокументыСЛишнейНоменклатурой
        ПО ДокументыСНоменклатуройИзМассива.Ссылка = ДокументыСЛишнейНоменклатурой.Ссылка
ГДЕ
    ДокументыСЛишнейНоменклатурой.Ссылка ЕСТЬ NULL

Условие ДокументыСЛишнейНоменклатурой.Ссылка ЕСТЬ NULL является ключевым. Оно отбирает только те документы, для которых не было найдено «лишней» номенклатуры. Это решение эффективно исключает документы с любой номенклатурой, не входящей в переданный массив. Однако, оно не гарантирует, что все номенклатуры из массива присутствуют в документе, и что их количество совпадает.

Решение 2: Строгое совпадение наборов – добавление проверки количества уникальных позиций

Первое решение гарантирует отсутствие «лишних» позиций. Но что, если в &Массив передано 3 товара (А, Б, В), а в документе есть только 2 из них (А, Б) и нет никаких других? Решение 1 все равно отберет такой документ, так как в нем нет «лишних» позиций. Чтобы обеспечить полное совпадение наборов номенклатуры (т.е. чтобы в документе были все позиции из &Массив и только они), необходимо добавить проверку на количество уникальных позиций.

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

Шаг 4: Создаем временную таблицу для подсчета уникальных позиций

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


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

Здесь КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) подсчитывает количество уникальных элементов номенклатуры для каждой ссылки на документ. Условие ГДЕ РеализацияТоваровУслугТовары.Номенклатура В (&Массив) гарантирует, что мы считаем только те позиции, которые интересуют пользователя.

Шаг 5: Дополняем итоговый запрос

Теперь мы соединяем результаты Шага 3 с новой временной таблицей КоличествоУникальнойНоменклатуры и добавляем условие на совпадение количества элементов.


// Временная таблица 1: Документы, содержащие номенклатуру из &Массив
ВЫБРАТЬ РАЗЛИЧНЫЕ
    РеализацияТоваровУслугТовары.Ссылка
ПОМЕСТИТЬ ДокументыСНоменклатуройИзМассива
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
    РеализацияТоваровУслугТовары.Номенклатура В (&Массив)
;

// Временная таблица 2: Документы, содержащие номенклатуру НЕ из &Массив (для исключения "лишних")
ВЫБРАТЬ РАЗЛИЧНЫЕ
    РеализацияТоваровУслугТовары.Ссылка
ПОМЕСТИТЬ ДокументыСЛишнейНоменклатурой
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
    НЕ РеализацияТоваровУслугТовары.Номенклатура В (&Массив)
;

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

// Итоговый запрос: соединяем все условия для строгого совпадения
ВЫБРАТЬ
    ДокументыСНоменклатуройИзМассива.Ссылка
ИЗ
    ДокументыСНоменклатуройИзМассива КАК ДокументыСНоменклатуройИзМассива
        ЛЕВОЕ СОЕДИНЕНИЕ ДокументыСЛишнейНоменклатурой КАК ДокументыСЛишнейНоменклатурой
            ПО ДокументыСНоменклатуройИзМассива.Ссылка = ДокументыСЛишнейНоменклатурой.Ссылка
        ЛЕВОЕ СОЕДИНЕНИЕ КоличествоУникальнойНоменклатуры КАК КоличествоУникальнойНоменклатуры
            ПО ДокументыСНоменклатуройИзМассива.Ссылка = КоличествоУникальнойНоменклатуры.Ссылка
ГДЕ
    ДокументыСЛишнейНоменклатурой.Ссылка ЕСТЬ NULL // Нет "лишней" номенклатуры
    И КоличествоУникальнойНоменклатуры.КоличествоНоменклатурыВДОкументе = &КоличествоЭлементовВМассиве // Количество уникальной номенклатуры совпадает с размером массива

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

Решение 3: Использование агрегатных функций и оператора «ИМЕЯ» (HAVING)

Для опытных разработчиков и при определенных сценариях, более лаконичным и зачастую более производительным может быть подход с использованием группировки и оператора ИМЕЯ (HAVING). Этот метод позволяет решить обе части задачи (отсутствие лишних позиций и совпадение количества уникальных позиций) в одном запросе.

  1. Группируем по ссылке на документ: Мы собираем все строки табличной части для каждого документа.

  2. Проверяем количество уникальных позиций: В блоке ИМЕЯ мы убеждаемся, что количество различных номенклатур в документе равно количеству элементов в нашем &Массив.

  3. Проверяем отсутствие «лишних» позиций: Дополнительно в ИМЕЯ мы проверяем, что все номенклатуры в документе входят в наш &Массив. Для этого мы считаем сумму единиц для тех строк, где номенклатура входит в &Массив, и сравниваем эту сумму с общим количеством строк в табличной части документа. Если они равны, это означает, что все позиции в документе находятся в нашем массиве.

Пример запроса:


ВЫБРАТЬ
    РеализацияТоваровУслугТовары.Ссылка
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
СГРУППИРОВАТЬ ПО
    РеализацияТоваровУслугТовары.Ссылка
ИМЕЯ
    // Условие 1: Количество уникальной номенклатуры в документе должно совпадать
    // с количеством элементов в пользовательском массиве.
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) = &КоличествоЭлементовВМассиве

    И

    // Условие 2: Вся номенклатура в документе должна входить в пользовательский массив.
    // Если есть хотя бы одна позиция вне массива, сумма будет меньше общего количества строк.
    СУММА(ВЫБОР КОГДА РеализацияТоваровУслугТовары.Номенклатура В (&Массив) ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) = КОЛИЧЕСТВО(РеализацияТоваровУслугТовары.Номенклатура)

В этом запросе мы используем два агрегатных условия в операторе ИМЕЯ:

Данный подход является очень мощным и позволяет получить требуемый результат в одном, хотя и довольно плотном, запросе. Он считается одним из наиболее элегантных для задач совпадения наборов.

Важность индексов для производительности

При работе с большими объемами данных производительность запросов становится критически важной. Для всех предложенных решений крайне важно убедиться в наличии адекватных индексов в базе данных.

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

Заключение

Мы рассмотрели несколько эффективных способов отбора документов, чья табличная часть с номенклатурой полностью совпадает с заданным пользователем списком. Каждый из подходов имеет свои особенности:

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

← На главную