Слияние двух массивов на встроенном языке 1С: от простого цикла до запроса

· 11 мин

Содержание

Встроенного метода «склеить два массива» у объекта Массив в 1С нет. Объединить 2 массива можно циклом Для Каждого с методом Добавить(), а в конфигурациях на БСП — одной строкой ОбщегоНазначенияКлиентСервер.ДополнитьМассив(МассивА, МассивБ). Если нужны только уникальные значения, третий параметр этой процедуры ставят в Истина или убирают дубли через Соответствие.

Выбор способа зависит от трёх вещей: нужны ли дубли, важен ли порядок и сколько элементов в массивах. Для сотни строк хватит цикла, для десятков тысяч ссылок лучше Соответствие или запрос с ОБЪЕДИНИТЬ. Код ниже — встроенный язык платформы 8.3, без привязки к конкретной конфигурации, кроме отдельно отмеченных процедур БСП.

Что нужно знать о массивах до слияния

Три свойства массива определяют, какой способ объединения выбрать и где можно ошибиться.

  • 🔢 Индексы с нуля. Первый элемент — Массив[0], последний — Массив[Массив.Количество() - 1]. Метод ВГраница() у пустого массива возвращает -1.
  • 🔗 Передача по ссылке. Массив — объект. Строка МассивВ = МассивА не создаёт копию: обе переменные смотрят на один и тот же массив. Добавили элемент в МассивВ — он появился и в МассивА.
  • 🧩 Любые типы внутри. В одном массиве могут лежать строка, число, ссылка и структура. Для сравнения при уникализации это важно: структуры и таблицы сравниваются по ссылке на объект, а не по содержимому.

Отсюда первое правило: если исходные массивы ещё понадобятся, объединяйте в новый массив, а не дописывайте второй в первый.

Копию массива в одну строку делают через фиксированный массив:

КопияА = Новый Массив(Новый ФиксированныйМассив(МассивА));

Копия поверхностная: если внутри лежат структуры, обе копии будут ссылаться на одни и те же структуры.

Объединение двух массивов циклом

Базовый способ работает везде: на сервере, на тонком клиенте, во внешней обработке, в конфигурации без БСП.

Функция ОбъединитьМассивы(МассивА, МассивБ)

	Результат = Новый Массив;

	Для Каждого Элемент Из МассивА Цикл
		Результат.Добавить(Элемент);
	КонецЦикла;

	Для Каждого Элемент Из МассивБ Цикл
		Результат.Добавить(Элемент);
	КонецЦикла;

	Возврат Результат;

КонецФункции

Порядок сохраняется: сначала все элементы первого массива, потом второго. Дубли остаются — если значение есть в обоих массивах, оно попадёт в результат дважды.

Функцию удобно положить в общий модуль с флажками «Клиент» и «Сервер», чтобы вызывать её из любого контекста.

Дописать второй массив в первый

Если исходный МассивА больше не нужен, создавать третий массив незачем:

Для Каждого Элемент Из МассивБ Цикл
	МассивА.Добавить(Элемент);
КонецЦикла;

Не дописывайте массив сам в себя циклом Для Каждого Элемент Из МассивА с МассивА.Добавить(). Коллекция растёт по ходу обхода, и результат будет не тот, что вы ждёте. Сначала сделайте копию.

Готовая процедура из БСП

В конфигурациях на Библиотеке стандартных подсистем (Бухгалтерия 3.0, ЗУП 3.1, УТ 11, ERP и другие) есть процедура общего модуля:

ОбщегоНазначенияКлиентСервер.ДополнитьМассив(МассивА, МассивБ);
// уникальные значения:
ОбщегоНазначенияКлиентСервер.ДополнитьМассив(МассивА, МассивБ, Истина);

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

Вставка элементов: Добавить и Вставить

Метод Добавить(Значение) ставит элемент в конец. Метод Вставить(Индекс, Значение) — на позицию с указанным индексом, сдвигая остальные вправо.

