При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 нередко возникает задача получить полный список элементов одного справочника вместе с подчиненными данными другого справочника (аналог конструкции LEFT JOIN в SQL). Однако при реализации таких запросов и последующем обходе итогов разработчики часто сталкиваются с неочевидным поведением платформы и критическими ошибками зависания.
Разберем подробно, как правильно составить текст запроса для объединения данных, выясним причину зависания при обратном обходе подчиненных выборок и рассмотрим надежные способы обхода результатов.
Представим типовую ситуацию: в информационной базе есть справочник ГТД и справочник Партии, в котором реквизит ГТД ссылается на соответствующий элемент. Нам необходимо получить полный список всех номеров ГТД, а для тех из них, которые указаны в партиях, вывести связанные данные (например, саму партию или страну происхождения). Для ГТД, не задействованных в партиях, группировка по партиям должна оставаться пустой.
Если составить стандартный запрос с фильтрацией, в выборку попадут только те элементы, по которым есть пересечение. Чтобы принудительно включить все элементы первого справочника, во встроенном языке запросов 1С 7.7 используется объединение источников через запятую и модификатор Все (или ВсеВошедшиеВЗапрос) в секции группировки.
Посмотрим на корректно составленный текст запроса:
ТекстЗапроса = "
|Обрабатывать НеПомеченныеНаУдаление;
|ГТД = Справочник.ГТД.ТекущийЭлемент, Справочник.Партии.ГТД;
|Партия = Справочник.Партии.ТекущийЭлемент;
|СтранаПроисхождения = Справочник.Партии.СтранаПроисхождения;
|Функция КолвоПартий = Счётчик();
|Группировка ГТД без групп Все;
|Группировка Партия без групп;
|Условие (ПустоеЗначение(Строка(ГТД)) + Найти(Строка(ГТД), ""--"") = 0);";
Разберем ключевые особенности этого текста:
ГТД = Справочник.ГТД.ТекущийЭлемент, Справочник.Партии.ГТД; указывает платформе, что переменная запроса связывает элементы двух справочников по значению.Все в группировке: указание Группировка ГТД без групп Все; заставляет движок запросов выгружать в итог все существующие элементы справочника ГТД, даже если для них нет соответствующих записей в подчиненной группировке Партия.Функция КолвоПартий = Счётчик(); подсчитывает количество подчиненных записей внутри каждой группировки.Проанализируем проблему, возникающую при обработке полученного запроса. Довольно часто для решения прикладной задачи требуется получить не все подчиненные партии, а только самую последнюю. Для оптимизации разработчики нередко вызывают метод обхода группировки в обратном направлении, передавая второй параметр со значением -1:
Пока Запрос.Группировка("ГТД") = 1 Цикл
// Попытка получить последний элемент партии сразу
Если Запрос.Группировка("Партия", -1) = 1 Тогда
// Обработка последней партии
КонецЕсли;
КонецЦикла;
При таком подходе на платформе 1С 7.7 (в частности, на релизе 7.70.027) происходит полное зависание программы («намертво») в тот момент, когда курсор доходит до ГТД, не имеющего ни одной партии.
Выясним причину этой ошибки: во внутреннем механизме курсоров движка запросов (библиотека backend.dll) при отрицательном шаге обхода (-1) курсор пытается сразу позиционироваться на последний элемент диапазона. Если подвыборка пуста, внутренняя проверка выхода за границы диапазона не отрабатывает корректно, и поток выполнения попадает в бесконечный внутренний цикл.
Рассмотрим, как правильно организовать обход результатов без риска зависания программы и без выгрузки данных в промежуточную таблицу значений.
Самый надежный способ при использовании параметра -1 — предварительно проверять значение счетчика на уровне родительской группировки. Если счетчик равен 0, вызов подчиненной группировки просто пропускается:
Пока Запрос.Группировка("ГТД") = 1 Цикл
Если Запрос.КолвоПартий > 0 Тогда
// Вызов безопасен, так как записи гарантированно есть
Если Запрос.Группировка("Партия", -1) = 1 Тогда
Сообщить("ГТД: " + Запрос.ГТД + ", Последняя партия: " + Запрос.Партия);
КонецЕсли;
Иначе
Сообщить("ГТД: " + Запрос.ГТД + " (нет связанных партий)");
КонецЕсли;
КонецЦикла;
Можно изменить порядок следования партий непосредственно в самом запросе, добавив сортировку по убыванию (например, по коду или дате):
|Группировка Партия упорядочить по Партия.Код убыв без групп;
В этом случае нужный последний элемент сразу окажется первым в подвыборке. Для его чтения достаточно вызвать стандартный прямой обход Запрос.Группировка("Партия", 1) ровно один раз. На пустых подвыборках прямой обход штатно возвращает 0 без зависаний.
При работе с большими объемами данных важно учитывать внутренние ограничения запросов 1С 7.7. Использование модификатора Все в комбинации с несколькими источниками формирует объемные временные таблицы на стороне клиента, что может снижать скорость выполнения.
Если требуется максимальное быстродействие, рассмотрим альтернативные варианты:
ГТД в справочнике Партии установлен признак отбора, можно перебрать справочник ГТД через ВыбратьЭлементы(), а партии получать методом ИспользоватьОтбор("ГТД", СпрГТД.ТекущийЭлемент()). Это работает стабильно и эффективно на уровне встроенных индексов.LEFT JOIN через компоненты прямого доступа, что переносит всю нагрузку на сервер СУБД.