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