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