Как правильно суммировать данные в таблице значений и удалять строки без ошибок

Программист 1С v8.3 (Обычные формы) Управленческий учет Финансы и профессиональные услуги
← На главную

Работа с коллекциями данных, такими как ТаблицаЗначений, — это фундамент разработки в среде 1С. Часто перед программистом встает задача: проанализировать таблицу, найти строки с одинаковыми ключевыми полями, просуммировать их числовые показатели, а затем избавиться от лишних строк-дублей. Несмотря на кажущуюся простоту, здесь легко совершить логические ошибки, которые приводят к "задваиванию" сумм или критическим ошибкам системы вроде Ошибка при вызове метода контекста (Удалить): Значение индекса выходит за границы диапазона.

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

Разбор логической ошибки: почему данные "задваиваются"?

Рассмотрим типичную ситуацию, с которой столкнулся автор темы. Имеется таблица тпБюджетныеОперации, где нужно сравнить строки по двум колонкам (индексы 1 и 2) и, если они совпадают, прибавить значения ресурсов (колонки с 7 по 19) к первой найденной строке. Первоначальный код выглядел так:


Для к = 0 по КоличествоСтрок-1 Цикл
    Для н = 1 по КоличествоСтрок-1 Цикл
        Если тпБюджетныеОперации[к][1] = тпБюджетныеОперации[н][1] И тпБюджетныеОперации[к][2] = тпБюджетныеОперации[н][2] Тогда
            Для е = 7 по 19 цикл 
                тпБюджетныеОперации[к][е] = тпБюджетныеОперации[к][е] + тпБюджетныеОперации[н][е];
            КонецЦикла;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Выясним причину ошибки: вложенный цикл по переменной н всегда начинается с 1. Это означает, что система сравнивает строку саму с собой (когда к = н), а также повторно обрабатывает уже пройденные пары строк. В итоге одно и то же значение прибавляется несколько раз, что и приводит к некорректным результатам.

Для исправления этой логики достаточно изменить диапазон внутреннего цикла. Мы должны сравнивать текущую строку только с теми, что идут после нее. Рассмотрим исправленный вариант:


Для к = 0 по КоличествоСтрок-1 Цикл
    Для н = к + 1 по КоличествоСтрок-1 Цикл
        // Сравнение идет только с последующими строками
        // ... логика суммирования ...
    КонецЦикла;
КонецЦикла;

Теперь, когда н = к + 1, исключается вероятность самосравнения и повторной обработки одних и тех же пар данных.

Проблема удаления строк в прямом цикле

Следующий этап — удаление строки после того, как её данные были прибавлены к "эталонной". Здесь многие начинающие разработчики пытаются вызвать метод Удалить(н) прямо внутри цикла. Это фатальная ошибка для прямого цикла (от 0 до N).

Проанализируем ситуацию: представьте, что в таблице 10 строк. Вы удаляете строку с индексом 5. Теперь те строки, что были 6-й, 7-й и так далее, "сдвигаются" вверх, и их индексы уменьшаются на единицу. Однако счетчик цикла н продолжает расти. В результате система пропускает следующую строку, а в конце цикла пытается обратиться к индексу, которого больше не существует, вызывая ошибку Значение индекса выходит за границы диапазона.

Рассмотрим два надежных способа решения этой проблемы.

Решение №1: Использование массива для отложенного удаления

Это один из самых безопасных и понятных методов. Вместо того чтобы удалять строки мгновенно, мы сначала помечаем их (добавляем в массив), а после завершения основного анализа удаляем их отдельным проходом.

Разберем по шагам:

  1. Создаем пустой объект Массив перед началом циклов.
  2. Внутри условия, где мы нашли дубль и просуммировали данные, добавляем ссылку на строку тпБюджетныеОперации[н] в этот массив.
  3. После завершения всех циклов обходим массив и удаляем накопленные строки из таблицы значений.

МассивУдаления = Новый Массив;

