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