Почему в запросе 1С 7.7 у групп не заполняется родитель и как это исправить

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Приветствуем всех разработчиков, продолжающих поддерживать и развивать решения на легендарной платформе 1С:Предприятие 7.7. Несмотря на триумфальное шествие платформы 1С 8.3, конфигурации на "семерке" все еще активно используются в малом бизнесе и на различных промышленных предприятиях благодаря своей сверхвысокой скорости работы на старом оборудовании и простоте администрирования.

В этой статье мы подробно разберем одну из самых популярных архитектурных проблем встроенного языка запросов 1С 7.7 (так называемого "черного запроса"), с которой сталкиваются программисты при попытке выгрузить иерархический список справочника (например, справочника Номенклатура) вместе с родительскими группами. Выясним, почему у элементов справочника родительские группы заполняются корректно, а у самих групп поле Родитель остается пустым, и рассмотрим несколько эффективных способов решения этой задачи.

---

Суть проблемы и анализ исходного запроса

Рассмотрим типичный пример запроса, который часто пишут разработчики для получения списка номенклатуры с иерархией:


Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = 
"//{{ЗАПРОС(Сформировать)
|Обрабатывать НеПомеченныеНаУдаление;
|Без итогов;
|Наименование = Справочник.Номенклатура.ТекущийЭлемент;
|ВидНоменклатуры = Справочник.Номенклатура.ВидНоменклатуры;
|ЕдиницаИзмерения = Справочник.Номенклатура.ЕдиницаИзмерения;
|Родитель = Справочник.Номенклатура.Родитель;
|Группировка Наименование;
|"//}}ЗАПРОС

При выполнении этого запроса и последующем обходе группировки по полю Наименование (которое по сути является текущим элементом справочника) мы сталкиваемся со странным поведением системы:

  1. Для конечных элементов справочника переменная запроса Родитель возвращает правильную ссылку на родительскую группу.
  2. Для строк, представляющих собой группы (папки) справочника, переменная запроса Родитель возвращает пустое значение, хотя у этой группы физически есть родительская папка более высокого уровня.

Давайте разберемся, почему так происходит на уровне платформы.

---

Почему стандартный запрос в 1С 7.7 не возвращает реквизиты групп?

Чтобы понять причину, проанализируем внутреннее устройство механизма запросов 1С 7.7. На уровне СУБД (будь то SQL Server или DBF-файлы таблиц) движок запросов 1С 7.7 выбирает исключительно конечные элементы справочника (у которых признак ЭтоГруппа() = 0). Сами группы как отдельные сущности в первичную SQL/DBF-выборку не попадают.

Когда мы выполняем метод обхода результатов запроса Запрос.Группировка() с включенной иерархией, платформа программно на стороне клиента "на лету" достраивает дерево групп для тех элементов, которые вошли в итоговую выборку. Но поскольку сами группы физически не запрашивались базой данных через тело запроса, все связанные переменные запроса (такие как Родитель, Код, ВидНоменклатуры и любые другие реквизиты) для строк-групп остаются неинициализированными (пустыми).

Данные реквизитов корректно заполняются только на конечных элементах.

---

Способ 1. Получение родителя из объекта базы данных при обходе (Рекомендуемый)

Самое простое и элегантное решение проблемы, не требующее изменения самого текста запроса, заключается в том, чтобы брать родительскую группу не из результатов самого запроса, а напрямую из контекста базы данных при позиционировании на строке-группе.

Давайте посмотрим на практический пример реализации этого метода:


Запрос = СоздатьОбъект("Запрос");
// ... Выполнение запроса ...

Пока Запрос.Группировка("Наименование") = 1 Цикл
    // Проверяем, является ли текущая строка группировки группой справочника
    Если Запрос.Наименование.ЭтоГруппа() = 1 Тогда
        // Это группа. Получаем родителя напрямую через обращение к объекту БД
        РодительОбъекта = Запрос.Наименование.Родитель;
        Сообщить("Группа: " + Запрос.Наименование + " | Родитель: " + РодительОбъекта);
    Иначе
        // Это конечный элемент. Берем родителя напрямую из переменной запроса
        РодительОбъекта = Запрос.Родитель;
        Сообщить("Элемент: " + Запрос.Наименование + " | Родитель: " + РодительОбъекта);
    КонецЕсли;
КонецЦикла;

Важное примечание: Обращение вида Запрос.Наименование.Родитель заставляет платформу выполнить дополнительное быстрое обращение к кэшу или базе данных для считывания реквизита конкретного объекта. На небольших и средних справочниках это практически не влияет на производительность.

---

Способ 2. Использование метода запроса ЭтоГруппа()

Для оптимизации кода и явного разделения логики обработки элементов и групп можно использовать встроенную функцию объекта Запрос — метод ЭтоГруппа(). Этот метод возвращает значение 1, если текущая строка выборки является группировочной строкой-родителем, созданной иерархией платформы.

