Как исключить из результатов запроса записи, соответствующие условию другого запроса?

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

В процессе разработки на платформе 1С довольно часто возникает задача фильтрации данных по принципу исключения. Типичный пример: нам необходимо выбрать все характеристики номенклатуры, которые обладают свойством «Размер», но при этом из этой выборки нужно убрать те объекты, у которых заполнено свойство «Рост». На первый взгляд задача кажется простой, но специфика хранения данных в регистрах сведений со свойствами (модель Entity-Attribute-Value) накладывает свои ограничения. Разберем подробно, как правильно строить такие запросы, проанализируем ошибки и найдем оптимальные решения.

В чем сложность работы с регистрами свойств?

Прежде чем переходить к коду, проанализируем структуру данных. В типовых конфигурациях (например, УТ 10.3, как в примере автора) дополнительные реквизиты хранятся в регистре сведений ЗначенияСвойствОбъектов. Каждая запись в этом регистре — это отдельная строка, связывающая Объект (характеристику), Свойство и Значение.

Рассмотрим ситуацию на примере:

  1. Объект «Х1», Свойство «Размер», Значение «48»
  2. Объект «Х1», Свойство «Рост», Значение «176»
  3. Объект «Х2», Свойство «Размер», Значение «48»

Если мы напишем простое условие ГДЕ Свойство <> &СвойствоРост, система просто исключит вторую строку из результата, но Объект «Х1» все равно попадет в выборку, так как для него существует первая строка. Наша же задача — полностью исключить Объект «Х1», так как у него в принципе существует запись со свойством «Рост».

Решение №1: Использование оператора НЕ В с подзапросом

Рассмотрим наиболее понятный и логичный способ, к которому в итоге пришел автор темы. Мы выбираем все объекты с нужным размером и в условии ГДЕ указываем, что эти объекты не должны входить в список тех, у кого есть рост.

Проанализируем пример кода:


ВЫБРАТЬ
    ЗначенияСвойствОбъектов.Объект КАК ХарактеристикаНоменклатуры
ИЗ
    РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
    ЗначенияСвойствОбъектов.Свойство = &СвойствоРазмер
    И ЗначенияСвойствОбъектов.Значение = &ЗначениеРазмер
    И ЗначенияСвойствОбъектов.Объект ССЫЛКА Справочник.ХарактеристикиНоменклатуры
    И НЕ ЗначенияСвойствОбъектов.Объект В
        (ВЫБРАТЬ
            ВложенныйРегистр.Объект
        ИЗ
            РегистрСведений.ЗначенияСвойствОбъектов КАК ВложенныйРегистр
        ГДЕ
            ВложенныйРегистр.Свойство = &СвойствоРост)

В данном примере мы сначала получаем все характеристики с заданным размером. Затем с помощью конструкции НЕ В мы «отсеиваем» те характеристики, которые встречаются в подзапросе по свойству «Рост». Это надежный метод, который дает корректный результат.

Решение №2: Использование ЛЕВОГО СОЕДИНЕНИЯ (Anti-Join)

Использование ЛЕВОГО СОЕДИНЕНИЯ считается более производительным методом в SQL-системах, особенно на больших объемах данных. Однако здесь важно правильно настроить условие соединения и фильтрацию по NULL. Посмотрим, как это реализовать правильно, чтобы избежать ошибок, с которыми столкнулся автор.

Рассмотрим по шагам:

  1. Сначала поместим во временную таблицу все объекты, которые нам подходят по первому критерию (размер).
  2. Затем выберем основные данные из этой временной таблицы и присоединим к ней таблицу свойств еще раз, но только по свойству «Рост».
  3. В условии укажем, что правая часть соединения должна быть пустой (ЕСТЬ NULL).

// Помещаем отобранные по размеру объекты во временную таблицу
ВЫБРАТЬ
    ЗначенияСвойствОбъектов.Объект КАК Объект
ПОМЕСТИТЬ ВТ_ОтобранныеПоРазмеру
ИЗ
    РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
    ЗначенияСвойствОбъектов.Свойство = &СвойствоРазмер
    И ЗначенияСвойствОбъектов.Значение = &ЗначениеРазмер;

////////////////////////////////////////////////////////////////////////////////
// Исключаем те, у которых есть свойство Рост
ВЫБРАТЬ
    ВТ.Объект КАК ХарактеристикаНоменклатуры
ИЗ
    ВТ_ОтобранныеПоРазмеру КАК ВТ
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК СвойстваРоста
    ПО ВТ.Объект = СвойстваРоста.Объект
    И СвойстваРоста.Свойство = &СвойствоРост
ГДЕ
    СвойстваРоста.Объект ЕСТЬ NULL

Важный нюанс: условие СвойстваРоста.Свойство = &СвойствоРост должно находиться именно в секции ПО (условие связи), а не в секции ГДЕ. Если вынести его в ГДЕ, левое соединение превратится во внутреннее, и логика исключения перестанет работать.

Решение №3: Группировка и условие ИМЕЮЩИЕ

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

Проанализируем ситуацию:


ВЫБРАТЬ
    ЗначенияСвойствОбъектов.Объект КАК ХарактеристикаНоменклатуры
ИЗ
    РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
    ЗначенияСвойствОбъектов.Свойство В (&СвойствоРазмер, &СвойствоРост)
ГРУППИРОВАТЬ ПО
    ЗначенияСвойствОбъектов.Объект
ИМЕЮЩИЕ
    МАКСИМУМ(ВЫБОР
            КОГДА ЗначенияСвойствОбъектов.Свойство = &СвойствоРазмер
                    И ЗначенияСвойствОбъектов.Значение = &ЗначениеРазмер
                ТОГДА 1
            ИНАЧЕ 0
        КОНЕЦ) = 1
    И МАКСИМУМ(ВЫБОР
            КОГДА ЗначенияСвойствОбъектов.Свойство = &СвойствоРост
                ТОГДА 1
            ИНАЧЕ 0
        КОНЕЦ) = 0

Этот метод хорош тем, что он выполняет всего один проход по таблице регистра, что может быть значительно быстрее при наличии сотен тысяч записей. Мы буквально говорим системе: «Дай мне объекты, у которых точно есть запись с Размером, но максимальный индикатор наличия Роста равен 0».

Оптимизация и рекомендации

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

Таким образом, мы выяснили причину неудач автора (попытка фильтровать свойства в рамках одной строки вместо анализа всей совокупности свойств объекта) и разобрали три способа решения задачи: через НЕ В, через ЛЕВОЕ СОЕДИНЕНИЕ и через ГРУППИРОВКУ. Выбор конкретного метода зависит от объема ваших данных и требований к производительности кода.

← На главную