При загрузке и обработке больших массивов информации во внешней обработке или серверном модуле разработчики часто сталкиваются с задачей: в объекте ТаблицаЗначений на сотни тысяч строк присутствует колонка с текстовыми наименованиями (например, «Номенклатура», «Производитель» или «Валюта»). Требуется получить список уникальных значений, найти соответствующие элементы в базе данных 1С и заменить строки на найденные ссылки без потери производительности.
Разберем различные подходы к решению этой задачи: от стандартных методов платформы до высокопроизводительного сопоставления через хэш-таблицы.
Проанализируем классический способ платформы 1С. Метод Свернуть() позволяет быстро схлопнуть дублирующиеся записи. Чтобы не модифицировать исходную большую таблицу, выгрузим целевую колонку в отдельную промежуточную таблицу значений.
Рассмотрим пример кода:
// Создаем копию только нужной колонки
ТЗУникальных = ИсходнаяТЗ.Скопировать(, "ПроизводительНаименование");
// Сворачиваем по группировочной колонке
ТЗУникальных.Свернуть("ПроизводительНаименование");
// Теперь ТЗУникальных содержит только уникальные строки
Особенности метода:
rphost.Выясним, почему коллекция Соответствие является самым эффективным инструментом в сценариях нормализации данных. Объект Соответствие в платформе 1С реализован как хэш-таблица. Это дает ключевые преимущества:
Вставить(Ключ, Значение) перезаписывает значение при совпадении ключа, не создавая дубликатов.НайтиСтроки().Посмотрим на практическую реализацию двухэтапного сопоставления:
// Шаг 1. Сбор уникальных значений в момент обхода или чтения
КэшПроизводителей = Новый Соответствие;
Для Каждого СтрТЗ Из ИсходнаяТЗ Цикл
// Ключом выступает текст, значением изначально может быть Неопределено
КэшПроизводителей.Вставить(СтрТЗ.ПроизводительСтрока, Неопределено);
КонецЦикла;
// Шаг 2. Заполнение ссылок для уникальных ключей
Для Каждого ЭлементКэша Из КэшПроизводителей Цикл
ТекстПоиска = ЭлементКэша.Ключ;
СсылкаНайдена = Справочники.Производители.НайтиПоНаименованию(ТекстПоиска, Истина);
Если ЗначениеЗаполнено(СсылкаНайдена) Тогда
КэшПроизводителей.Вставить(ТекстПоиска, СсылкаНайдена);
Иначе
// Обработка отсутствующих элементов (создание или оповещение)
КэшПроизводителей.Вставить(ТекстПоиска, Справочники.Производители.ПустаяСсылка());
КонецЕсли;
КонецЦикла;
// Шаг 3. Быстрая замена строк на ссылки за один проход
Для Каждого СтрТЗ Из ИсходнаяТЗ Цикл
СтрТЗ.ПроизводительСсылка = КэшПроизводителей[СтрТЗ.ПроизводительСтрока];
КонецЦикла;
Рассмотрим подробнее: на шаге 3 мы делаем ровно один проход по исходной таблице, обращаясь к соответствию по индексу КэшПроизводителей[Ключ]. Для 650 000 строк такой цикл отрабатывает за доли секунды, полностью исключая тяжелые поисковые операции.
Разберем еще один способ получения уникального списка — использование объекта ПостроительЗапроса. Он умеет принимать в качестве источника данных объект ТаблицаЗначений напрямую через ОписаниеИсточникаДанных:
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ИсходнаяТЗ);
Построитель.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ИсходныеДанные.ПроизводительСтрока КАК ПроизводительСтрока
|ИЗ
| ИсходныеДанные КАК ИсходныеДанные";
Построитель.Выполнить();
ТЗУникальных = Построитель.Результат.Выгрузить();
Важное ограничение: если передавать таблицу значений напрямую в параметры обычного объекта Запрос (Запрос.УстановитьПараметр("ТЗ", ИсходнаяТЗ)), платформа будет сериализовать данные и создавать временную таблицу в базе данных tempdb на стороне СУБД. При сотнях тысяч строк это приводит к дополнительным затратам времени на передачу и запись данных по сети.
Если требуется полностью заменить значения в колонке, можно сформировать массив целевых значений той же размерности и воспользоваться низкоуровневым методом ЗагрузитьКолонку(). Он выполняется на уровне платформы значительно быстрее ручной построчной перезаписи полей:
МассивСсылок = Новый Массив;
Для Каждого Стр Из ИсходнаяТЗ Цикл
МассивСсылок.Добавить(КэшПроизводителей[Стр.ПроизводительСтрока]);
КонецЦикла;
// Загружаем готовый массив в колонку
ИсходнаяТЗ.ЗагрузитьКолонку(МассивСсылок, "ПроизводительСсылка");
При обработке колонок, содержащих пустые ссылки различных справочников, необходимо корректно проверять их заполненность:
ЗначениеЗаполнено(Значение) вернет Ложь как для пустой ссылки любого типа (СправочникСсылка.Номенклатура.Пустая()), так и для Неопределено или пустой строки.Значение.Пустая().НайтиСтроки() в циклах: даже при наличии индекса метод НайтиСтроки() при каждом вызове создает в памяти новый Массив. Сотни тысяч вызовов вызывают резкую деградацию производительности и нагружают сборщик мусора.ТаблицаЗначений. Читайте файл построчно через ЧтениеТекста или ЧтениеXML, сразу накапливая соответствие уникальных записей.ПоместитьВоВременноеХранилище() и ПолучитьИзВременногоХранилища() по уникальному идентификатору формы, чтобы не гонять тяжелый контекст формы при серверных вызовах.