В практике разработчика 1С часто возникает задача очистки коллекций данных от повторяющихся записей. Особенно это актуально, когда "уникальность" строки определяется не по всем полям, а только по одной или нескольким ключевым колонкам (например, по колонке Характеристика). При этом критически важно не просто удалить лишнее, а сохранить именно первое вхождение данных, чтобы не нарушить логику последующей обработки. В этой статье мы подробно разберем все доступные методы: от простых циклов до высокопроизводительных запросов и использования хеш-таблиц.
Проанализируем самый быстрый программный способ поиска дублей без использования запросов. Объект Соответствие в 1С работает по принципу хеш-таблицы, что обеспечивает практически мгновенный поиск ключа даже в коллекциях из десятков тысяч элементов. Этот метод идеально подходит, если нам нужно оставить первую встреченную строку, а остальные удалить.
Разберем алгоритм: мы будем обходить исходную таблицу, проверять наличие ключевого значения в Соответствии. Если значения еще нет — добавляем его туда. Если оно уже есть — помечаем текущую строку на удаление.
Важный нюанс: Чтобы не нарушить индексы при удалении строк "на лету", мы сначала соберем все ненужные строки в отдельный массив, а затем удалим их из таблицы.
// Предположим, ТЗ - наша исходная таблица значений
// КлючУникальности - имя колонки, по которой ищем дубли
СоответствиеКлючей = Новый Соответствие;
МассивНаУдаление = Новый Массив;
Для Каждого СтрокаТЗ Из ТЗ Цикл
Ключ = СтрокаТЗ.Характеристика;
// Проверяем, встречалось ли уже такое значение
Если СоответствиеКлючей.Получить(Ключ) = Неопределено Тогда
// Это первое вхождение, запоминаем его в соответствии
СоответствиеКлючей.Вставить(Ключ, Истина);
Иначе
// Значение уже было, значит текущая строка - дубль
МассивНаУдаление.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Теперь удаляем собранные дубли
Для Каждого СтрокаДляУдаления Из МассивНаУдаление Цикл
ТЗ.Удалить(СтрокаДляУдаления);
КонецЦикла;
Этот способ оптимален по памяти и скорости, так как мы совершаем всего два прохода по данным.
Если объем данных в ТаблицеЗначений превышает несколько десятков тысяч строк, программный перебор может замедлить работу системы. В таких случаях мы рекомендуем использовать механизм запросов. Это позволяет переложить задачу поиска дублей на СУБД, которая справится с этим значительно быстрее. Для более сложных сценариев пакетной обработки данных пригодится статья об особенностях работы с временными таблицами в 1С.
Рассмотрим по шагам, как это реализовать:
ТаблицуЗначений в запрос в качестве параметра.СГРУППИРОВАТЬ ПО или ВЫБРАТЬ РАЗЛИЧНЫЕ.Посмотрим на пример кода, где нам нужно оставить только уникальные характеристики:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Данные.Характеристика КАК Характеристика,
| МАКСИМУМ(ВТ_Данные.Цена) КАК Цена,
| МАКСИМУМ(ВТ_Данные.Количество) КАК Количество
|ПОМЕСТИТЬ ВТ_Уникальные
|ИЗ
| &ВТ_Исходная КАК ВТ_Данные
|
|СГРУППИРОВАТЬ ПО
| ВТ_Данные.Характеристика;
|
|ВЫБРАТЬ * ИЗ ВТ_Уникальные";
// Важно: колонки в ТЗ должны иметь типизацию
Запрос.УстановитьПараметр("ВТ_Исходная", ТЗ);
ТЗ = Запрос.Выполнить().Выгрузить();
Обратите внимание: Если в других колонках (например, Цена) данные различаются, использование агрегатных функций типа МАКСИМУМ или МИНИМУМ позволит вам предсказать, какие именно данные попадут в итоговую строку. Если же вам нужно просто "первую попавшуюся" строку из исходной ТЗ, в запросе можно добавить колонку НомерСтроки и группировать по ней (выбирая минимальный номер).
Это самый быстрый в написании способ, предоставляемый платформой 1С. Метод Свернуть объединяет строки с одинаковыми значениями в указанных колонках группировки.
Выясним причину, почему этот метод не всегда подходит: при свертке числовые колонки, не вошедшие в группировку, суммируются, а нечисловые — очищаются (если они не указаны в списке группировки). Если вам нужно сохранить строковые данные в дополнительных колонках, этот метод может их "затереть".
Пример использования:
// Группируем по Характеристике, суммируем Количество
// Все остальные колонки, не указанные здесь, будут удалены!
ТЗ.Свернуть("Характеристика", "Количество");
Используйте этот метод только тогда, когда вам достаточно оставить только ключевые колонки и числовые показатели.
Если вы принципиально хотите удалять строки прямо в основном цикле, проанализируем ситуацию с индексами. При удалении строки с индексом 5, строка, которая была 6-й, становится 5-й. Обычный цикл Для Каждого или цикл по возрастанию индекса пропустит эту строку. Поэтому мы используем обход с конца.
КоличествоСтрок = ТЗ.Количество();
Для Счетчик = 1 По КоличествоСтрок Цикл
Индекс = КоличествоСтрок - Счетчик;
ТекущаяСтрока = ТЗ[Индекс];
// Логика проверки дубля (например, через НайтиСтроки)
СтруктураПоиска = Новый Структура("Характеристика", ТекущаяСтрока.Характеристика);
Найденные = ТЗ.НайтиСтроки(СтруктураПоиска);
Если Найденные.Количество() > 1 Тогда
// Если нашли больше одной строки, проверяем, не является ли текущая "первой"
// Чтобы оставить первую, удаляем только если индекс больше индекса первой найденной
Если ТЗ.Индекс(Найденные[0]) < Индекс Тогда
ТЗ.Удалить(ТекущаяСтрока);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Этот метод считается менее производительным из-за постоянного вызова НайтиСтроки внутри цикла, но он наглядно демонстрирует работу с индексами коллекции.
При работе с дублями в 1С важно учитывать несколько технических моментов, которые могут повлиять на надежность вашего кода:
Характеристика тоже считаются дублями. Если логика вашей задачи подразумевает, что строки с незаполненной характеристикой должны остаться, добавьте условие: Если ЗначениеЗаполнено(Строка.Характеристика) Тогда....Номенклатура, и Характеристика, и Склад, используйте структуру в качестве ключа для Соответствия или объединяйте значения в одну строку через разделитель (например, Ключ = Строка(Номенклатура.УникальныйИдентификатор()) + "_" + Строка(Склад)).НайтиСтроки() в большой таблице значений, обязательно добавьте индекс по этой колонке: ТЗ.Индексы.Добавить("Характеристика"). Это в разы ускорит поиск.ТаблицыЗначений, а простого Массива, самым эффективным способом будет создание нового массива и использование Соответствия для фильтрации, так как у массива нет метода Свернуть.Подводя итог, можно сказать: для простых задач используйте Свернуть(), для сложных алгоритмов с сохранением данных — Соответствие, а для обработки огромных массивов информации на стороне сервера — Запросы. Выбор правильного инструмента позволит вашему коду работать быстро и предсказуемо.