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

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

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


|Бренд = Регистр.ОстаткиТМЦ.Номенклатура.Родитель.Родитель;
|Группировка Бренд без групп;

Однако на практике встроенный транслятор запросов 1С 7.7 («чёрный запрос») возвращает в поле Бренд значение непосредственного родителя первого уровня вложенности (Номенклатура.Родитель), полностью игнорируя вторую точку. Разберем причины такого поведения платформы и рассмотрим рабочие способы получения нужного результата.

Причина ограничения платформы 1С 7.7

Проанализируем работу встроенного механизма запросов 1С 7.7. Платформа без проблем обрабатывает цепочки через несколько точек, если они обращаются к разным объектам метаданных (например: Регистр.ОстаткиТМЦ.Номенклатура.БазоваяЕдиница.ОКЕИ). В этом случае транслятор последовательно соединяет физические таблицы между собой.

Ограничение возникает именно при попытке самосоединения (self-join). Реквизит Родитель (в базе данных — поле PARENTID) ссылается на ту же самую таблицу справочника. Встроенный процессор запросов платформы не умеет создавать второй псевдоним (алиас) для таблицы, чтобы связать её саму с собой повторно. В итоге платформа отсекает второе обращение и возвращает значение поля PARENTID первого уровня.

Для решения этой проблемы рассмотрим несколько проверенных подходов.

Способ 1. Постобработка результатов через Таблицу Значений и метод Свернуть

В объектной модели 1С 7.7 обращение по цепочке Элемент.Родитель.Родитель работает штатно и без ограничений. Поэтому самый распространенный и простой вариант — получить в запросе детальные данные по номенклатуре или ее непосредственному родителю, выгрузить результат в объект ТаблицаЗначений, вычислить нужный уровень в цикле и свернуть таблицу.

Разберем по шагам реализацию этого метода:

  1. В тексте запроса строим группировку по номенклатуре.
  2. Выгружаем результат выполнения запроса в ТаблицаЗначений с помощью метода Выгрузить().
  3. Добавляем в таблицу колонку Бренд.
  4. В цикле заполняем колонку обращением через объектную модель.
  5. Сворачиваем таблицу по колонке бренда с суммированием числовых колонок методом Свернуть().

Посмотрим на пример кода:


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

Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    ТЗ = СоздатьОбъект("ТаблицаЗначений");
    Запрос.Выгрузить(ТЗ);
    
    ТЗ.НоваяКолонка("Бренд", "Справочник.Номенклатура");
    
    ТЗ.ВыбратьСтроки();
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
        Родитель1 = ТЗ.Номенклатура.Родитель;
        Если Родитель1.Выбран() = 1 Тогда
            Родитель2 = Родитель1.Родитель;
            Если Родитель2.Выбран() = 1 Тогда
                ТЗ.Бренд = Родитель2;
            Иначе
                ТЗ.Бренд = Родитель1;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    ТЗ.Свернуть("Бренд", "КонОст");
    ТЗ.ВыбратьСтроки();
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
        // Вывод данных в печатную форму
    КонецЦикла;
КонецЕсли;

Способ 2. Иерархический обход дерева группировок запроса

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

Разберем логику:

  1. В запросе объявляем группировку |Группировка Номенклатура;. Платформа автоматически строит иерархическое дерево папок.
  2. При обходе через Запрос.Группировка("Номенклатура") проверяем, является ли текущая запись группой с помощью функции Запрос.ЭтоГруппа().
  3. С помощью метода Запрос.Номенклатура.Уровень() определяем позицию в иерархии и перехватываем итоги только на нужном уровне (например, втором), пропуская детализацию ниже.

Способ 3. Прямой SQL-запрос к базе данных

Для конфигураций, работающих под управлением Microsoft SQL Server с установленными внешними компонентами прямого доступа (например, 1С++), ограничение встроенного транслятора обходится классическим соединением таблиц базы данных. В SQL-запросе мы можем связать таблицу справочника номенклатуры саму с собой необходимое количество раз через псевдонимы.

Посмотрим на пример запроса:


SELECT 
    Дедушка.ID [Бренд $Справочник.Номенклатура],
    SUM(Остатки.КоличествоОстаток) AS [Количество]
FROM 
    $РегистрОстатки.ОстаткиТМЦ(:ВыбДата~,,
                              ,
                              (Номенклатура),
                              (Количество)) AS Остатки
    INNER JOIN $Справочник.Номенклатура AS Номенклатура ON Остатки.Номенклатура = Номенклатура.ID
    LEFT JOIN $Справочник.Номенклатура AS Родитель ON Номенклатура.PARENTID = Родитель.ID
    LEFT JOIN $Справочник.Номенклатура AS Дедушка ON Родитель.PARENTID = Дедушка.ID
GROUP BY 
    Дедушка.ID

Обратите внимание: соединение родительских таблиц обязательно выполняем через LEFT JOIN, а не через INNER JOIN. Если глубина структуры каталога неоднородна и часть позиций лежит на первом или втором уровне, INNER JOIN отсечет такие позиции и исказит итоговые остатки.

Способ 4. Денормализация справочника

Если отчетность в разрезе брендов или категорий строится регулярно на больших объемах данных, постоянный рекурсивный поиск родителей замедляет систему. В таком случае оптимальным архитектурным решением будет создание явного реквизита Бренд в справочнике Номенклатура.

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

← На главную