Для к = 0 по тпБюджетныеОперации.Количество() - 1 Цикл
    Для н = к + 1 по тпБюджетныеОперации.Количество() - 1 Цикл
        Если тпБюджетныеОперации[к].Поле1 = тпБюджетныеОперации[н].Поле1 Тогда 
            // Суммируем ресурсы
            Для е = 7 по 19 Цикл
                тпБюджетныеОперации[к][е] = тпБюджетныеОперации[к][е] + тпБюджетныеОперации[н][е];
            КонецЦикла;
            
            // Добавляем строку в очередь на удаление, если она еще не там
            Если МассивУдаления.Найти(тпБюджетныеОперации[н]) = Неопределено Тогда
                МассивУдаления.Добавить(тпБюджетныеОперации[н]);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

// Удаляем накопленные строки
Для Каждого СтрокаДляУдаления Из МассивУдаления Цикл
    тпБюджетныеОперации.Удалить(СтрокаДляУдаления);
КонецЦикла;

Важный нюанс: при использовании метода Удалить() в 1С можно передавать как индекс строки, так и саму СтрокаТаблицыЗначений. Использование ссылки на строку в данном случае предпочтительнее и надежнее.

Решение №2: "Обратный цикл" или метод перевертыша

Если вам необходимо удалять строки сразу, не создавая дополнительных массивов, следует использовать обход коллекции с конца к началу. В этом случае удаление элемента не влияет на индексы тех строк, которые циклу еще только предстоит обработать.

Рассмотрим пример реализации удаления через обратный цикл:


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

Этот метод считается классическим в программировании на 1С и часто рекомендуется в официальной документации (ЖКК) для операций очистки коллекций.

Профессиональный подход: метод Свернуть()

В 1С существует встроенный инструмент, который делает всю вышеописанную работу (группировку и суммирование) за одну микросекунду. Это метод Свернуть(). Рассмотрим его, так как он является наиболее оптимальным для большинства задач.

Синтаксис метода: ТаблицаЗначений.Свернуть("КолонкиГруппировки", "КолонкиСуммирования").

Проанализируем, как это применить к нашей задаче:


// Допустим, колонки 1 и 2 называются "Подразделение" и "Статья"
// А колонки 7-19 — это "Месяц1", "Месяц2" и т.д.
КолонкиГруппировки = "Подразделение, Статья";
КолонкиСуммирования = "СуммаЯнварь, СуммаФевраль, СуммаМарт"; // и так далее

тпБюджетныеОперации.Свернуть(КолонкиГруппировки, КолонкиСуммирования);

Преимущества метода Свернуть():

Использование соответствий для ускорения (Кэширование)

Если метод Свернуть() вам не подходит (например, логика суммирования слишком сложная и требует дополнительных проверок), вместо вложенных циклов со сложностью O(n²) лучше использовать объект Соответствие.

Суть метода заключается в том, что вы проходите по таблице один раз. В качестве ключа соответствия используете уникальную комбинацию ваших измерений (например, структуру или строку), а в качестве значения — уже найденную строку таблицы, куда нужно плюсовать данные. Если в соответствии ключа еще нет — добавляем текущую строку. Если есть — суммируем данные и помечаем текущую строку на удаление.

Этот подход позволяет обрабатывать огромные таблицы (десятки тысяч строк) за доли секунды, в то время как вложенные циклы могут заставить пользователя ждать несколько минут.

Полезные советы по работе с Таблицей Значений

Подводя итог, разберем несколько рекомендаций, которые помогут избежать ошибок в будущем:

  1. Индексация: если вы часто используете поиск в большой таблице через НайтиСтроки(), обязательно добавьте индексы: тпБюджетныеОперации.Индексы.Добавить("Колонка1, Колонка2").
  2. Типизация: перед суммированием убедитесь, что колонки имеют тип Число. В противном случае вместо 10 + 20 = 30 вы можете получить ошибку при попытке сложить число со значением Неопределено.
  3. Копирование структуры: если логика фильтрации очень сложная, иногда проще создать новую таблицу (тпБюджетныеОперации.СкопироватьКолонки()) и заполнять ее только нужными данными, чем пытаться удалять строки из исходной.

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

← На главную