МетодКуда ставитКогда использовать
Добавить(Значение)В конецОбычное слияние «первый + второй»
Вставить(0, Значение)В началоВторой массив должен идти перед первым
Вставить(Индекс, Значение)На позициюВклеить массив в середину
Установить(Индекс, Значение)Заменяет элементЗаполнение заранее созданного массива

Вставить второй массив в начало первого

Если вставлять элементы по одному в позицию 0, порядок перевернётся. Правильно — сдвигать позицию вставки:

Позиция = 0;
Для Каждого Элемент Из МассивБ Цикл
	МассивА.Вставить(Позиция, Элемент);
	Позиция = Позиция + 1;
КонецЦикла;

Было: МассивА = [1, 2], МассивБ = [8, 9]. Стало: [8, 9, 1, 2].

Вклеить массив в середину

Тот же приём, только стартовая позиция другая. Вставить МассивБ после второго элемента МассивА:

Позиция = 2;
Для Каждого Элемент Из МассивБ Цикл
	МассивА.Вставить(Позиция, Элемент);
	Позиция = Позиция + 1;
КонецЦикла;

Индекс вставки допустим от 0 до МассивА.Количество() включительно. Значение Количество() равносильно добавлению в конец.

Чередование элементов

Иногда нужно не склеить, а перемешать по порядку: первый из А, первый из Б, второй из А и так далее. Длина массивов может отличаться:

Результат = Новый Массив;
Макс = Макс(МассивА.Количество(), МассивБ.Количество());

Для Индекс = 0 По Макс - 1 Цикл
	Если Индекс < МассивА.Количество() Тогда
		Результат.Добавить(МассивА[Индекс]);
	КонецЕсли;
	Если Индекс < МассивБ.Количество() Тогда
		Результат.Добавить(МассивБ[Индекс]);
	КонецЕсли;
КонецЦикла;

Если оба массива пустые, Макс - 1 равно -1, цикл не выполнится ни разу, ошибки не будет.

Как объединить массивы без повторов

Самый частый запрос на практике — объединить два списка ссылок (номенклатура, контрагенты, документы) так, чтобы каждая ссылка встретилась один раз.

Медленный способ: Найти в цикле

Результат = Новый Массив(Новый ФиксированныйМассив(МассивА));
Для Каждого Элемент Из МассивБ Цикл
	Если Результат.Найти(Элемент) = Неопределено Тогда
		Результат.Добавить(Элемент);
	КонецЕсли;
КонецЦикла;

Метод Найти() перебирает массив от начала до конца. Для массивов по 50–100 элементов разницы не заметно. Но при 20 000 элементах в каждом это уже до 400 млн сравнений, и код будет работать минуты.

Кроме того, этот вариант не чистит дубли внутри самого МассивА — если там они были, они останутся.

Быстрый способ: Соответствие

Ключи Соответствия уникальны, а поиск по ключу не перебирает коллекцию целиком. Это стандартный приём для уникализации:

Функция ОбъединитьБезПовторов(МассивА, МассивБ)

	Встречено = Новый Соответствие;
	Результат = Новый Массив;

	Для Каждого Элемент Из МассивА Цикл
		Если Встречено[Элемент] = Неопределено Тогда
			Встречено.Вставить(Элемент, Истина);
			Результат.Добавить(Элемент);
		КонецЕсли;
	КонецЦикла;

	Для Каждого Элемент Из МассивБ Цикл
		Если Встречено[Элемент] = Неопределено Тогда
			Встречено.Вставить(Элемент, Истина);
			Результат.Добавить(Элемент);
		КонецЕсли;
	КонецЦикла;

	Возврат Результат;

КонецФункции

Порядок первого появления сохраняется, дубли убираются и между массивами, и внутри каждого из них.

💡 В БСП есть готовая ОбщегоНазначенияКлиентСервер.СвернутьМассив(Массив) — она возвращает массив без повторов. Сначала склеиваете циклом, потом сворачиваете.

Строки в разном регистре

Для массива и соответствия "ООО Ромашка" и "ооо ромашка" — разные значения. Если строки пришли из разных источников (Excel, ввод пользователя), нормализуйте ключ:

Ключ = ВРег(СокрЛП(Элемент));
Если Встречено[Ключ] = Неопределено Тогда
	Встречено.Вставить(Ключ, Истина);
	Результат.Добавить(Элемент);