Рассмотрим структуру кода с использованием этого метода:


Пока Запрос.Группировка("Наименование") = 1 Цикл
    // Передаем имя группировки в метод ЭтоГруппа
    Если Запрос.ЭтоГруппа("Наименование") = 1 Тогда
        // Строка является группой. Переменные запроса здесь пусты.
        // Читаем ТекущийЭлемент напрямую для извлечения родителя
        ТекГруппа = Запрос.Наименование; // Получаем ссылку на группу
        РодительГруппы = ТекГруппа.Родитель;
        
        // Теперь мы можем работать с родителем группы
        Если ПустоеЗначение(РодительГруппы) = 0 Тогда
            Сообщить("Родитель группы '" + ТекГруппа + "': " + РодительГруппы);
        КонецЕсли;
    Иначе
        // Обычный элемент. Все переменные запроса заполнены нормально
        РодительЭлемента = Запрос.Родитель;
    КонецЕсли;
КонецЦикла;

Этот подход делает код более структурированным и понятным для других разработчиков, сопровождающих вашу конфигурацию.

---

Способ 3. Заполнение родителя при выгрузке в Таблицу Значений

Часто в практике программирования 1С результат запроса выгружают в таблицу значений с помощью метода Выгрузить() для последующего вывода в форму, на печать или для программной обработки. При выполнении кода Запрос.Выгрузить(ТЗ, 0, 0) строки, соответствующие группам, перенесутся в таблицу значений, но колонка Родитель для них также будет содержать пустые значения.

Давайте проанализируем, как решить эту задачу с помощью постобработки полученной таблицы значений:


ТЗ = СоздатьОбъект("ТаблицаЗначений");
// Выгружаем результаты запроса в таблицу значений со всей иерархией
Запрос.Выгрузить(ТЗ, 0, 0);

// Производим обход строк ТЗ для заполнения пустых родителей у групп
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    // Если колонка Родитель пуста, проверяем, не группа ли это
    Если ПустоеЗначение(ТЗ.Родитель) = 1 Тогда
        // Если в качестве группировочного поля использовалось "Наименование" (ТекущийЭлемент)
        Если ПолучитьПустоеЗначение("Справочник.Номенклатура") <> ТЗ.Наименование Тогда
            Если ТЗ.Наименование.ЭтоГруппа() = 1 Тогда
                // Восстанавливаем родителя группы через прямое обращение к базе данных
                ТЗ.Родитель = ТЗ.Наименование.Родитель;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

После такого цикла таблица значений становится полностью заполненной и готовой к дальнейшему использованию (например, для передачи в дерево значений на форму или для выгрузки во внешние файлы).

---

Способ 4. Отказ от запросов в пользу ВыбратьЭлементы()

Если вам не требуется накладывать сложные условия по реквизитам подчиненных справочников или подсчитывать итоги, использование механизма запросов в 1С 7.7 для простого получения иерархии может быть избыточным. Объектный обход справочника работает напрямую с физическими таблицами и лишен описанных недостатков "черного запроса".

Проанализируем пример кода с использованием объектного метода:


Спр = СоздатьОбъект("Справочник.Номенклатура");
// Параметр 1 в методе ВыбратьЭлементы() включает иерархический порядок обхода
Спр.ВыбратьЭлементы(1); 

Пока Спр.ПолучитьЭлемент() = 1 Цикл
    // Метод Спр.Родитель всегда гарантированно возвращает родительскую группу 
    // как для элементов, так и для групп любого уровня вложенности!
    Если Спр.ЭтоГруппа() = 1 Тогда
        Сообщить("Группа: " + Спр.Наименование + ", Родитель: " + Спр.Родитель);
    Иначе
        Сообщить("Элемент: " + Спр.Наименование + ", Родитель: " + Спр.Родитель);
    КонецЕсли;
КонецЦикла;

Этот способ работает стабильно, предсказуемо и не требует никаких дополнительных ухищрений для восстановления реквизитов родительских веток дерева.

---

В чем разница с платформой 1С:Предприятие 8?

Для разработчиков, привыкших к возможностям современных версий платформы, такое поведение 1С 7.7 может показаться странным багом. На самом деле это просто архитектурная особенность старой версии платформы.

В 1С 8.x язык запросов построен на полноценном декларативном стандарте SQL. Элементы и группы в таблицах СУБД 1С 8 равноправны при выборке данных. Запрос вида:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    Номенклатура.Родитель КАК Родитель
ИЗ
    Справочник.Номенклатура КАК Номенклатура

Сразу вернет заполненное поле Родитель как для элементов, так и для всех родительских групп справочника на любом уровне иерархии без написания дополнительного программного кода обхода результатов.

← На главную