При разработке и доработке систем на платформе 1С:Предприятие мы часто сталкиваемся с задачами, требующими проверки принадлежности элемента справочника к определенной иерархии групп. Типичный пример – это проверка, разрешено ли изменять количество номенклатуры, если она относится к определенным группам. Изначальный запрос, который мы рассмотрим, предназначен для решения именно такой задачи. Он возвращает булево значение (Да/Нет) на основе довольно сложной логики:
ВЫБРАТЬ
ВЫБОР
КОГДА Номенклатура.Ссылка = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА ЛОЖЬ
ИНАЧЕ ИСТИНА
КОНЕЦ КАК Разрешено
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ЭтоГруппа = ЛОЖЬ
И Номенклатура.Родитель В ИЕРАРХИИ
(ВЫБРАТЬ
Конт1_ОбходБлокировки.Номенклатура КАК Номенклатура
ИЗ
РегистрСведений.Конт1_ОбходБлокировки КАК Конт1_ОбходБлокировки
ГДЕ
Конт1_ОбходБлокировки.Разрешено = ИСТИНА
И Конт1_ОбходБлокировки.Номенклатура.ЭтоГруппа = ИСТИНА)
И Номенклатура.Ссылка = &Ссылка
Первоначальный запрос имеет несколько потенциальных проблем, которые могут привести к неэффективности и усложнить поддержку:
ВЫБОР ... КОГДА Номенклатура.Ссылка = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка): В данном случае, если строка найдена (то есть условие ГДЕ выполнилось), Номенклатура.Ссылка никогда не будет пустой ссылкой. Эта часть запроса всегда будет возвращать ИСТИНА, что делает ее бесполезной. Если же записи не найдено, запрос ничего не вернет, а не ЛОЖЬ.В ИЕРАРХИИ с подзапросом в условии ГДЕ: Хотя платформа 1С может обрабатывать такую конструкцию, она часто не является оптимальной. При большом количестве групп в подзапросе или глубокой иерархии справочника Номенклатура, это может привести к значительным задержкам. Платформа преобразует В ИЕРАРХИИ в последовательность SQL-запросов с временными таблицами, что не всегда эффективно.Номенклатура. При переносе элемента или группы в другую иерархию, данные в регистре могут стать неактуальными.Давайте подробно разберем, как мы можем оптимизировать этот запрос и предложить альтернативные подходы для достижения желаемого результата.
Прежде всего, нам необходимо решить проблему с получением гарантированного булевого значения (ИСТИНА или ЛОЖЬ), даже если исходный запрос не находит ни одной записи. Для этого мы можем использовать несколько подходов.
КОЛИЧЕСТВО()Мы обернем основной запрос в еще один, внешний запрос, где используем агрегатную функцию КОЛИЧЕСТВО(). Если количество найденных записей больше нуля, это означает, что условие выполнилось, и мы возвращаем ИСТИНА. В противном случае — ЛОЖЬ.
Рассмотрим пример:
ВЫБРАТЬ
ВЫБОР
КОГДА КОЛИЧЕСТВО(ИсходныйЗапрос.Ссылка) > 0
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Разрешено
ИЗ
(ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ЭтоГруппа = ЛОЖЬ
И Номенклатура.Родитель В ИЕРАРХИИ
(ВЫБРАТЬ
Конт1_ОбходБлокировки.Номенклатура
ИЗ
РегистрСведений.Конт1_ОбходБлокировки КАК Конт1_ОбходБлокировки
ГДЕ
Конт1_ОбходБлокировки.Разрешено = ИСТИНА
И Конт1_ОбходБлокировки.Номенклатура.ЭтоГруппа = ИСТИНА)
И Номенклатура.Ссылка = &Ссылка) КАК ИсходныйЗапрос
В этом варианте, если внутренний запрос не возвращает ни одной строки, КОЛИЧЕСТВО(ИсходныйЗапрос.Ссылка) будет равно 0, и внешний запрос корректно вернет ЛОЖЬ.
ОБЪЕДИНИТЬ ВСЕ с заведомо ложным значениемЭтот метод гарантирует, что запрос всегда вернет хотя бы одну строку. Мы объединяем основной запрос, который возвращает ИСТИНА при нахождении записи, с дополнительным запросом, который всегда возвращает ЛОЖЬ. Затем из объединенного результата мы выбираем максимум или первое значение с определенным порядком, чтобы ИСТИНА имела приоритет.
Посмотрим на реализацию:
ВЫБРАТЬ
МАКСИМУМ(ТаблицаРезультатов.Разрешено) КАК Разрешено
ИЗ
(ВЫБРАТТЬ
ИСТИНА КАК Разрешено,
1 КАК Порядок
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ЭтоГруппа = ЛОЖЬ
И Номенклатура.Родитель В ИЕРАРХИИ
(ВЫБРАТЬ
Конт1_ОбходБлокировки.Номенклатура
ИЗ
РегистрСведений.Конт1_ОбходБлокировки КАК Конт1_ОбходБлокировки
ГДЕ
Конт1_ОбходБлокировки.Разрешено = ИСТИНА
И Конт1_ОбходБлокировки.Номенклатура.ЭтоГруппа = ИСТИНА)
И Номенклатура.Ссылка = &Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЛОЖЬ,
2
) КАК ТаблицаРезультатов
Здесь мы используем поле Порядок, чтобы в случае объединения ИСТИНА и ЛОЖЬ при выборе МАКСИМУМ (или ПЕРВЫЕ 1 с соответствующей сортировкой) всегда выбиралась ИСТИНА. Если же первый пакет не возвращает ничего, то второй пакет (с ЛОЖЬ) обеспечит одну строку в объединенной таблице, и внешний запрос вернет ЛОЖЬ.
ЛЕВОЕ СОЕДИНЕНИЕ для проверки существованияХотя в контексте сложного условия В ИЕРАРХИИ прямое ЛЕВОЕ СОЕДИНЕНИЕ может быть менее очевидным, оно является мощным инструментом для проверки существования связанных записей. В общем случае, если нам нужно проверить наличие записи по простым условиям, мы можем использовать ЛЕВОЕ СОЕДИНЕНИЕ и затем проверять поле из присоединенной таблицы на NULL.
Для нашего случая, чтобы применить ЛЕВОЕ СОЕДИНЕНИЕ, нам потребуется предварительно подготовить список разрешенных групп во временной таблице, а затем проверить наличие пересечения с иерархией текущего элемента. Однако, для исходной конструкции Номенклатура.Родитель В ИЕРАРХИИ (Подзапрос), наиболее прямое применение ЛЕВОЕ СОЕДИНЕНИЕ без В ИЕРАРХИИ будет сложным. Лучше рассматривать его как часть более глубокой оптимизации с использованием временных таблиц.
В ИЕРАРХИИ с использованием временных таблицКонструкция В ИЕРАРХИИ, особенно с подзапросом, часто является узким местом в производительности запросов 1С. Платформа 1С:Предприятие при обработке В ИЕРАРХИИ генерирует несколько запросов на уровне СУБД, создавая временные таблицы для каждого уровня иерархии. При большом объеме данных или глубокой иерархии это может привести к значительному замедлению.
Для повышения эффективности мы можем вручную управлять временными таблицами, разделяя запрос на несколько шагов.
Сначала мы выберем все группы из регистра сведений, которые разрешены, и поместим их во временную таблицу. Это позволяет выполнить один раз относительно "легкий" запрос к регистру.
// Пакет 1: Получаем разрешенные группы
ВЫБРАТЬ РАЗЛИЧНЫЕ
Конт1_ОбходБлокировки.Номенклатура КАК Группа
ПОМЕСТИТЬ ВтРазрешенныеГруппы
ИЗ
РегистрСведений.Конт1_ОбходБлокировки КАК Конт1_ОбходБлокировки
ГДЕ
Конт1_ОбходБлокировки.Разрешено = ИСТИНА
И Конт1_ОбходБлокировки.Номенклатура.ЭтоГруппа = ИСТИНА
;
Теперь основной запрос будет использовать временную таблицу ВтРазрешенныеГруппы вместо подзапроса. Это не полностью устранит В ИЕРАРХИИ, но позволит платформе работать с уже готовым, оптимизированным набором групп, что может быть быстрее, чем каждый раз выполнять подзапрос.
// Пакет 2: Основной запрос с использованием ВтРазрешенныеГруппы
ВЫБРАТЬ
ВЫБОР
КОГДА КОЛИЧЕСТВО(Номенклатура.Ссылка) > 0
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Разрешено
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ЭтоГруппа = ЛОЖЬ
И Номенклатура.Родитель В ИЕРАРХИИ (ВЫБРАТЬ Группа ИЗ ВтРазрешенныеГруппы)
И Номенклатура.Ссылка = &Ссылка
Этот подход значительно улучшает читаемость и потенциально производительность, поскольку набор разрешенных групп вычисляется один раз.
В ИЕРАРХИИ для проверки одной номенклатуры (продвинутый сценарий)Если нам критически важно избавиться от В ИЕРАРХИИ для проверки одной конкретной номенклатуры (как в случае с параметром &Ссылка), мы можем вручную собрать всю иерархию родителей для этой номенклатуры, а затем проверить пересечение с ВтРазрешенныеГруппы.
Пакет 1: Получаем разрешенные группы (аналогично предыдущему)
ВЫБРАТЬ РАЗЛИЧНЫЕ
Конт1_ОбходБлокировки.Номенклатура КАК Группа
ПОМЕСТИТЬ ВтРазрешенныеГруппы
ИЗ
РегистрСведений.Конт1_ОбходБлокировки КАК Конт1_ОбходБлокировки
ГДЕ
Конт1_ОбходБлокировки.Разрешено = ИСТИНА
И Конт1_ОбходБлокировки.Номенклатура.ЭтоГруппа = ИСТИНА
;
Пакет 2: Получаем всех родителей текущей номенклатуры (включая саму номенклатуру)
В 1С нет прямой SQL-функции для получения всех родителей вверх по иерархии для одной ссылки. Обычно для этого используется многократное соединение или программный обход. Однако, можно использовать конструкцию В ИЕРАРХИИ, чтобы получить всех потомков корневых элементов, а затем выбирать тех, которые являются родителями нашей &Ссылка, или, что проще, использовать тот факт, что В ИЕРАРХИИ (&Ссылка) вернет и саму ссылку, и всех её потомков. Если же мы хотим получить родителей, то это может быть сделано через несколько ЛЕВОЕ СОЕДИНЕНИЕ по полю Родитель, если глубина иерархии известна и невелика, или через цикл в коде 1С, добавляя родителей во временную таблицу.
Для примера, используем В ИЕРАРХИИ от родителя текущей номенклатуры, что более близко к изначальной логике:
// Пакет 2: Получаем родителя текущей номенклатуры
ВЫБРАТЬ
Номенклатура.Родитель КАК РодительТекущейНоменклатуры
ПОМЕСТИТЬ ВтРодитель
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка = &Ссылка
И Номенклатура.ЭтоГруппа = ЛОЖЬ
;
// Пакет 3: Проверяем, входит ли родитель в иерархию разрешенных групп
ВЫБРАТЬ
ВЫБОР
КОГДА КОЛИЧЕСТВО(ВтРодитель.РодительТекущейНоменклатуры) > 0
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Разрешено
ИЗ
ВтРодитель КАК ВтРодитель
ГДЕ
ВтРодитель.РодительТекущейНоменклатуры В ИЕРАРХИИ (ВЫБРАТЬ Группа ИЗ ВтРазрешенныеГруппы)
И НЕ ВтРодитель.РодительТекущейНоменклатуры = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
Это по-прежнему использует В ИЕРАРХИИ, но уже с одним параметром, что часто работает быстрее. Если же требуется полностью избавиться от В ИЕРАРХИИ в запросах, то это более сложная задача, требующая построения иерархии вручную в коде или использования рекурсивных запросов, если СУБД это поддерживает (и 1С их транслирует).
Более продвинутый способ, если нужен список всех предков текущей номенклатуры для проверки, может быть таким:
// Пакет 2: Формируем список предков для &Ссылка
ВЫБРАТЬ
&Ссылка КАК ЭлементИлиПредок
ПОМЕСТИТЬ ВтПредки
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка = &Ссылка;
// Итеративно добавляем родителей, пока не достигнем корня или пустой ссылки
// (Этот цикл выполняется в коде 1С, а не в самом запросе)
// Пример логики в коде 1С:
// ТекущийРодитель = НоменклатураСсылка.Родитель;
// Пока ТекущийРодитель <> Справочники.Номенклатура.ПустаяСсылка Цикл
// Добавить ТекущийРодитель во ВтПредки;
// ТекущийРодитель = ТекущийРодитель.Родитель;
// КонецЦикла;
// Пакет 3: Проверяем пересечение предков с разрешенными группами
ВЫБРАТЬ
ВЫБОР
КОГДА КОЛИЧЕСТВО(ВтПредки.ЭлементИлиПредок) > 0
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Разрешено
ИЗ
ВтПредки КАК ВтПредки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВтРазрешенныеГруппы КАК ВтРазрешенныеГруппы
ПО ВтПредки.ЭлементИлиПредок = ВтРазрешенныеГруппы.Группа
Данный подход, где сбор иерархии происходит программно или через множественные пакеты запросов, позволяет полностью избежать В ИЕРАРХИИ в финальной проверке, заменив его простым соединением.
Помимо оптимизации конкретного запроса, мы можем рассмотреть более глобальные изменения в архитектуре системы, которые сделают проверку иерархии номенклатуры более быстрой и поддерживаемой.
Один из самых эффективных способов ускорить проверку — это добавить к справочнику Номенклатура новый булевый реквизит, например, РазрешеноКИзменению. Затем мы можем поддерживать актуальность этого реквизита следующим образом:
Регламентное задание: Настроим регламентное задание, которое периодически (например, раз в час или раз в день) будет пересчитывать значение этого реквизита для всех элементов номенклатуры, основываясь на правилах вхождения в разрешенные группы.
Подписки на события: Создадим подписки на события для справочника Номенклатура (например, при записи элемента или группы, при изменении родителя) и для регистра сведений Конт1_ОбходБлокировки. При изменении разрешенных групп или иерархии номенклатуры, соответствующий код в подписке будет обновлять реквизит РазрешеноКИзменению для затронутых элементов.
Преимущества:
ВЫБРАТЬ
Номенклатура.РазрешеноКИзменению
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка = &Ссылка
Недостатки:
Если мы работаем с конфигурациями, такими как 1С:ERP или 1С:Управление торговлей, у нас есть мощный типовой механизм "Сегменты номенклатуры". Этот механизм разработан специально для гибкой классификации товаров по различным правилам, включая иерархические. Сегменты могут быть:
Для нашей задачи мы можем создать динамический сегмент номенклатуры, который будет включать все элементы, чьи родители (или предки) попадают в список разрешенных групп из регистра сведений. Правила формирования такого сегмента можно настроить с использованием условий "Входит в группу/группы" или "Не входит в группу/группы".
Как это работает:
СегментыНоменклатуры, например, "Разрешенная к изменению номенклатура".Конт1_ОбходБлокировки или просто задается).Использование в запросе:
После того как сегмент настроен и заполнен, запрос на проверку становится значительно проще. Мы можем обращаться к регистру сведений НоменклатураСегмента.
ВЫБРАТЬ
ВЫБОР
КОГДА НоменклатураСегмента.Номенклатура ЕСТЬ НЕ NULL
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Разрешено
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НоменклатураСегмента КАК НоменклатураСегмента
ПО Номенклатура.Ссылка = НоменклатураСегмента.Номенклатура
ГДЕ
Номенклатура.Ссылка = &Ссылка
И НоменклатураСегмента.Сегмент = &СегментРазрешеннойНоменклатуры
Где &СегментРазрешеннойНоменклатуры — это ссылка на созданный нами сегмент. Этот подход перекладывает сложную логику определения иерархии и принадлежности на функционал платформы, обеспечивая высокую производительность и гибкость.
Вне зависимости от конкретного случая, мы всегда должны помнить об общих принципах оптимизации запросов в 1С:
ГДЕ и ПО используют индексированные поля и максимально сужают выборку данных.Конт1_ОбходБлокировки, измерения обычно индексируются автоматически, но дополнительные индексы могут потребоваться для ресурсов или других полей в условиях ГДЕ.В ИЕРАРХИИ: Мы уже обсудили, что В ИЕРАРХИИ может быть неэффективным. Если возможно, заменяйте его на работу с временными таблицами, заранее подготовленными списками элементов или используйте механизмы платформы, которые решают эту задачу более оптимально (например, сегменты).Применяя эти методы, мы сможем значительно повысить производительность и надежность системы, а также сделать код более чистым и поддерживаемым.