В практике разработки на платформе 1С:Предприятие часто возникает задача синхронизации данных. Классический пример: у нас есть внешняя ТаблицаЗначений (загруженная из Excel, XML или другого источника) и существующая ТабличнаяЧасть справочника или документа. Нам необходимо выяснить, каких строк из внешней таблицы еще нет в базе данных, чтобы добавить только их, не создавая дублей.
Рассмотрим подробно несколько способов решения этой задачи: от классических запросов до оптимизированных алгоритмов для больших объемов данных.
Самый надежный и производительный способ решения данной задачи в 1С — использование языка запросов. Логика здесь строится на операции вычитания множеств через ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN).
Разберем алгоритм по шагам:
ТаблицаЗначений во временную таблицу.ГДЕ отбираем только те записи, где поля из табличной части равны NULL. Это и будет признаком того, что строка есть во входящей таблице, но отсутствует в базе.Посмотрим на пример кода:
// Предположим, у нас есть ТЗ (ТаблицаЗначений) и Ссылка на справочник
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// 1. Помещаем ТЗ во временную таблицу
// ВАЖНО: Типизируйте колонки ТЗ перед помещением в запрос, если они не типизированы
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Поле1 КАК Поле1,
| ТЗ.Поле2 КАК Поле2,
| ТЗ.Поле3 КАК Поле3
|ПОМЕСТИТЬ ВТ_ВходящиеДанные
|ИЗ
| &ТЗ КАК ТЗ";
// Обязательно создаем индекс для ускорения соединения, если строк много
// Это критически важно для производительности
Запрос.Текст = Запрос.Текст +
";
|ИНДЕКСИРОВАТЬ ПО
| Поле1,
| Поле2,
| Поле3";
Запрос.УстановитьПараметр("ТЗ", ТаблицаЗначений);
Запрос.Выполнить();
// 2. Основной запрос: ищем отсутствующие строки
Запрос.Текст =
"ВЫБРАТЬ
| Входящие.Поле1,
| Входящие.Поле2,
| Входящие.Поле3
|ИЗ
| ВТ_ВходящиеДанные КАК Входящие
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.НашСправочник.ТЧТовары КАК Существующие
| ПО Входящие.Поле1 = Существующие.Поле1
| И Входящие.Поле2 = Существующие.Поле2
| И Входящие.Поле3 = Существующие.Поле3
| И (Существующие.Ссылка = &Ссылка)
|ГДЕ
| Существующие.Ссылка ЕСТЬ NULL";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
НовыеСтроки = Запрос.Выполнить().Выгрузить();
Преимущества: Этот метод корректно обрабатывает NULL значения в полях, работает быстро благодаря индексам и является стандартом "best practice".
Иногда разработчики используют конструкцию НЕ В (NOT IN). Логика проста: выбрать все из ТЗ, где поля не входят в выборку из ТЧ.
ВЫБРАТЬ
ТЗ.Поле1
ИЗ
&ТЗ КАК ТЗ
ГДЕ
(ТЗ.Поле1, ТЗ.Поле2) НЕ В
(ВЫБРАТЬ
ТЧ.Поле1, ТЧ.Поле2
ИЗ
Справочник.Спр.ТЧ КАК ТЧ
ГДЕ
ТЧ.Ссылка = &Ссылка)
Важное предостережение: Мы настоятельно не рекомендуем использовать этот способ, если в ваших полях могут встречаться значения NULL. В языке SQL (и 1С) любое сравнение с NULL внутри подзапроса NOT IN может привести к неочевидному результату (пустой выборке), даже если данные различаются. Используйте этот метод только если вы гарантированно уверены, что все поля заполнены.
Для высоконагруженных систем или очень больших таблиц иногда эффективнее использовать метод объединения с маркерами. Мы не используем соединения (JOIN), которые могут быть тяжелыми, а просто складываем две таблицы в одну кучу и считаем вхождения.
Алгоритм:
1.-1.ОБЪЕДИНИТЬ ВСЕ.СУММА(Индикатор) = 1, значит строка была только в ТЗ (новая). Если -1 — только в ТЧ (удаленная). Если 0 — есть и там, и там.
ВЫБРАТЬ
ТЗ.Поле1,
ТЗ.Поле2,
1 КАК Индикатор
ПОМЕСТИТЬ ВТ_Общая
ИЗ &ТЗ КАК ТЗ
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТЧ.Поле1,
ТЧ.Поле2,
-1
ИЗ Справочник.Спр.ТЧ КАК ТЧ
ГДЕ ТЧ.Ссылка = &Ссылка
;
ВЫБРАТЬ
Общая.Поле1,
Общая.Поле2
ИЗ
ВТ_Общая КАК Общая
СГРУППИРОВАТЬ ПО
Общая.Поле1,
Общая.Поле2
ИМЕЮЩИЕ
СУММА(Общая.Индикатор) = 1
Если по каким-то причинам использование запросов невозможно (например, данные еще не записаны в БД, и мы работаем с формой объекта), придется искать программно.
Часто новички пишут двойной цикл или используют НайтиСтроки внутри цикла:
Для Каждого СтрТЗ Из ТаблицаЗначений Цикл
Отбор = Новый Структура("Поле1, Поле2", СтрТЗ.Поле1, СтрТЗ.Поле2);
Найденные = Объект.ТЧ.НайтиСтроки(Отбор);
Если Найденные.Количество() = 0 Тогда
// Добавляем строку
КонецЕсли;
КонецЦикла;
Этот код имеет сложность O(N*M) и будет работать очень медленно на таблицах размером более 100-500 строк.
Чтобы ускорить поиск в памяти, используйте объект Соответствие. Это аналог хеш-таблицы, поиск в котором происходит почти мгновенно.
Рассмотрим правильный алгоритм:
// 1. Индексируем существующие строки
СуществующиеСтроки = Новый Соответствие;
Для Каждого СтрТЧ Из Объект.ТЧ Цикл
// Формируем уникальный ключ. Если полей много, можно использовать хеш-функцию
// или склеить значения в строку с разделителем
Ключ = Строка(СтрТЧ.Поле1) + "|" + Строка(СтрТЧ.Поле2) + "|" + Строка(СтрТЧ.Поле3);
СуществующиеСтроки.Вставить(Ключ, Истина);
КонецЦикла;
// 2. Ищем новые строки (проход по ТЗ)
НовыеСтрокиТЗ = Новый Массив;
Для Каждого СтрТЗ Из ТаблицаЗначений Цикл
Ключ = Строка(СтрТЗ.Поле1) + "|" + Строка(СтрТЗ.Поле2) + "|" + Строка(СтрТЗ.Поле3);
// Мгновенная проверка наличия ключа
Если СуществующиеСтроки.Получить(Ключ) = Неопределено Тогда
НовыеСтрокиТЗ.Добавить(СтрТЗ);
КонецЕсли;
КонецЦикла;
Если полей для сравнения не 3, а 10 или 20, писать условия соединения в запросе становится неудобно и медленно. В таких случаях рекомендуется добавить в таблицу вычисляемое поле "Хеш" (например, MD5 от строки, содержащей значения всех значимых полей). Сравнение в запросе будет производиться всего по одной колонке — хешу.
Обратите внимание, что стандартные методы (LEFT JOIN, NOT IN, свертка) работают с множествами уникальных значений. Если в вашей ТЗ две абсолютно одинаковые строки (например, два одинаковых товара), а в ТЧ — одна, то описанные выше методы не покажут, что вторую строку нужно добавить. Система посчитает, что "товар такой-то существует".
Если вам важно количество дублей, задачу нужно решать сложнее: предварительно нумеровать строки внутри групп одинаковых ключей (эмуляция ROW_NUMBER()) в обоих наборах данных и включать этот номер в условие соединения.
Для большинства задач оптимальным решением является Способ 1 (Запрос с временной таблицей и левым соединением). Он обеспечивает баланс между простотой кода, читаемостью и высокой производительностью. Если же вы работаете с данными только в оперативной памяти (на клиенте или без записи в БД), используйте Оптимизированный программный подход через Соответствие.