КонецЕсли;

В результат попадает исходная строка, а сравнение идёт по очищенному ключу.

Массивы структур: уникальность по полю

Две структуры с одинаковым содержимым — разные объекты, Найти() их не сочтёт дублями. Уникализируйте по ключевому полю:

Для Каждого Строка Из МассивБ Цикл
	Если Встречено[Строка.Код] = Неопределено Тогда
		Встречено.Вставить(Строка.Код, Истина);
		Результат.Добавить(Строка);
	КонецЕсли;
КонецЦикла;

Таблица значений как промежуточный контейнер

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

Пример: два массива структур Номенклатура + Количество из разных документов, нужно получить итог по каждой позиции.

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура");
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

Для Каждого Источник Из МассивА Цикл
	ЗаполнитьЗначенияСвойств(ТЗ.Добавить(), Источник);
КонецЦикла;
Для Каждого Источник Из МассивБ Цикл
	ЗаполнитьЗначенияСвойств(ТЗ.Добавить(), Источник);
КонецЦикла;

ТЗ.Свернуть("Номенклатура", "Количество");
ТЗ.Сортировать("Номенклатура");

После Свернуть() в таблице одна строка на каждую номенклатуру, количество просуммировано.

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

ТЗ.Свернуть("Значение");
ТЗ.Сортировать("Значение");
Результат = ТЗ.ВыгрузитьКолонку("Значение");

ТаблицаЗначений недоступна на тонком клиенте. В модуле управляемой формы этот код пишите в процедуре с директивой &НаСервере, иначе получите ошибку о неизвестном типе.

У самого массива метода сортировки нет. Для сортировки без таблицы используют СписокЗначений: ЗагрузитьЗначения(Массив), затем СортироватьПоЗначению() и ВыгрузитьЗначения().

Объединение массивов в запросе

Если элементы — ссылки на объекты базы, объединение и уникализацию можно отдать СУБД. Это быстрее циклов на больших объёмах и сразу даёт сортировку и доступ к реквизитам.

Вариант 1: два параметра и условие В

Самый короткий путь для массивов ссылок одного типа:

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

Запрос.УстановитьПараметр("МассивА", МассивА);
Запрос.УстановитьПараметр("МассивБ", МассивБ);

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

Дублей не будет: каждая запись справочника выбирается один раз. Заменив ИЛИ на И, получите пересечение — элементы, которые есть в обоих массивах.

Вариант 2: ОБЪЕДИНИТЬ через временные таблицы

Для массивов строк, чисел или ссылок разных типов массивы сначала превращают в таблицы значений с типизированной колонкой, а затем помещают во временные таблицы:

ОписаниеСтроки = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150));

ТаблицаА = Новый ТаблицаЗначений;
ТаблицаА.Колонки.Добавить("Значение", ОписаниеСтроки);
Для Каждого Элемент Из МассивА Цикл
	ТаблицаА.Добавить().Значение = Элемент;
КонецЦикла;

ТаблицаБ = ТаблицаА.СкопироватьКолонки();
Для Каждого Элемент Из МассивБ Цикл
	ТаблицаБ.Добавить().Значение = Элемент;
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Т.Значение КАК Значение
|ПОМЕСТИТЬ ВТ_А
|ИЗ
|	&ТаблицаА КАК Т
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	Т.Значение КАК Значение
|ПОМЕСТИТЬ ВТ_Б
|ИЗ
|	&ТаблицаБ КАК Т
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ_А.Значение КАК Значение
|ИЗ
|	ВТ_А КАК ВТ_А
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ
|	ВТ_Б.Значение
|ИЗ
|	ВТ_Б КАК ВТ_Б";

Запрос.УстановитьПараметр("ТаблицаА", ТаблицаА);
Запрос.УстановитьПараметр("ТаблицаБ", ТаблицаБ);

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

Разница между двумя операторами:

ОператорДублиСкорость
ОБЪЕДИНИТЬУбираетМедленнее: СУБД ищет повторы
ОБЪЕДИНИТЬ ВСЕОставляетБыстрее: просто склейка

