При разработке на платформе 1С:Предприятие часто возникает задача поиска объектов, которые обладают сразу несколькими характеристиками. Типичный пример: поиск товара в справочнике Номенклатура или ХарактеристикиНоменклатуры, у которого в табличной части ДополнительныеРеквизиты заданы конкретные пары «Свойство — Значение». Трудность заключается в том, что каждое свойство хранится в отдельной строке, и обычное условие ГДЕ с оператором И не сработает, так как одна строка не может содержать разные значения одновременно.
В этой статье мы подробно разберем, как правильно составить запрос для решения этой задачи, проанализируем различные подходы и выберем наиболее эффективные методы в зависимости от условий задачи.
Рассмотрим ситуацию, описанную в обсуждении. Программист пытается найти характеристику, у которой «Цвет» — «Красный» И «Размер» — «42». Если написать условие так:
ГДЕ (ДополнительныеРеквизиты.Свойство = &СвойствоЦвет И ДополнительныеРеквизиты.Значение = &ЗначениеКрасный)
И (ДополнительныеРеквизиты.Свойство = &СвойствоРазмер И ДополнительныеРеквизиты.Значение = &Значение42)
Такой запрос всегда вернет пустой результат. Это происходит потому, что система анализирует каждую строку табличной части по отдельности. В базе данных нет строки, где поле Свойство одновременно равно и цвету, и размеру. Нам же нужно найти объект, у которого есть минимум две разные строки, соответствующие этим условиям.
Проанализируем самый популярный и лаконичный метод. Его суть заключается в том, чтобы выбрать все строки, подходящие под любое из условий (через ИЛИ), сгруппировать их по ссылке на объект и оставить только те объекты, количество найденных строк у которых равно количеству искомых свойств.
Разберем этот алгоритм пошагово:
Ссылка.ИМЕЮЩИЕ проверяем, что количество различных свойств в группе совпадает с количеством условий, которые мы передали.Посмотрим на пример кода:
ВЫБРАТЬ
ТчДопРеквизиты.Ссылка КАК Ссылка
ИЗ
Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ТчДопРеквизиты
ГДЕ
(ТчДопРеквизиты.Свойство = &Свойство1 И ТчДопРеквизиты.Значение = &Значение1)
ИЛИ (ТчДопРеквизиты.Свойство = &Свойство2 И ТчДопРеквизиты.Значение = &Значение2)
СГРУППИРОВАТЬ ПО
ТчДопРеквизиты.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТчДопРеквизиты.Свойство) = &КоличествоУсловий
Важный нюанс: использование КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТчДопРеквизиты.Свойство) критически важно, если в табличной части теоретически могут дублироваться строки с одинаковыми свойствами. Если вы уверены в уникальности строк, можно использовать КОЛИЧЕСТВО(ТчДопРеквизиты.НомерСтроки).
Рассмотрим другой подход, который часто оказывается более производительным на больших объемах данных, так как позволяет СУБД эффективно использовать индексы. Мы будем последовательно присоединять таблицу саму к себе столько раз, сколько условий нам нужно проверить.
Выясним причину эффективности этого метода: каждое новое ВНУТРЕННЕЕ СОЕДИНЕНИЕ отсекает объекты, не соответствующие очередному критерию, еще на этапе выборки, не дожидаясь агрегации всей таблицы.
ВЫБРАТЬ
Тч1.Ссылка КАК Ссылка
ИЗ
Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Тч1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Тч2
ПО Тч1.Ссылка = Тч2.Ссылка
ГДЕ
(Тч1.Свойство = &Свойство1 И Тч1.Значение = &Значение1)
И (Тч2.Свойство = &Свойство2 И Тч2.Значение = &Значение2)
Этот метод отлично подходит, если количество условий фиксировано (например, всегда два или три). Если же количество условий динамическое, текст запроса придется собирать программно в цикле.
Если нам необходимо найти объект по длинному списку свойств (например, 10–20 параметров), загромождать запрос множеством соединений или условий ИЛИ неудобно. В таком случае рассмотрим профессиональный подход с использованием временной таблицы.
Разберем реализацию по этапам:
ТаблицыЗначений).ДополнительныеРеквизиты с этой временной таблицей по обоим полям (свойство и значение).Пример реализации:
// Предположим, ТЗ_Условия уже передана в МенеджерВременныхТаблиц как ВТ_Условия
// В ней колонки: Свойство, Значение
ВЫБРАТЬ
Тч.Ссылка КАК Ссылка
ИЗ
Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Тч
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Условия КАК Условия
ПО Тч.Свойство = Условия.Свойство
И Тч.Значение = Условия.Значение
СГРУППИРОВАТЬ ПО
Тч.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(Тч.Ссылка) = &ОбщееКоличествоУсловий
Иногда удобно использовать вложенные запросы с оператором В. Этот метод читабелен и легко строится динамически.
ВЫБРАТЬ Ссылка ИЗ Справочник.ХарактеристикиНоменклатуры
ГДЕ
Ссылка В (ВЫБРАТЬ Ссылка ИЗ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты ГДЕ Свойство = &С1 И Значение = &З1)
И Ссылка В (ВЫБРАТЬ Ссылка ИЗ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты ГДЕ Свойство = &С2 И Значение = &З2)
Хотя визуально кажется, что это два подзапроса, современные СУБД (MS SQL, PostgreSQL) оптимизируют такие конструкции в «semi-joins», и по скорости они сопоставимы с внутренними соединениями.
Если ваша конфигурация построена на БСП (УТ 11, ERP, КА 2, БП 3), важно помнить несколько моментов:
ДополнительныеСведения. Убедитесь, что вы обращаетесь к нужному источнику.Значение в дополнительных реквизитах имеет составной тип. Для того чтобы индекс по этому полю работал корректно, в параметрах запроса следует передавать значение строго того типа, который ожидается (например, ссылку на конкретный справочник ЗначенияСвойствОбъектов, а не просто строку или число).Свойство и Значение в табличной части. Без этого поиск по сотням тысяч записей будет крайне медленным.Мы проанализировали ситуацию и выяснили, что для отбора по нескольким строкам одной таблицы нельзя использовать простое логическое «И». Наиболее универсальным и "гламурным" решением (как выразился автор темы) является использование группировки СГРУППИРОВАТЬ ПО совместно с условием ИМЕЮЩИЕ КОЛИЧЕСТВО. Это позволяет динамически изменять количество условий, не меняя структуру самого запроса, а лишь передавая разные параметры.
Для высоконагруженных систем рекомендуется использовать метод внутренних соединений или временных таблиц условий, так как они обеспечивают лучшую предсказуемость плана запроса в SQL.