При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 разработчики нередко сталкиваются с необходимостью отобрать только папки (группы) иерархического или подчиненного справочника. Однако при попытке написать привычный запрос с условием на принадлежность к группе запрос возвращает пустой результат. Разберем причины такого поведения встроенного языка запросов 1С 7.7 и подробно рассмотрим все способы решения этой задачи.
Проанализируем исходную конструкцию запроса, которая не дает результата:
// Нерабочий вариант запроса
ТекстЗапроса = "
|Обрабатывать НеПомеченныеНаУдаление;
|Без итогов;
|ТекущийЭлемент = Справочник.Договора.ТекущийЭлемент;
|Владелец = Справочник.Договора.Владелец;
|Группировка Владелец;
|Группировка ТекущийЭлемент;
|Условие(ТекущийЭлемент.ЭтоГруппа = 1);
|Условие(Владелец = ВыбВладелец);
|";
Выясним причину ошибки в данном подходе. Механизм встроенного («черного») запроса 1С 7.7 имеет фундаментальную особенность: на этапе первичной выборки данных из таблиц базы данных запрос обращается исключительно к элементам (листьям дерева иерархии). Сами группы как самостоятельные физические записи в первичную выборку не попадают — они достраиваются механизмом запроса виртуально только при обходе иерархических группировок.
Кроме того, ЭтоГруппа() — это контекстный метод агрегатного объекта платформы, а не отдельный реквизит таблицы базы данных. Когда в секции Условие(...) проверяется выражение ТекущийЭлемент.ЭтоГруппа = 1, платформа вычисляет его для каждого перебираемого элемента. Поскольку перебираются только элементы, для них это значение всегда равно 0, и запрос отсекает абсолютно все строки выборки.
Если перед нами стоит задача получить только те папки, внутри которых есть хотя бы один подчиненный элемент, мы можем использовать группировку по полю Родитель. В этом случае запрос отбирает элементы, берет их непосредственных родителей и сворачивает результат.
Посмотрим на пример правильного построения запроса:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Обрабатывать НеПомеченныеНаУдаление;
|Без итогов;
|Родитель = Справочник.Договора.Родитель;
|Владелец = Справочник.Договора.Владелец;
|Группировка Родитель;
|Условие(Владелец = ВыбВладелец);
|";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка("Родитель") = 1 Цикл
ГруппаДоговоров = Запрос.Родитель;
// Если элемент находится в корне справочника, Родитель будет пустым
Если ГруппаДоговоров.Выбран() = 1 Тогда
Сообщить("Найдена группа: " + ГруппаДоговоров.Наименование + ", Код: " + ГруппаДоговоров.Код);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Разберем важные нюансы этого метода:
Группировка ТекущийЭлемент, благодаря чему одинаковые родители сворачиваются в одну строку.Запрос.Родитель мы получаем полноценную ссылку на группу справочника, через которую доступны все стандартные реквизиты: наименование, код и реквизиты шапки.Если штатный запрос не подходит из-за пропуска пустых групп или медленной работы, самым надежным платформенным решением является перебор через объект Справочник с отключением лишней рекурсии.
Рассмотрим, как правильно настроить выборку, чтобы быстро получить группы без чтения лишних данных:
Спр = СоздатьОбъект("Справочник.Договора");
// Устанавливаем владельца для подчиненного справочника
Спр.ИспользоватьВладельца(ВыбВладелец);
// 0 — отключает автоматический рекурсивный вход в подчиненные группы
Спр.ВключатьПодчиненные(0);
// Выбираем только элементы и группы текущего уровня
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если Спр.ЭтоГруппа() = 1 Тогда
// Обрабатываем найденную группу (включая пустые)
Сообщить("Группа верхнего уровня: " + Спр.Наименование + " [Код: " + Спр.Код + "]");
КонецЕсли;
КонецЦикла;
Если справочник имеет многоуровневую структуру и требуется обойти дерево групп целиком, напишем простую рекурсивную процедуру, которая обходит только папки:
Процедура ОбойтиГруппы(Владелец, ТекущийРодитель = "")
Перем Спр;
Спр = СоздатьОбъект("Справочник.Договора");
Спр.ИспользоватьВладельца(Владелец);
Если ПустоеЗначение(ТекущийРодитель) = 0 Тогда
Спр.ИспользоватьРодителя(ТекущийРодитель);
КонецЕсли;
Спр.ВключатьПодчиненные(0);
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если Спр.ЭтоГруппа() = 1 Тогда
Сообщить("Найдена папка: " + Спр.Наименование);
// Рекурсивно заходим в найденную группу
ОбойтиГруппы(Владелец, Спр.ТекущийЭлемент());
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Такой перебор выполняется значительно быстрее, чем сканирование всех элементов запросом с последующей ручной проверкой в цикле, так как мы пропускаем вложенные элементы и считываем только узлы дерева.
Когда информационная база работает под управлением Microsoft SQL Server, максимальную скорость работы обеспечивает выполнение прямого SQL-запроса (например, через внешнюю компоненту 1CPP или объект ADODB.Connection).
В физической структуре таблиц 1С 7.7 признак группы хранится в отдельной системной колонке:
ISFOLDER — признак типа записи: 1 — группа (папка), 2 — элемент.PARENTEXT — идентификатор владельца для подчиненных справочников.ISMARK — пометка на удаление (1 — помечен, 0 — не помечен).PARENTID — идентификатор родительской папки для иерархических справочников.Посмотрим на пример прямого SQL-запроса к таблице справочника:
// Пример SQL-запроса с типизацией для 1С++
ТекстSQL = "
|SELECT
| ID as [Договор $Справочник.Договора],
| CODE as [Код $Строка],
| DESCR as [Наименование $Строка]
|FROM
| $Справочник.Договора (NOLOCK)
|WHERE
| PARENTEXT = :ВыбВладелец
| AND ISFOLDER = 1
| AND ISMARK = 0
|";
Прямой SQL-запрос выполняется мгновенно даже на базах с миллионами записей и возвращает полный список групп владельца, включая пустые папки любого уровня вложенности.