Для строк строковый квалификатор обязателен: неограниченная строка в запросе ведёт себя плохо при сравнении и группировке. 150 символов в примере — подберите длину под свои данные.

Скорость на больших массивах

Пока массивы небольшие, выбирайте самый читаемый способ. Когда счёт идёт на десятки тысяч элементов, начинает играть роль алгоритм.

СпособПодходит дляОграничения
Цикл с Добавить()Любой объём, дубли не важныДубли остаются
Найти() в циклеДо нескольких сотен элементовВремя растёт квадратично
СоответствиеТысячи и десятки тысяч, уникальностьНужен ключ, сравнимый по значению
Таблица значений + Свернуть()Свёртка с суммами, сортировкаТолько сервер и толстый клиент
Запрос с В или ОБЪЕДИНИТЬСсылки, нужны реквизитыСерверный вызов, подготовка таблиц

Что ещё ускоряет работу:

  • ⚡ Не гоняйте массив между клиентом и сервером. Каждый серверный вызов сериализует параметры. Соберите и объедините данные в одной процедуре &НаСервере и верните на клиент только результат.
  • 📦 Не вызывайте Количество() и Найти() там, где можно обойтись без них. В цикле Для Каждого счётчик не нужен вовсе.
  • 🧮 Создавайте массив нужного размера, если он известен. Новый Массив(МассивА.Количество() + МассивБ.Количество()) создаёт массив, заполненный значениями Неопределено, который потом заполняют через Установить() или присваивание по индексу. Выигрыш небольшой, но для очень больших массивов заметен.
  • 🗄️ Ссылки — в запрос. Если после объединения вы всё равно читаете реквизиты элементов через точку, одним запросом выйдет быстрее, чем объединить в цикле и потом обращаться к каждому элементу.

Пример с заранее заданным размером:

КолА = МассивА.Количество();
КолБ = МассивБ.Количество();
Результат = Новый Массив(КолА + КолБ);

Для Индекс = 0 По КолА - 1 Цикл
	Результат[Индекс] = МассивА[Индекс];
КонецЦикла;
Для Индекс = 0 По КолБ - 1 Цикл
	Результат[КолА + Индекс] = МассивБ[Индекс];
КонецЦикла;

Важно не перепутать: если создать массив размером КолА + КолБ и потом вызывать Добавить(), элементы встанут после пустых значений и массив окажется вдвое длиннее.

Ошибки при слиянии массивов и их отладка

Диагностика: выберите симптом

Для отладки удобно остановиться точкой останова после объединения и открыть результат в окне «Вычислить выражение» (Shift+F9) — там видно количество элементов и значение каждого. Для проверки на дубли в отладчике можно вычислить ОбщегоНазначенияКлиентСервер.СвернутьМассив(Результат).Количество() и сравнить с Результат.Количество().

Можно ли объединить массивы одной строкой без цикла и без БСП?
Нет. У объекта Массив нет метода конкатенации. Без БСП нужен цикл или своя функция в общем модуле.
Чем Массив отличается от ФиксированныйМассив при объединении?
Фиксированный массив нельзя изменять: методов Добавить и Вставить у него нет. Его можно читать в цикле как источник, но приёмником должен быть обычный Массив.
Как найти элементы, которые есть только в одном из массивов?
В БСП для этого есть ОбщегоНазначенияКлиентСервер.РазностьМассивов(МассивА, МассивБ) — она возвращает элементы первого массива, которых нет во втором. Без БСП — через Соответствие из второго массива и проверку каждого элемента первого.
Как объединить два массива одинаковой длины в пары?
Пройдите по индексу от 0 до Количество() - 1 и для каждого индекса создайте Новый Структура("Ключ, Значение", МассивА[Индекс], МассивБ[Индекс]) или строку таблицы значений с двумя колонками.
Сохранится ли порядок элементов после ОБЪЕДИНИТЬ в запросе?
Нет. Без УПОРЯДОЧИТЬ ПО порядок строк в результате запроса не гарантирован. Если порядок важен, добавьте сортировку или объединяйте в коде.