При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 нередко возникает задача обойти исключительно папки корневого уровня справочника (например, основные категории номенклатуры). Однако разработчики, пытающиеся решить эту задачу через стандартный механизм запросов, сталкиваются с серьезными архитектурными ограничениями встроенного языка. Разберем детально, почему штатный запрос ведет себя некорректно, и рассмотрим правильные и эффективные способы решения задачи.
Проанализируем механику работы штатного языка запросов платформы 7.7. Этот механизм проектировался главным образом для агрегации числовых показателей по регистрам и документам, а также для построения иерархических итогов по элементам. В нем действуют фундаментальные ограничения:
ЭтоГруппа() и Уровень(). В запросе невозможно синтаксически отфильтровать только группы.Посмотрим на пример запроса, который разработчики часто пытаются использовать:
Пустышка = ПолучитьПустоеЗначение("Справочник.Номенклатура");
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Обрабатывать НеПомеченныеНаУдаление;
|Группа = Справочник.Номенклатура.Родитель;
|Условие(Группа.Родитель = Пустышка);
|Группировка Группа;";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
Сообщить(Запрос.Группа.Наименование);
КонецЦикла;
КонецЕсли;
Разберем, почему данный код нельзя считать надежным. Во-первых, он полностью проигнорирует новые или пустые папки, внутри которых еще нет элементов. Во-вторых, если на первом уровне справочника без родителя записан обычный элемент, логика фильтрации по полю Родитель может выдать некорректные результаты. Следовательно, для получения корневых папок использовать встроенный запрос не рекомендуется.
Оптимальным решением в рамках штатной функциональности является использование объектной модели справочника с отключением рекурсивного обхода. Метод ВключатьПодчиненные(0) указывает платформе, что обход должен производиться строго в пределах текущего установленного родителя, без спуска по иерархическому дереву.
Разберем по шагам правильный алгоритм построения выборки:
ИспользоватьРодителя("") или передаем пустое значение справочника, чтобы выборка началась с корневого уровня.ВключатьПодчиненные(0). В этом случае выборка читает только непосредственные строки корня.ВыбратьЭлементы().Спр.ЭтоГруппа() = 1.Спр.ПометкаУдаления() = 0.Посмотрим на практическую реализацию этого метода в программном коде:
Спр = СоздатьОбъект("Справочник.Номенклатура");
// Устанавливаем отбор по корню дерева и отключаем обход подпапок
Спр.ИспользоватьРодителя("");
Спр.ВключатьПодчиненные(0);
// Открываем выборку элементов первого уровня
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
// Проверяем, что текущая запись является группой и не помечена на удаление
Если (Спр.ЭтоГруппа() = 1) И (Спр.ПометкаУдаления() = 0) Тогда
Сообщить("Найдена корневая группа: " + Спр.Наименование + " [Код: " + Спр.Код + "]");
КонецЕсли;
КонецЦикла;
Такой подход выполняется платформой практически мгновенно, гарантированно возвращает даже пустые группы и не требует сторонних компонент.
Если результат работы обязательно требуется получить в виде набора данных, аналогичного результату запроса (например, для передачи в отчет или связывания с другими таблицами), разберем наполнение объекта ТаблицаЗначений через линейную выборку:
ТЗГруппы = СоздатьОбъект("ТаблицаЗначений");
ТЗГруппы.НоваяКолонка("ТекущийЭлемент", "Справочник.Номенклатура");
ТЗГруппы.НоваяКолонка("Код", "Строка");
ТЗГруппы.НоваяКолонка("Наименование", "Строка");
Спр = СоздатьОбъект("Справочник.Номенклатура");
Спр.ИспользоватьРодителя("");
Спр.ВключатьПодчиненные(0);
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если (Спр.ЭтоГруппа() = 1) И (Спр.ПометкаУдаления() = 0) Тогда
ТЗГруппы.НоваяСтрока();
ТЗГруппы.ТекущийЭлемент = Спр.ТекущийЭлемент();
ТЗГруппы.Код = Спр.Код;
ТЗГруппы.Наименование = Спр.Наименование;
КонецЕсли;
КонецЦикла;
В результате мы получаем готовую плоскую таблицу со всеми группами первого уровня, готовую к дальнейшим операциям сортировки, фильтрации или вывода в печатную форму.
В конфигурациях, работающих на SQL-версии платформы с использованием внешних компонент прямого доступа к данным (таких как 1sqlite, класс ODBCRecordset из состава ВК 1S++), задачу можно решить напрямую через базу данных. Это решение извлекает папки первого уровня одним запросом.
Проанализируем структуру хранения иерархии в таблицах 1С 7.7:
ISFOLDER: отвечает за тип записи. Значение 1 соответствует группе справочника, значение 2 — конечному элементу.PARENTID: хранит идентификатор вышестоящей папки. Для корневых элементов и папок первого уровня это поле содержит пустой идентификатор (в базе MS SQL и DBF это строка фиксированной длины из 9 пробелов: ' ').ISMARK: логический флаг пометки на удаление (0 — не помечен, 1 — помечен на удаление).Посмотрим на пример прямого SQL-запроса:
SELECT
Номенклатура.ID AS [Элемент $Справочник.Номенклатура],
Номенклатура.CODE AS Код,
Номенклатура.DESCR AS Наименование
FROM
$Справочник.Номенклатура AS Номенклатура WITH (NOLOCK)
WHERE
Номенклатура.ISFOLDER = 1
AND Номенклатура.PARENTID = $ПустойИд
AND Номенклатура.ISMARK = 0
ORDER BY
Номенклатура.DESCR
Прямой запрос выбирает только физические записи групп корневого уровня, включая пустые папки, и отрабатывает с максимальной производительностью даже на базах объемом в миллионы строк.
Выясним причину выбора конкретного решения в реальной практике:
ВключатьПодчиненные(0) и ЭтоГруппа() = 1 является стандартным, универсальным и высокоскоростным решением для файловых (DBF) и клиент-серверных (SQL) баз без необходимости установки внешних компонент.