Как найти общие элементы между результатами запроса и массивом в 1С

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

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

Способ 1. Использование внутреннего соединения (ВНУТРЕННЕЕ СОЕДИНЕНИЕ)

Если оба набора данных хранятся в базе данных (в табличных частях документов, регистрах сведений или регистрах накопления), оптимальным решением является выполнение одного запроса с конструкцией ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN). Внутреннее соединение отбирает только те записи, для которых выполняется условие сопоставления в обеих соединяемых таблицах. В этом случае отпадает необходимость в пакетных запросах и постобработке результатов во встроенном языке.

Рассмотрим пример запроса, выбирающего общих сотрудников из двух табличных частей:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	РаботаДанные.Сотрудник КАК Сотрудник
    |ИЗ
    |	Документ.РаботаСотрудниковНаОбъекте.Данные КАК РаботаДанные
    |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.НачислениеЗарплаты.Начисления КАК НачислениеЗарплаты
    |		ПО РаботаДанные.Сотрудник = НачислениеЗарплаты.Сотрудник
    |ГДЕ
    |	НачислениеЗарплаты.Ссылка = &СсылкаНачисление
    |	И РаботаДанные.Ссылка = &СсылкаРабота";

Запрос.УстановитьПараметр("СсылкаНачисление", ДокументНачислениеСсылка);
Запрос.УстановитьПараметр("СсылкаРабота", ДокументРаботаСсылка);

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
	// Обработка общих сотрудников
КонецЦикла;

Разберем важные детали построения этого запроса:

  1. Отбор по обоим документам: Обязательно указываем условия ГДЕ для обоих документов. Если опустить условие по второму документу, СУБД выполнит полное сканирование всей таблицы базы данных, что приведет к существенным просадкам производительности.
  2. Сравнение ссылок напрямую: В условии соединения пишем РаботаДанные.Сотрудник = НачислениеЗарплаты.Сотрудник. Не следует писать Сотрудник.Ссылка, так как реквизит Сотрудник уже является ссылочным типом.

Способ 2. Использование временных таблиц с индексацией

Когда требуется соединить большие массивы информации или предварительно сгруппировать данные, рекомендуется помещать промежуточные результаты во временные таблицы с явным созданием индексов по полям соединения.

Посмотрим на пример использования пакетного запроса с временными таблицами:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	НачислениеЗарплаты.Сотрудник КАК Сотрудник
    |ПОМЕСТИТЬ ВТ_Начисления
    |ИЗ
    |	Документ.НачислениеЗарплаты.Начисления КАК НачислениеЗарплаты
    |ГДЕ
    |	НачислениеЗарплаты.Ссылка = &СсылкаНачисление
    |
    |ИНДЕКСИРОВАТЬ ПО
    |	Сотрудник
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	РаботаДанные.Сотрудник КАК Сотрудник
    |ПОМЕСТИТЬ ВТ_Работы
    |ИЗ
    |	Документ.РаботаСотрудниковНаОбъекте.Данные КАК РаботаДанные
    |ГДЕ
    |	РаботаДанные.Ссылка = &СсылкаРабота
    |
    |ИНДЕКСИРОВАТЬ ПО
    |	Сотрудник
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_Начисления.Сотрудник КАК Сотрудник
    |ИЗ
    |	ВТ_Начисления КАК ВТ_Начисления
    |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Работы КАК ВТ_Работы
    |		ПО ВТ_Начисления.Сотрудник = ВТ_Работы.Сотрудник";

Запрос.УстановитьПараметр("СсылкаНачисление", ДокументНачислениеСсылка);
Запрос.УстановитьПараметр("СсылкаРабота", ДокументРаботаСсылка);

Результат = Запрос.Выполнить();

Индексация временных таблиц позволяет оптимизатору СУБД строить эффективные планы выполнения (например, с использованием оператора Merge Join или Index Lookup), что критически важно при обработке сотен тысяч строк.

Способ 3. Передача готового массива в параметры запроса

Если один список элементов уже сформирован в коде 1С (например, получен в виде массива ссылок), а второй список нужно получить из базы данных, передаем массив напрямую в параметр запроса с оператором В (&Массив).

