Как найти совпадения и различия двух списков значений в 1С 8.3
Содержание
Чтобы сравнить два списка значений в 1С, есть три рабочих пути. Для списков до пары тысяч элементов хватит цикла с НайтиПоЗначению(). Для больших — индекс через Соответствие, он находит элемент почти мгновенно. Если данные всё равно идут в запрос или нужно сравнение по нескольким полям, используйте запрос с временными таблицами и ПОЛНОЕ СОЕДИНЕНИЕ.
Ниже каждый способ с примером кода, который можно вставить в общий модуль или обработку. Всё написано для платформы 8.3 и работает в любой конфигурации: это возможности встроенного языка, а не конкретного прикладного решения.
Какой способ сравнения списков выбрать
Сначала решите, что именно нужно получить: признак «списки одинаковые», перечень общих элементов или три группы — «только в первом», «только во втором», «в обоих».
| Способ | Когда подходит | Скорость на больших списках | Подводные камни |
|---|---|---|---|
НайтиПоЗначению() в цикле | до 1–2 тыс. элементов, быстрый код «на месте» | медленно: перебор каждого со всеми | квадратичная сложность |
Массив.Найти() | то же, но данные уже в массиве | так же медленно | возвращает индекс, а не элемент |
Соответствие как индекс | десятки тысяч элементов, клиент или сервер | быстро | строки сравниваются с учётом регистра |
| Запрос с временными таблицами | ссылки, сравнение с данными базы, несколько полей | быстро, работает СУБД | нужны типизированные колонки |
ТаблицаЗначений.Свернуть() | сравнение по нескольким полям и суммам без запроса | быстро | числовые колонки нужно типизировать |
СравнениеЗначений + сортировка | нужен порядок, слияние отсортированных списков | быстро | код длиннее |
💡 Цикл в цикле на 10 000 × 10 000 элементов — это до 100 млн сравнений. Соответствие на тех же данных делает около 20 000 операций: 10 000 вставок и 10 000 поисков.
Почему «Список1 = Список2» не работает и как проверить равенство целиком
Частая ошибка новичка:
Если Список1 = Список2 Тогда
// сюда код не попадёт, даже если содержимое одинаковое
КонецЕсли;
Оператор = для коллекций сравнивает ссылки на объекты, а не содержимое. Два разных объекта СписокЗначений с одинаковыми элементами дают Ложь. Это касается и массивов, и структур, и таблиц значений.
Проверка «тот же набор, порядок не важен»
Проще всего сравнить количество и проверить вхождение каждого элемента через соответствие:
Функция СпискиСовпадают(Список1, Список2) Экспорт
Если Список1.Количество() <> Список2.Количество() Тогда
Возврат Ложь;
КонецЕсли;
Счетчики = Новый Соответствие;
Для Каждого Элемент Из Список1 Цикл
Было = Счетчики.Получить(Элемент.Значение);
Счетчики.Вставить(Элемент.Значение, ?(Было = Неопределено, 0, Было) + 1);
КонецЦикла;
Для Каждого Элемент Из Список2 Цикл
Было = Счетчики.Получить(Элемент.Значение);
Если Было = Неопределено Или Было = 0 Тогда
Возврат Ложь;
КонецЕсли;
Счетчики.Вставить(Элемент.Значение, Было - 1);
КонецЦикла;
Возврат Истина;
КонецФункции
Счётчик, а не флаг, нужен из-за повторов. Списки [А, А, Б] и [А, Б, Б] одного размера, но разные — функция это увидит.
Проверка «то же и в том же порядке»
Если важен порядок, достаточно одного цикла по индексу:
Для Индекс = 0 По Список1.Количество() - 1 Цикл
Если Список1[Индекс].Значение <> Список2[Индекс].Значение Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Количество элементов проверьте до цикла, как в первой функции. Иначе на коротком втором списке получите ошибку выхода индекса за границу.
Поиск совпадений и различий через НайтиПоЗначению()
Метод НайтиПоЗначению() возвращает ЭлементСпискаЗначений или Неопределено, если значения в списке нет. На этом строится самое короткое решение.
Функция ЭлементыТолькоВПервом(Список1, Список2) Экспорт
Результат = Новый СписокЗначений;
Для Каждого Элемент Из Список1 Цикл
Если Список2.НайтиПоЗначению(Элемент.Значение) = Неопределено Тогда
Результат.Добавить(Элемент.Значение, Элемент.Представление);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Чтобы получить общие элементы, поменяйте условие на <> Неопределено. «Только во втором» — тот же вызов с переставленными параметрами.
Тот же приём для массивов
Если на входе массивы, используйте Найти(). Он возвращает индекс найденного элемента или Неопределено:
Общие = Новый Массив;
Для Каждого Значение Из Массив1 Цикл
Если Массив2.Найти(Значение) <> Неопределено
И Общие.Найти(Значение) = Неопределено Тогда
Общие.Добавить(Значение);
КонецЕсли;
КонецЦикла;
Вторая проверка убирает дубли из результата. Список значений и массив переводятся друг в друга одной строкой: Список.ВыгрузитьЗначения() даёт массив, Список.ЗагрузитьЗначения(Массив) — обратно.
Оба метода перебирают коллекцию с начала до первого совпадения. Внутри цикла это превращается в перебор «каждый с каждым». На списке из 50 000 номенклатурных позиций обработка будет думать минуты.
Быстрое сравнение больших списков через Соответствие
Соответствие ищет по ключу без перебора. Строим индекс по каждому списку и за два прохода раскладываем значения на три группы.
Функция СравнитьСпискиЗначений(Список1, Список2) Экспорт
Индекс1 = Новый Соответствие;
Для Каждого Элемент Из Список1 Цикл
Индекс1.Вставить(Элемент.Значение, Истина);
КонецЦикла;
Индекс2 = Новый Соответствие;
Для Каждого Элемент Из Список2 Цикл
Индекс2.Вставить(Элемент.Значение, Истина);
КонецЦикла;
Результат = Новый Структура;
Результат.Вставить("Общие", Новый Массив);
Результат.Вставить("ТолькоВПервом", Новый Массив);
Результат.Вставить("ТолькоВоВтором", Новый Массив);
Для Каждого КлючИЗначение Из Индекс1 Цикл
Если Индекс2.Получить(КлючИЗначение.Ключ) = Неопределено Тогда
Результат.ТолькоВПервом.Добавить(КлючИЗначение.Ключ);
Иначе
Результат.Общие.Добавить(КлючИЗначение.Ключ);
КонецЕсли;
КонецЦикла;
Для Каждого КлючИЗначение Из Индекс2 Цикл
Если Индекс1.Получить(КлючИЗначение.Ключ) = Неопределено Тогда
Результат.ТолькоВоВтором.Добавить(КлючИЗначение.Ключ);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Обход идёт по соответствиям, а не по исходным спискам. Поэтому повторы схлопываются сами, и в результате каждое значение встречается один раз.
Пример. Список1 — [Склад А, Склад Б, Склад В], Список2 — [Склад Б, Склад В, Склад Г, Склад Г]. Результат: общие — Б и В, только в первом — А, только во втором — Г (один раз, хотя в исходнике дважды).
💡 Порядок обхода соответствия не гарантирован. Если результат нужно показать пользователю, загрузите массив в список значений и вызовите СортироватьПоЗначению().
Какие значения годятся в ключ
- ✅ Строки, числа, даты, булево, ссылки на справочники и документы, значения перечислений — сравниваются по значению.
- ⚠️ Структуры, массивы, таблицы значений — сравниваются по ссылке на объект. Две одинаковые структуры станут двумя разными ключами.
- ⚠️ Строки — с учётом регистра: «ООО Ромашка» и «ооо ромашка» дадут два ключа.
Сравнение списков значений запросом с временными таблицами
Если в списках ссылки, а сравнение потом нужно дополнить данными базы (остатками, реквизитами), удобнее отдать работу СУБД.
Короткий вариант через параметр-список
Для небольших списков достаточно условия В:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&Список1)
| И НЕ Номенклатура.Ссылка В (&Список2)";
Запрос.УстановитьПараметр("Список1", Список1);
Запрос.УстановитьПараметр("Список2", Список2);
Параметром можно передать и СписокЗначений, и Массив. Запрос вернёт элементы, которые есть в первом списке и отсутствуют во втором.
Полный вариант: три группы одним запросом
Для тысяч элементов и полной картины загрузите списки во временные таблицы с индексом. Колонке обязательно задайте тип — без него запрос упадёт с ошибкой о том, что тип не может быть выбран в запросе.
Функция ТаблицаДляЗапроса(Список)
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Значение",
Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Для Каждого Элемент Из Список Цикл
ТЗ.Добавить().Значение = Элемент.Значение;
КонецЦикла;
Возврат ТЗ;
КонецФункции
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ Т.Значение КАК Значение
|ПОМЕСТИТЬ ВТ_Список1
|ИЗ &Таблица1 КАК Т
|ИНДЕКСИРОВАТЬ ПО Значение
|;
|ВЫБРАТЬ РАЗЛИЧНЫЕ Т.Значение КАК Значение
|ПОМЕСТИТЬ ВТ_Список2
|ИЗ &Таблица2 КАК Т
|ИНДЕКСИРОВАТЬ ПО Значение
|;
|ВЫБРАТЬ
| ЕСТЬNULL(В1.Значение, В2.Значение) КАК Значение,
| ВЫБОР
| КОГДА В2.Значение ЕСТЬ NULL ТОГДА ""Только в первом""
| КОГДА В1.Значение ЕСТЬ NULL ТОГДА ""Только во втором""
| ИНАЧЕ ""В обоих""
| КОНЕЦ КАК Результат
|ИЗ
| ВТ_Список1 КАК В1
| ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Список2 КАК В2
| ПО В1.Значение = В2.Значение";
Запрос.УстановитьПараметр("Таблица1", ТаблицаДляЗапроса(Список1));
Запрос.УстановитьПараметр("Таблица2", ТаблицаДляЗапроса(Список2));
Выборка = Запрос.Выполнить().Выбрать();
Что здесь делает каждая деталь:
РАЗЛИЧНЫЕубирает повторы. Без него дубль в одном списке размножит строки соединения.ИНДЕКСИРОВАТЬ ПОускоряет соединение на больших объёмах.ПОЛНОЕ СОЕДИНЕНИЕсохраняет строки обеих сторон. Где пары нет, поле с другой стороны равноNULL— по нему и определяем группу.- Если нужны только расхождения, добавьте
ГДЕ В1.Значение ЕСТЬ NULL ИЛИ В2.Значение ЕСТЬ NULL.
Строки в таблице-параметре задавайте с ограниченной длиной: Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)). Строки неограниченной длины в условии соединения сравнивать нельзя.
Сравнение таблиц по нескольким полям через Свернуть()
Часто сравнивают не просто значения, а пары «товар + склад» или «товар + количество». Без запроса это делается одной таблицей и методом Свернуть().
Идея: сливаем обе таблицы в одну, первая ставит единицу в колонку В1, вторая — в В2. После свёртки по ключевым полям нуль в колонке показывает, что строки на этой стороне не было.
ТипЧисло = Новый ОписаниеТипов("Число");
Общая = Новый ТаблицаЗначений;
Общая.Колонки.Добавить("Номенклатура");
Общая.Колонки.Добавить("Склад");
Общая.Колонки.Добавить("В1", ТипЧисло);
Общая.Колонки.Добавить("В2", ТипЧисло);
Общая.Колонки.Добавить("Количество1", ТипЧисло);
Общая.Колонки.Добавить("Количество2", ТипЧисло);
Для Каждого Стр Из Таблица1 Цикл
Нов = Общая.Добавить();
Нов.Номенклатура = Стр.Номенклатура;
Нов.Склад = Стр.Склад;
Нов.В1 = 1;
Нов.Количество1 = Стр.Количество;
КонецЦикла;
Для Каждого Стр Из Таблица2 Цикл
Нов = Общая.Добавить();
Нов.Номенклатура = Стр.Номенклатура;
Нов.Склад = Стр.Склад;
Нов.В2 = 1;
Нов.Количество2 = Стр.Количество;
КонецЦикла;
Общая.Свернуть("Номенклатура, Склад", "В1, В2, Количество1, Количество2");
Для Каждого Стр Из Общая Цикл
Если Стр.В2 = 0 Тогда
// строка есть только в первой таблице
ИначеЕсли Стр.В1 = 0 Тогда
// строка есть только во второй таблице
ИначеЕсли Стр.Количество1 <> Стр.Количество2 Тогда
// ключ совпал, количество разное
КонецЕсли;
КонецЦикла;
Тип Число у колонок суммирования обязателен. Тогда незаполненная ячейка равна 0, а не Неопределено, и сравнение = 0 работает.
Пример с числами. В первой таблице «Гвозди / Основной склад» — 40 шт., во второй та же пара — 35 шт. После свёртки получаем одну строку: В1 = 1, В2 = 1, Количество1 = 40, Количество2 = 35. Она попадёт в ветку «количество разное», расхождение — 5 шт.
💡 Если нужно найти строку по нескольким полям без свёртки, у таблицы значений есть НайтиСтроки(Новый Структура("Номенклатура, Склад", Товар, Склад)). Добавьте индекс Таблица.Индексы.Добавить("Номенклатура, Склад") — поиск в цикле станет быстрым.
Регистр, пустые значения и типы: почему одинаковое не совпадает
Самые обидные расхождения — когда элементы на вид одинаковые.
- 🔤 Регистр. Во встроенном языке
"Ромашка" = "ромашка"даётЛожь. Перед сравнением приведите строки:ВРег(СокрЛП(Строка)). В запросе сравнение строк обычно идёт без учёта регистра — это стоит проверить на вашей СУБД, если данные смешанные. - ␣ Пробелы. «ООО Ромашка » с пробелом в конце не равна «ООО Ромашка».
СокрЛП()решает и это. - 🔢 Типы. Число
1и строка"1"— разные значения. При загрузке из файла коды часто приходят строкой, а в базе лежат числом. Приведите к одному типу:Число(Строка)илиФормат(Число, "ЧГ=0"). - 📅 Даты со временем.
01.10.2026 00:00:00и01.10.2026 15:30:00не равны. Если важен только день, сравнивайтеНачалоДня(Дата). - ∅ Пустые значения.
Неопределено, пустая ссылка, пустая строка иNULLиз запроса — четыре разных значения. Пустая ссылкаСправочники.Номенклатура.ПустаяСсылка()не равнаНеопределено.
Нормализация перед сравнением
Удобно прогнать каждое значение через одну функцию, а потом уже строить индекс:
Функция КлючСравнения(Значение)
Если Не ЗначениеЗаполнено(Значение) Тогда
Возврат "<пусто>"; // все виды «пусто» сводим к одному маркеру
ИначеЕсли ТипЗнч(Значение) = Тип("Строка") Тогда
Возврат ВРег(СокрЛП(Значение));
ИначеЕсли ТипЗнч(Значение) = Тип("Дата") Тогда
Возврат НачалоДня(Значение);
КонецЕсли;
Возврат Значение;
КонецФункции
ЗначениеЗаполнено() возвращает Ложь для пустой строки, нуля, пустой даты, пустой ссылки, Неопределено и NULL. Если нуль у вас — осмысленное значение, а не пустота, уберите числа из первой проверки.
Нормализуйте оба списка одной и той же функцией. Если привести к верхнему регистру только первый список, совпадений не будет вовсе.
Готовые функции БСП, СравнениеЗначений и другие альтернативы
Не всегда нужно писать сравнение с нуля.
Функции Библиотеки стандартных подсистем
В типовых конфигурациях на БСП (Бухгалтерия, ЗУП, УТ, ERP) есть готовые процедуры:
ОбщегоНазначенияКлиентСервер.РазностьМассивов(Массив, ВычитаемыйМассив)— элементы первого массива, которых нет во втором. Работает и на клиенте, и на сервере.ОбщегоНазначенияКлиентСервер.СвернутьМассив(Массив)— убирает повторы.ОбщегоНазначенияКлиентСервер.ДополнитьМассив(Приемник, Источник, Истина)— объединяет массивы; третий параметр оставляет только уникальные значения.ОбщегоНазначения.ДанныеСовпадают(Данные1, Данные2)— сравнивает содержимое коллекций с учётом вложенности. Серверная функция.
Список значений сначала переведите в массив через ВыгрузитьЗначения(). Во внешней обработке эти функции тоже доступны, если обработка запускается в конфигурации на БСП.
Объект СравнениеЗначений и слияние отсортированных списков
Встроенный объект СравнениеЗначений с методом Сравнить() возвращает −1, 0 или 1 — меньше, равно, больше. Им удобно пройти два заранее отсортированных списка одновременно, двумя указателями:
Список1.СортироватьПоЗначению();
Список2.СортироватьПоЗначению();
Сравнение = Новый СравнениеЗначений;
И1 = 0;
И2 = 0;
Пока И1 < Список1.Количество() И И2 < Список2.Количество() Цикл
Знач1 = Список1[И1].Значение;
Знач2 = Список2[И2].Значение;
Итог = Сравнение.Сравнить(Знач1, Знач2);
Если Итог = 0 Тогда
// значение есть в обоих списках
И1 = И1 + 1;
И2 = И2 + 1;
ИначеЕсли Итог < 0 Тогда
// Знач1 есть только в первом
И1 = И1 + 1;
Иначе
// Знач2 есть только во втором
И2 = И2 + 1;
КонецЕсли;
КонецЦикла;
// хвосты: элементы с И1 до конца — только в первом, с И2 до конца — только во втором
Способ хорош, когда результат нужен сразу упорядоченным, например для печатной формы сверки.
Внешние компоненты
Для сравнения списков значений они не нужны: встроенных средств хватает и по скорости, и по возможностям. Компоненты оправданы для другой задачи — построчного сравнения больших текстовых файлов. Но и тогда проще загрузить файлы в таблицы значений и применить способы выше.
Частые вопросы
Можно ли сравнить списки значений с учётом пометок?
Пометка элемента списка. В индекс-соответствии храните её как значение: Индекс.Вставить(Элемент.Значение, Элемент.Пометка), затем сравнивайте полученные пометки по одинаковым ключам.Как сравнить списки на клиенте, не вызывая сервер?
НайтиПоЗначению(), Соответствие и СравнениеЗначений работают на клиенте. Запрос и таблицы значений доступны только на сервере, поэтому для них нужен серверный вызов.Почему в результате сравнения запросом появились лишние строки?
ВЫБРАТЬ РАЗЛИЧНЫЕ при помещении во временную таблицу.Как посчитать, сколько раз элемент повторяется в каждом списке?
Истина или сверните таблицу с числовой колонкой-единицей. Разница счётчиков покажет, на сколько вхождений списки расходятся.