Как реализовать левое соединение справочников в запросе 1С 7.7 и избежать зависания при обходе группировок?

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

При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 нередко возникает задача получить полный список элементов одного справочника вместе с подчиненными данными другого справочника (аналог конструкции LEFT JOIN в SQL). Однако при реализации таких запросов и последующем обходе итогов разработчики часто сталкиваются с неочевидным поведением платформы и критическими ошибками зависания.

Разберем подробно, как правильно составить текст запроса для объединения данных, выясним причину зависания при обратном обходе подчиненных выборок и рассмотрим надежные способы обхода результатов.

Постановка задачи и составление запроса с левым соединением

Представим типовую ситуацию: в информационной базе есть справочник ГТД и справочник Партии, в котором реквизит ГТД ссылается на соответствующий элемент. Нам необходимо получить полный список всех номеров ГТД, а для тех из них, которые указаны в партиях, вывести связанные данные (например, саму партию или страну происхождения). Для ГТД, не задействованных в партиях, группировка по партиям должна оставаться пустой.

Если составить стандартный запрос с фильтрацией, в выборку попадут только те элементы, по которым есть пересечение. Чтобы принудительно включить все элементы первого справочника, во встроенном языке запросов 1С 7.7 используется объединение источников через запятую и модификатор Все (или ВсеВошедшиеВЗапрос) в секции группировки.

Посмотрим на корректно составленный текст запроса:


ТекстЗапроса = "
|Обрабатывать НеПомеченныеНаУдаление;
|ГТД = Справочник.ГТД.ТекущийЭлемент, Справочник.Партии.ГТД;
|Партия = Справочник.Партии.ТекущийЭлемент;
|СтранаПроисхождения = Справочник.Партии.СтранаПроисхождения;
|Функция КолвоПартий = Счётчик();
|Группировка ГТД без групп Все;
|Группировка Партия без групп;
|Условие (ПустоеЗначение(Строка(ГТД)) + Найти(Строка(ГТД), ""--"") = 0);";

Разберем ключевые особенности этого текста:

  1. Перечисление источников через запятую: конструкция ГТД = Справочник.ГТД.ТекущийЭлемент, Справочник.Партии.ГТД; указывает платформе, что переменная запроса связывает элементы двух справочников по значению.
  2. Модификатор Все в группировке: указание Группировка ГТД без групп Все; заставляет движок запросов выгружать в итог все существующие элементы справочника ГТД, даже если для них нет соответствующих записей в подчиненной группировке Партия.
  3. Агрегатная функция: строка Функция КолвоПартий = Счётчик(); подсчитывает количество подчиненных записей внутри каждой группировки.

Причина зависания платформы при обратном обходе

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


Пока Запрос.Группировка("ГТД") = 1 Цикл
    // Попытка получить последний элемент партии сразу
    Если Запрос.Группировка("Партия", -1) = 1 Тогда
        // Обработка последней партии
    КонецЕсли;
КонецЦикла;

При таком подходе на платформе 1С 7.7 (в частности, на релизе 7.70.027) происходит полное зависание программы («намертво») в тот момент, когда курсор доходит до ГТД, не имеющего ни одной партии.

Выясним причину этой ошибки: во внутреннем механизме курсоров движка запросов (библиотека backend.dll) при отрицательном шаге обхода (-1) курсор пытается сразу позиционироваться на последний элемент диапазона. Если подвыборка пуста, внутренняя проверка выхода за границы диапазона не отрабатывает корректно, и поток выполнения попадает в бесконечный внутренний цикл.

Способы решения проблемы и варианты обхода

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

  1. Контроль наличия записей через функцию-счетчик:

    Самый надежный способ при использовании параметра -1 — предварительно проверять значение счетчика на уровне родительской группировки. Если счетчик равен 0, вызов подчиненной группировки просто пропускается:

    
    Пока Запрос.Группировка("ГТД") = 1 Цикл
        Если Запрос.КолвоПартий > 0 Тогда
            // Вызов безопасен, так как записи гарантированно есть
            Если Запрос.Группировка("Партия", -1) = 1 Тогда
                Сообщить("ГТД: " + Запрос.ГТД + ", Последняя партия: " + Запрос.Партия);
            КонецЕсли;
        Иначе
            Сообщить("ГТД: " + Запрос.ГТД + " (нет связанных партий)");
        КонецЕсли;
    КонецЦикла;
    
  2. Сортировка подчиненной группировки по убыванию:

    Можно изменить порядок следования партий непосредственно в самом запросе, добавив сортировку по убыванию (например, по коду или дате):

    |Группировка Партия упорядочить по Партия.Код убыв без групп;

    В этом случае нужный последний элемент сразу окажется первым в подвыборке. Для его чтения достаточно вызвать стандартный прямой обход Запрос.Группировка("Партия", 1) ровно один раз. На пустых подвыборках прямой обход штатно возвращает 0 без зависаний.

Альтернативные подходы к оптимизации

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

Если требуется максимальное быстродействие, рассмотрим альтернативные варианты:

  1. Объектный перебор с отбором: если для реквизита ГТД в справочнике Партии установлен признак отбора, можно перебрать справочник ГТД через ВыбратьЭлементы(), а партии получать методом ИспользоватьОтбор("ГТД", СпрГТД.ТекущийЭлемент()). Это работает стабильно и эффективно на уровне встроенных индексов.
  2. Прямые SQL-запросы: в версиях с клиент-серверной базой данных на базе MS SQL Server для получения последней записи наиболее эффективно использовать прямой SQL-запрос с конструкцией LEFT JOIN через компоненты прямого доступа, что переносит всю нагрузку на сервер СУБД.
← На главную