Как найти и оставить в таблице значений только повторяющиеся строки по нескольким колонкам?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике программирования на платформе 1С часто возникает задача очистки данных в объекте ТаблицаЗначений. Одной из специфических задач является удаление строк, которые встречаются в таблице только один раз, и сохранение тех, которые имеют дубли по определенному набору колонок. Рассмотрим подробнее несколько способов реализации этой задачи: от использования механизмов запросов до оптимизированных процедурных методов.

Метод 1: Использование механизмов запросов

Этот способ считается наиболее «изящным» и часто самым быстрым, если данные уже находятся на сервере или их объем позволяет передать таблицу в менеджер временных таблиц. Основная идея заключается в том, чтобы сгруппировать таблицу по ключевым полям и отобрать только те группы, где количество строк больше единицы.

Проанализируем алгоритм реализации через запрос:

  1. Поместим исходную таблицу значений во временную таблицу запроса.
  2. Создадим вторую временную таблицу, в которой сгруппируем данные по нужным колонкам и применим условие ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1.
  3. Выполним внутреннее соединение исходной таблицы с таблицей повторяющихся ключей.

Разберем пример кода для этого метода:


// Предположим, у нас есть ТЗ с колонками: Дата, Фамилия, Документ
Запрос = Новый Запрос;
Запрос.МенеджерВременных Таблиц = Новый МенеджерВременныхТаблиц;

// Важно: колонки ТЗ должны быть типизированы
Запрос.Текст = 
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Исходная ИЗ &ТЗ КАК ТЗ;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ Дата, Фамилия ПОМЕСТИТЬ ВТ_ДубликатыКлючей
|ИЗ ВТ_Исходная СГРУППИРОВАТЬ ПО Дата, Фамилия ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ Ист.* ИЗ ВТ_Исходная КАК Ист
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДубликатыКлючей КАК Дубли
|ПО Ист.Дата = Дубли.Дата И Ист.Фамилия = Дубли.Фамилия";

Запрос.УстановитьПараметр("ТЗ", ИсходнаяТЗ);
РезультатТЗ = Запрос.Выполнить().Выгрузить();

Метод 2: Процедурный подход через «Свернуть» и поиск уникальных строк

Если по каким-то причинам использование запроса невозможно (например, колонки таблицы имеют произвольный тип или данные находятся на стороне клиента), можно воспользоваться методом Свернуть(). Рассмотрим ситуацию, когда нам нужно оставить только дубли по колонкам Контрагент и Договор.

Выполним следующие шаги:

  1. Создадим копию таблицы, оставив только ключевые колонки.
  2. Добавим вспомогательную колонку-счетчик.
  3. Свернем копию таблицы, просуммировав счетчик.
  4. Найдем строки, где счетчик равен 1 — это и есть уникальные записи, которые нужно удалить из основной таблицы.

Пример реализации этого алгоритма:


КлючевыеКолонки = "Контрагент,Договор";
КолонкаСчетчик = "КоличествоЗаписей";

// Шаг 1-3: Получаем таблицу с количеством вхождений каждой комбинации ключей
ТЗПовторы = ТЗ.Скопировать(, КлючевыеКолонки);
ТЗПовторы.Колонки.Добавить(КолонкаСчетчик);
ТЗПовторы.ЗаполнитьЗначения(1, КолонкаСчетчик);
ТЗПовторы.Свернуть(КлючевыеКолонки, КолонкаСчетчик);

// Шаг 4: Находим уникальные ключи (где количество = 1)
ПараметрыПоиска = Новый Структура(КолонкаСчетчик, 1);
СтрокиСУникальнымиКлючами = ТЗПовторы.НайтиСтроки(ПараметрыПоиска);

// Индексация основной таблицы ускорит удаление через НайтиСтроки
ТЗ.Индексы.Добавить(КлючевыеКолонки);

