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