Как найти совпадения и различия двух списков значений в 1С 8.3

· 11 мин

Содержание

Чтобы сравнить два списка значений в 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));
Выборка = Запрос.Выполнить().Выбрать();

Что здесь делает каждая деталь:

  1. РАЗЛИЧНЫЕ убирает повторы. Без него дубль в одном списке размножит строки соединения.
  2. ИНДЕКСИРОВАТЬ ПО ускоряет соединение на больших объёмах.
  3. ПОЛНОЕ СОЕДИНЕНИЕ сохраняет строки обеих сторон. Где пары нет, поле с другой стороны равно NULL — по нему и определяем группу.
  4. Если нужны только расхождения, добавьте ГДЕ В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 до конца — только во втором

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

Внешние компоненты

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

Частые вопросы

Можно ли сравнить списки значений с учётом пометок?
Да. Пометка — свойство Пометка элемента списка. В индекс-соответствии храните её как значение: Индекс.Вставить(Элемент.Значение, Элемент.Пометка), затем сравнивайте полученные пометки по одинаковым ключам.
Как сравнить списки на клиенте, не вызывая сервер?
Способы с НайтиПоЗначению(), Соответствие и СравнениеЗначений работают на клиенте. Запрос и таблицы значений доступны только на сервере, поэтому для них нужен серверный вызов.
Почему в результате сравнения запросом появились лишние строки?
Почти всегда это повторы в исходных списках: соединение перемножает их. Используйте ВЫБРАТЬ РАЗЛИЧНЫЕ при помещении во временную таблицу.
Как посчитать, сколько раз элемент повторяется в каждом списке?
Храните в соответствии счётчик вместо Истина или сверните таблицу с числовой колонкой-единицей. Разница счётчиков покажет, на сколько вхождений списки расходятся.
Сравнивает ли `НайтиПоЗначению()` по представлению?
Нет, только по значению. Два элемента с одинаковым представлением «Иванов» и разными ссылками на физлиц — разные элементы.