Для Каждого Ключ Из СтрокиСУникальнымиКлючами Цикл
    СтруктураПоиска = Новый Структура;
    ЗаполнитьЗначенияСвойств(СтруктураПоиска, Ключ);
    
    // Находим все соответствующие строки в основной ТЗ (их будет по 1)
    СтрокиКУдалению = ТЗ.НайтиСтроки(СтруктураПоиска);
    Для Каждого Строка Из СтрокиКУдалению Цикл
        ТЗ.Удалить(Строка);
    КонецЦикла;
КонецЦикла;

Метод 3: Оптимизированный обход с предварительной сортировкой

Для очень больших таблиц значений удаление строк в цикле может быть медленным из-за особенностей работы памяти в 1С (каждое удаление строки из середины массива приводит к сдвигу последующих элементов). В таких случаях эффективнее использовать сортировку и один проход по таблице с копированием нужных строк в новую таблицу.

Рассмотрим логику этого процесса:

  1. Отсортируем ТаблицаЗначений по ключевым колонкам.
  2. Обойдем таблицу в цикле, сравнивая текущую строку с предыдущей и следующей.
  3. Если текущая строка совпадает хотя бы с одной соседней — это дубль, добавляем её в новую таблицу.

Проанализируем пример кода:


ТЗ.Сортировать("Дата, Фамилия");
НоваяТЗ = ТЗ.СкопироватьКолонки();

КоличествоСтрок = ТЗ.Количество();
Для Индекс = 0 По КоличествоСтрок - 1 Цикл
    ТекущаяСтрока = ТЗ[Индекс];
    ЕстьДубль = Ложь;
    
    // Проверяем предыдущую строку
    Если Индекс > 0 Тогда
        ПредСтрока = ТЗ[Индекс - 1];
        Если ТекущаяСтрока.Дата = ПредСтрока.Дата И ТекущаяСтрока.Фамилия = ПредСтрока.Фамилия Тогда
            ЕстьДубль = Истина;
        КонецЕсли;
    КонецЕсли;
    
    // Проверяем следующую строку
    Если НЕ ЕстьДубль И Индекс < КоличествоСтрок - 1 Тогда
        СледСтрока = ТЗ[Индекс + 1];
        Если ТекущаяСтрока.Дата = СледСтрока.Дата И ТекущаяСтрока.Фамилия = СледСтрока.Фамилия Тогда
            ЕстьДубль = Истина;
        КонецЕсли;
    КонецЕсли;
    
    Если ЕстьДубль Тогда
        ЗаполнитьЗначенияСвойств(НоваяТЗ.Добавить(), ТекущаяСтрока);
    КонецЕсли;
КонецЦикла;

Важные замечания по производительности

При выборе алгоритма следует учитывать несколько технических факторов, влияющих на скорость работы:

1. Индексация: Если вы используете метод НайтиСтроки(), обязательно добавляйте индексы в ТаблицаЗначений с помощью ТЗ.Индексы.Добавить("Колонки"). Это превращает поиск из линейного в логарифмический, что критично при больших объемах данных.

2. Удаление или копирование: Как показывает практика, если нужно удалить более 10-20% строк, быстрее создать новую таблицу через СкопироватьКолонки() и наполнить её нужными данными, чем удалять ненужные из существующей. Это связано с тем, что операция Удалить() физически перестраивает массив строк в памяти платформы.

3. Типизация колонок: Для корректной работы запросов колонки таблицы должны иметь строго определенные типы. Если колонка имеет тип Произвольный, метод Сгруппировать в запросе может вызвать ошибку или работать некорректно со ссылочными типами.

4. Регистрозависимость: Помните, что встроенные методы Свернуть() и НайтиСтроки() обычно не чувствительны к регистру строк (например, "Иванов" и "ИВАНОВ" будут считаться одинаковыми). Если ваша задача требует учета регистра, используйте специализированные функции сравнения в процедурном обходе.

Таким образом, для большинства повседневных задач оптимальным является Метод 1 (Запрос), так как он минимизирует количество написанного кода и использует оптимизированные механизмы СУБД. Однако в сложных случаях, когда типы данных неоднородны, Метод 3 (Сортировка и обход) обеспечит максимальную стабильность и предсказуемое потребление памяти.

← На главную