Рассмотрим программную реализацию:


МассивСотрудников = Новый Массив;
// Заполнение массива элементов
МассивСотрудников.Добавить(СсылкаНаСотрудника1);
МассивСотрудников.Добавить(СсылкаНаСотрудника2);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	Начисления.Сотрудник КАК Сотрудник
    |ИЗ
    |	Документ.НачислениеЗарплаты.Начисления КАК Начисления
    |ГДЕ
    |	Начисления.Ссылка = &Ссылка
    |	И Начисления.Сотрудник В (&МассивСотрудников)";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Запрос.УстановитьПараметр("МассивСотрудников", МассивСотрудников);

Результат = Запрос.Выполнить();

Особенности работы с параметром-массивом:

  1. Пустой массив: Если в параметр &МассивСотрудников передать пустой массив, платформа автоматически трансформирует условие В (&Массив) в заведомо ложное (1 = 0), и запрос вернет пустой результат без возникновения ошибок.
  2. Ограничения по размеру: Не передавайте в условие В (&Массив) коллекции, содержащие тысячи элементов. Это приводит к раздуванию текста SQL-запроса (оператор IN (...)) и превышению лимитов параметров СУБД. При больших объемах данных массив следует загрузить в ТаблицаЗначений, передать её в запрос и поместить во временную таблицу.

Способ 4. Сопоставление двух коллекций во встроенном языке 1С

Когда оба набора данных уже находятся в оперативной памяти и выполнять повторный запрос к СУБД нецелесообразно, сравнение производится средствами языка 1С. Важно выбрать правильную структуру данных, чтобы избежать квадратичной сложности алгоритма.

Типичная ошибка — использование метода Массив.Найти() внутри цикла обхода другого массива. Такой подход имеет сложность O(N * M) и при больших массивах приводит к зависанию сеанса. Для быстрого поиска со сложностью O(N + M) используем объект Соответствие:


Функция ПолучитьПересечениеМассивов(Массив1, Массив2) Экспорт
    
    МассивРезультат = Новый Массив;
    ХешТаблица = Новый Соответствие;
    
    // Заполняем Соответствие ключами из первого массива
    Для Каждого Элемент Из Массив1 Цикл
        ХешТаблица.Вставить(Элемент, Истина);
    КонецЦикла;
    
    // Ищем совпадения за время O(1) для каждого элемента
    Для Каждого Элемент Из Массив2 Цикл
        Если ХешТаблица[Элемент] <> Неопределено Тогда
            МассивРезультат.Добавить(Элемент);
            // Удаляем найденный ключ, чтобы избежать дублей в результате
            ХешТаблица.Удалить(Элемент);
        КонецЕсли;
    КонецЦикла;
    
    Возврат МассивРезультат;
    
КонецФункции

Ошибки производительности и правила оптимизации

Выясним основные нюансы, влияющие на скорость выполнения запросов и устойчивость системы при высоких нагрузках:

  1. Разыменование ссылочных полей через точку (Поле.Ссылка): Если реквизит уже имеет ссылочный тип (например, Сотрудник ссылается на СправочникСсылка.Сотрудники), обращение Сотрудник.Ссылка заставляет платформу создавать неявное левое соединение с таблицей справочника. Это приводит к лишней нагрузке на СУБД и является грубой ошибкой разработки.
  2. Избыточное использование ВЫБРАТЬ РАЗЛИЧНЫЕ: Оператор РАЗЛИЧНЫЕ заставляет сервер базы данных выполнять дополнительную сортировку или операцию хеширования для устранения дубликатов. Применяйте его только тогда, когда дубли действительно возможны и их нельзя устранить правильным построением соединений или группировкой СГРУППИРОВАТЬ ПО.
  3. Чтение табличных частей вместо регистров: В реальных прикладных решениях данные о выработке или показателях обычно записываются в регистры. По стандартам 1С запросы следует строить к виртуальным таблицам соответствующих регистров с передачей условий в параметры виртуальных таблиц, а не напрямую к табличным частям проведенных документов.
← На главную