При разработке сложных отчетов или обработок в системе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью извлечения данных из связанных справочников с учетом иерархии. Типичный пример — получение данных партнера для контрагента, а если у контрагента указан «головной контрагент», то данных партнера именно этого головного звена. Рассмотрим, как превратить громоздкий запрос с повторяющимися условиями в элегантный и производительный код.
Разберем ситуацию, когда в исходном запросе используется многократное повторение конструкции ВЫБОР КОГДА ... ТОГДА для каждого извлекаемого поля (Ссылка, Код, Наименование). Проанализируем типичный пример «нечитаемого» кода:
ВЫБОР
КОГДА Продажи.АналитикаУчетаПоПартнерам.Контрагент.ГоловнойКонтрагент ЕСТЬ NULL
ТОГДА Продажи.АналитикаУчетаПоПартнерам.Контрагент.Партнер.Ссылка
ИНАЧЕ Продажи.АналитикаУчетаПоПартнерам.Контрагент.ГоловнойКонтрагент.Партнер.Ссылка
КОНЕЦ КАК Партнер,
ВЫБОР
КОГДА Продажи.АналитикаУчетаПоПартнерам.Контрагент.ГоловнойКонтрагент ЕСТЬ NULL
ТОГДА Продажи.АналитикаУчетаПоПартнерам.Контрагент.Партнер.Код
ИНАЧЕ Продажи.АналитикаУчетаПоПартнерам.Контрагент.ГоловнойКонтрагент.Партнер.Код
КОНЕЦ КАК ПартнерКод
Такой подход делает запрос избыточным, трудночитаемым и, что немаловажно, тяжелым для СУБД из-за многократного «разыменования» полей через точку. Выясним причину, почему это плохо: каждое обращение через точку в запросе 1С на уровне SQL превращается в LEFT JOIN. В примере выше для каждого поля система будет заново пытаться соединиться с таблицами контрагентов и партнеров.
Посмотрим на более компактный вариант написания того же условия. Вместо длинных конструкций ВЫБОР можно использовать функцию ЕСТЬNULL. Это сократит объем кода, хотя и не избавит полностью от проблем с производительностью при большом количестве полей.
(ЕСТЬNULL(
Продажи.АналитикаУчетаПоПартнерам.Контрагент.ГоловнойКонтрагент.Партнер,
Продажи.АналитикаУчетаПоПартнерам.Контрагент.Партнер
)) КАК Партнер,
(ЕСТЬNULL(
Продажи.АналитикаУчетаПоПартнерам.Контрагент.ГоловнойКонтрагент.Партнер,
Продажи.АналитикаУчетаПоПартнерам.Контрагент.Партнер
)).Код КАК ПартнерКод
Важный нюанс: такая конструкция будет работать корректно только в том случае, если ГоловнойКонтрагент действительно равен NULL (например, при левом соединении). Если же реквизит в базе просто не заполнен, там может храниться Значение(Справочник.Контрагенты.ПустаяСсылка). В этом случае ЕСТЬNULL не сработает, так как пустая ссылка — это не NULL.
Проанализируем наиболее эффективный способ, который соответствует стандартам разработки «1С». Суть заключается в том, чтобы сначала вычислить «правильную» ссылку на партнера во временной таблице, а затем один раз соединиться с ней в основном запросе.
Рассмотрим этот алгоритм по шагам:
ВТ_Партнеры, где для каждого контрагента определяем одну целевую ссылку на партнера.Посмотрим на пример кода:
// Шаг 1: Подготовка связей во временной таблице
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
ЕСТЬNULL(Контрагенты.ГоловнойКонтрагент.Партнер, Контрагенты.Партнер) КАК ЦелевойПартнер
ПОМЕСТИТЬ ВТ_СвязиПартнеров
ИЗ
Справочник.Контрагенты КАК Контрагенты;
// Шаг 2: Основной запрос
ВЫБРАТЬ
Продажи.СуммаОборот,
Связи.ЦелевойПартнер КАК Партнер,
Связи.ЦелевойПартнер.Код КАК ПартнерКод,
Связи.ЦелевойПартнер.Наименование КАК ПартнерНаименование
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СвязиПартнеров КАК Связи
ПО Продажи.АналитикаУчетаПоПартнерам.Контрагент = Связи.Контрагент
Этот метод значительно повышает читаемость и позволяет избежать дублирования логики ВЫБОР для каждого отдельного поля. Кроме того, это оптимизирует нагрузку на сервер базы данных.
Если создание временной таблицы кажется избыточным для маленького запроса, можно использовать прямое соединение с таблицей контрагентов дважды: один раз для основного контрагента, второй раз — для головного.
Разберем ситуацию на примере:
ВЫБРАТЬ
ЕСТЬNULL(ПартнерыГоловные.Ссылка, ПартнерыТекущие.Ссылка) КАК Партнер,
ЕСТЬNULL(ПартнерыГоловные.Код, ПартнерыТекущие.Код) КАК ПартнерКод
ИЗ
РегистрНакопления.Продажи КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
ПО Продажи.АналитикаУчетаПоПартнерам.Контрагент = Контрагенты.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Партнеры КАК ПартнерыТекущие
ПО Контрагенты.Партнер = ПартнерыТекущие.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Партнеры КАК ПартнерыГоловные
ПО Контрагенты.ГоловнойКонтрагент.Партнер = ПартнерыГоловные.Ссылка
Этот способ позволяет избавиться от многострочных ВЫБОР, заменяя их на простые ЕСТЬNULL. Однако стоит помнить, что большое количество соединений также может влиять на скорость выполнения.
При работе с крупными базами данных (например, 1С:ERP или УТ 11) важно учитывать следующие моменты:
Контрагент имеет составной тип данных, проверка ГоловнойКонтрагент ЕСТЬ NULL может работать медленнее, чем предварительная фильтрация по типу ссылки через конструкцию ССЫЛКА Справочник.Контрагенты.Контрагент в ней, если планируете соединять её с большими таблицами оборотов.NULL обычно хранится пустая ссылка. Поэтому в условиях ВЫБОР лучше использовать сравнение с ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка).Иногда встречается предложение формировать текст запроса программно через функции, подставляя куски строк (шаблоны). Рассмотрим пример такого подхода:
ТекстЗапроса = "ВЫБРАТЬ " + ПолучитьКусокЗапроса("Ссылка") + " КАК Партнер";
Важный совет: старайтесь избегать подобной «шаблонизации». Это делает невозможным использование конструктора запросов, крайне затрудняет отладку и чтение кода другими разработчиками. Лучше написать более длинный, но понятный и визуально доступный запрос в консоли, чем собирать его «конструктором» из строковых переменных.
Если вы разрабатываете отчет на СКД, задачу можно упростить еще сильнее. Вы можете вычислить только ссылку на нужного партнера в запросе или через «Вычисляемые поля», а извлечение Кода и Наименования доверить самой системе компоновки. Для этого достаточно в настройках отчета просто выбрать поля НужныйПартнер.Код и НужныйПартнер.Наименование. СКД автоматически и максимально оптимально добавит необходимые соединения в итоговый SQL-запрос.
Подводя итог, можно сказать, что наиболее сбалансированным решением является использование временных таблиц. Это разделяет логику поиска нужного объекта и логику получения его свойств, что делает код профессиональным и легким в поддержке.