Как в СКД реализовать строгий отбор по списку значений (логическое «И»)?

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

При работе с Системой компоновки данных (СКД) программисты часто сталкиваются со стандартным поведением отборов. Когда мы передаем список значений в параметр или используем стандартное поле отбора с видом сравнения В списке, система формирует SQL-запрос с оператором IN. В логике баз данных это означает условие «ИЛИ»: объект попадет в отчет, если он соответствует хотя бы одному значению из списка. Однако в реальных задачах часто требуется «строгий отбор» (логическое «И»), при котором объект (например, Номенклатура) должен обладать всеми признаками из переданного списка одновременно.

Рассмотрим ситуацию: у номенклатуры есть табличная часть с характеристиками. Нам нужно найти только те позиции, у которых в этой табличной части присутствуют все значения из нашего списка. Разберем по шагам, как это реализовать в запросе СКД.

Метод 1. Группировка и подсчет совпадений (Рекомендуемый)

Этот метод является наиболее универсальным и производительным. Идея заключается в том, чтобы соединить основную таблицу с искомыми значениями и проверить, совпадает ли количество найденных уникальных строк с количеством элементов в исходном списке. Проанализируем алгоритм реализации:

  1. Передаем список значений в параметр &СписокОтбора.
  2. В коде перед формированием отчета определяем количество уникальных элементов в этом списке и передаем это число в отдельный параметр &КоличествоЭлементов.
  3. В запросе соединяем основную таблицу с табличной частью номенклатуры, где установлен фильтр по нашему списку.
  4. Группируем результат по ссылке на номенклатуру и используем условие ИМЕЮЩИЕ.

Посмотрим на пример запроса, который реализует эту логику:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ОтфильтрованнаяНоменклатура
ИЗ
    Справочник.Номенклатура КАК Номенклатура
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК ТЧ_Реквизиты
        ПО Номенклатура.Ссылка = ТЧ_Реквизиты.Ссылка
ГДЕ
    ТЧ_Реквизиты.Значение В (&СписокОтбора)

СГРУППИРОВАТЬ ПО
    Номенклатура.Ссылка

ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ_Реквизиты.Значение) = &КоличествоЭлементов
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ОтфильтрованнаяНоменклатура.Ссылка
ИЗ
    ВТ_ОтфильтрованнаяНоменклатура КАК ВТ_ОтфильтрованнаяНоменклатура

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

Метод 2. Метод двойного отрицания (NOT EXISTS)

Проанализируем другой подход, основанный на теории множеств. Логика звучит так: «Нам нужны те объекты, для которых не существует такого значения в нашем списке отбора, которого не было бы в табличной части этого объекта».

Этот метод может быть эффективнее на очень больших объемах данных, так как СУБД иногда лучше оптимизирует вложенные запросы с отрицанием, чем тяжелые группировки. Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    Номенклатура.Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    НЕ СУЩЕСТВУЕТ (
        ВЫБРАТЬ
            ЭлементыСписка.Значение
        ИЗ
            &ВТ_СписокОтбора КАК ЭлементыСписка
        ГДЕ
            НЕ СУЩЕСТВУЕТ (
                ВЫБРАТЬ
                    1
                ИЗ
                    Справочник.Номенклатура.ДополнительныеРеквизиты КАК ТЧ
                ГДЕ
                    ТЧ.Ссылка = Номенклатура.Ссылка
                    И ТЧ.Значение = ЭлементыСписка.Значение
            )
    )

Здесь &ВТ_СписокОтбора — это временная таблица, в которую мы предварительно поместили элементы нашего списка значений. Этот запрос буквально ищет отсутствие исключений.

Метод 3. Программная модификация текста запроса

Если вы хотите предоставить пользователю максимальную гибкость и количество элементов в списке невелико (например, до 5-10), можно применить динамическое построение текста запроса. Выясним причину использования такого метода: каждое новое значение из списка превращается в отдельное ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN).

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


ВЫБРАТЬ
    Номенклатура.Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК ТЧ1
        ПО Номенклатура.Ссылка = ТЧ1.Ссылка И ТЧ1.Значение = &Значение1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК ТЧ2
        ПО Номенклатура.Ссылка = ТЧ2.Ссылка И ТЧ2.Значение = &Значение2
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК ТЧ3
        ПО Номенклатура.Ссылка = ТЧ3.Ссылка И ТЧ3.Значение = &Значение3

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

Особенности реализации в СКД

При использовании СКД важно помнить, что стандартные механизмы отборов на закладке «Настройки» всегда работают через ИЛИ (оператор IN). Чтобы реализовать строгий отбор, нам необходимо:

  1. Описать логику одного из вышеперечисленных методов непосредственно в тексте запроса набора данных.
  2. Использовать параметры (например, &СписокЗначений) вместо стандартных полей отбора для этих специфических целей.
  3. Если используется Метод 1, не забудьте в модуле объекта отчета (в событии ПриКомпоновкеРезультата) программно рассчитать количество элементов в списке и установить значение параметра &КоличествоЭлементов.

Пример кода для установки параметра количества:


// В процедуре ПриКомпоновкеРезультата
Количество = 0;
ПараметрСписок = КомпоновщикНастроек.Настройки.ПараметрыДанных.Элементы.Найти("СписокОтбора");
Если ПараметрСписок <> Неопределено И ПараметрСписок.Использование Тогда
    Количество = ПараметрСписок.Значение.Количество();
КонецЕсли;

ПараметрКол = КомпоновщикНастроек.Настройки.ПараметрыДанных.Элементы.Найти("КоличествоЭлементов");
Если ПараметрКол <> Неопределено Тогда
    ПараметрКол.Значение = Количество;
    ПараметрКол.Использование = Истина;
КонецЕсли;

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

← На главную