Как работает внутреннее соединение двух наборов данных в СКД и как обеспечить производительность при больших объемах данных?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке сложных отчетов в 1С на базе Системы компоновки данных (СКД) программисты часто сталкиваются с выбором: объединить все таблицы в одном большом SQL-запросе или использовать несколько наборов данных (запросов) и связать их на уровне схемы компоновки. Особую остроту этот вопрос приобретает, когда один из наборов содержит миллионы или даже миллиарды записей, а второй — всего несколько десятков. В этой статье мы детально разберем механику работы соединений в СКД, выясним, где именно происходит объединение данных, и как избежать критических ошибок в производительности.

Механика соединения наборов данных: Сервер приложений против SQL-сервера

Рассмотрим ситуацию, когда у нас есть два набора данных. Первый набор — это огромная таблица оборотов или движений регистра, а второй — небольшая справочная таблица (например, контрагенты с определенным ИНН). Разберем по шагам, как СКД обрабатывает такие запросы. Проанализируем ключевой момент: в отличие от обычного SQL-запроса, где соединение выполняет СУБД (Microsoft SQL Server, PostgreSQL и др.), соединение разных наборов данных в СКД выполняется на сервере приложений 1С.

Это означает следующую последовательность действий системы:

  1. Система выполняет запрос первого набора данных и получает результат в память сервера приложений.
  2. Система выполняет запрос второго набора данных.
  3. В памяти сервера 1С происходит сопоставление строк по полям связи.

Если в первом наборе данных 2 миллиарда записей, а во втором всего 100, и вы не используете параметры связи, сервер приложений попытается выгрузить из базы данных все 2 миллиарда строк. Это неизбежно приведет к переполнению оперативной памяти (Memory Overflow) или крайне медленной работе отчета. Выясним причину: СКД «не знает» о необходимости фильтровать первый набор данных на основе условий второго набора, пока данные не окажутся в ее распоряжении.

Левое или внутреннее соединение: как СКД принимает решение?

В настройках СКД на закладке «Связи наборов данных» нет явного переключателя «Левое» или «Внутреннее». По умолчанию СКД всегда выполняет левое соединение. Однако мы можем повлиять на этот процесс. Проанализируем ситуацию, когда отбор, установленный пользователем, превращает соединение во внутреннее.

Если пользователь устанавливает обязательный отбор по полю, которое присутствует только во втором (присоединяемом) наборе данных, то строки первого набора, для которых не нашлось соответствия во втором, будут отсечены. На уровне итогового результата это выглядит как INNER JOIN. Но важно понимать: на уровне получения данных из базы это все равно может оставаться неоптимальным процессом выгрузки огромного объема данных с последующей фильтрацией в памяти 1С.

Чтобы соединение работало эффективно, необходимо использовать флаг «Обязательная связь». Если этот флаг установлен, СКД будет понимать, что наличие данных во втором наборе критично для результата. Однако для настоящей оптимизации на уровне SQL этого недостаточно.

Оптимизация через параметры связи

Рассмотрим наиболее правильный способ работы с двумя наборами данных — использование параметров связи. Это позволяет СКД передавать значения из одного набора в фильтр другого набора на уровне SQL-запроса. Разберем, как это настроить:

  1. В запросе второго набора данных добавим условие в секцию ГДЕ. Например:
    
    ВЫБРАТЬ
        Контрагенты.Ссылка,
        Контрагенты.ИНН
    ИЗ
        Справочник.Контрагенты КАК Контрагенты
    ГДЕ
        Контрагенты.Ссылка = &ПараметрСвязиКонтрагент
    
  2. На закладке «Связи наборов данных» укажем это поле в колонке «Параметры».

В этом случае система будет выполнять второй запрос итерационно или пакетами, подставляя значения из первого набора. Но внимание! Если основная проблема в том, что первый набор слишком велик, нам нужно действовать наоборот: ограничивать первый набор данными из второго. В СКД «из коробки» это сделать сложнее, так как связи обычно направлены от «ведущего» набора к «ведомому».

Лучшее решение: использование расширения языка запросов СКД

Если стоит задача отфильтровать 2 миллиарда записей по условию из другой таблицы, мы рекомендуем отказаться от двух наборов данных в пользу одного запроса с использованием расширений СКД. Это позволит СУБД выполнить оптимизацию на своем уровне. Посмотрим на пример запроса:


ВЫБРАТЬ
    Продажи.Номенклатура,
    Продажи.Контрагент,
    Продажи.СуммаОборот
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК Продажи
{ГДЕ
    Продажи.Контрагент.* КАК Контрагент,
    (ВЫРАЗИТЬ(Продажи.Контрагент КАК Справочник.Контрагенты)).ИНН КАК ИНН}

Использование конструкции {} позволяет СКД динамически добавлять соединения и условия ГДЕ только тогда, когда пользователь реально установил отбор в настройках отчета. Если пользователь выбрал фильтр по ИНН, СКД сама добавит ВНУТРЕННЕЕ СОЕДИНЕНИЕ со справочником контрагентов в итоговый SQL-запрос. Это самый быстрый и надежный способ обработки больших данных.

Динамическое управление текстом запроса

В некоторых случаях нам нужно программно решать, будет соединение левым или внутренним. Для этого проанализируем возможности модуля объекта отчета. Мы можем использовать событие ПриКомпоновкеРезультата, чтобы модифицировать текст запроса перед выполнением.


Процедура ПриКомпоновкеРезультата(ДанныеРасшифровки, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    
    // Получаем настройки пользователя
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();
    
    // Проверяем наличие определенного отбора
    НужноВнутреннееСоединение = Ложь;
    Для Каждого ЭлементОтбора Из Настройки.Отбор.Элементы Цикл
        Если ТипЗнч(ЭлементОтбора) = Тип("ЭлементОтбораКомпоновкиДанных") 
            И ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ИНН") Тогда
            НужноВнутреннееСоединение = Истина;
        КонецЕсли;
    КонецЦикла;
    
    // При необходимости меняем текст запроса в схеме
    Если НужноВнутреннееСоединение Тогда
        // Логика подмены ТекстаЗапроса или использование другого Набора Данных
    КонецЕсли;
    
    // Выполняем компоновку стандартным методом с обновленной схемой
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
    
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(Результат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);
    
КонецПроцедуры

Подведем итоги

Работа с несколькими наборами данных в СКД — это мощный инструмент, но он требует осторожности при работе с Big Data. Разберем основные правила:

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

← На главную