Работа с коллекциями данных, такими как ТаблицаЗначений, — это фундамент разработки в среде 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-й и так далее, "сдвигаются" вверх, и их индексы уменьшаются на единицу. Однако счетчик цикла н продолжает расти. В результате система пропускает следующую строку, а в конце цикла пытается обратиться к индексу, которого больше не существует, вызывая ошибку Значение индекса выходит за границы диапазона.
Рассмотрим два надежных способа решения этой проблемы.
Это один из самых безопасных и понятных методов. Вместо того чтобы удалять строки мгновенно, мы сначала помечаем их (добавляем в массив), а после завершения основного анализа удаляем их отдельным проходом.
Разберем по шагам:
Массив перед началом циклов.тпБюджетныеОперации[н] в этот массив.
МассивУдаления = Новый Массив;
Для к = 0 по тпБюджетныеОперации.Количество() - 1 Цикл
Для н = к + 1 по тпБюджетныеОперации.Количество() - 1 Цикл
Если тпБюджетныеОперации[к].Поле1 = тпБюджетныеОперации[н].Поле1 Тогда
// Суммируем ресурсы
Для е = 7 по 19 Цикл
тпБюджетныеОперации[к][е] = тпБюджетныеОперации[к][е] + тпБюджетныеОперации[н][е];
КонецЦикла;
// Добавляем строку в очередь на удаление, если она еще не там
Если МассивУдаления.Найти(тпБюджетныеОперации[н]) = Неопределено Тогда
МассивУдаления.Добавить(тпБюджетныеОперации[н]);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
// Удаляем накопленные строки
Для Каждого СтрокаДляУдаления Из МассивУдаления Цикл
тпБюджетныеОперации.Удалить(СтрокаДляУдаления);
КонецЦикла;
Важный нюанс: при использовании метода Удалить() в 1С можно передавать как индекс строки, так и саму СтрокаТаблицыЗначений. Использование ссылки на строку в данном случае предпочтительнее и надежнее.
Если вам необходимо удалять строки сразу, не создавая дополнительных массивов, следует использовать обход коллекции с конца к началу. В этом случае удаление элемента не влияет на индексы тех строк, которые циклу еще только предстоит обработать.
Рассмотрим пример реализации удаления через обратный цикл:
Колво = тпБюджетныеОперации.Количество();
Для Индекс = 1 По Колво Цикл
ОбратныйИндекс = Колво - Индекс;
ТекущаяСтрока = тпБюджетныеОперации[ОбратныйИндекс];
// Проверяем условие для удаления
Если НужноУдалитьСтроку(ТекущаяСтрока) Тогда
тпБюджетныеОперации.Удалить(ТекущаяСтрока);
КонецЕсли;
КонецЦикла;
Этот метод считается классическим в программировании на 1С и часто рекомендуется в официальной документации (ЖКК) для операций очистки коллекций.
В 1С существует встроенный инструмент, который делает всю вышеописанную работу (группировку и суммирование) за одну микросекунду. Это метод Свернуть(). Рассмотрим его, так как он является наиболее оптимальным для большинства задач.
Синтаксис метода: ТаблицаЗначений.Свернуть("КолонкиГруппировки", "КолонкиСуммирования").
Проанализируем, как это применить к нашей задаче:
// Допустим, колонки 1 и 2 называются "Подразделение" и "Статья"
// А колонки 7-19 — это "Месяц1", "Месяц2" и т.д.
КолонкиГруппировки = "Подразделение, Статья";
КолонкиСуммирования = "СуммаЯнварь, СуммаФевраль, СуммаМарт"; // и так далее
тпБюджетныеОперации.Свернуть(КолонкиГруппировки, КолонкиСуммирования);
Преимущества метода Свернуть():
Если метод Свернуть() вам не подходит (например, логика суммирования слишком сложная и требует дополнительных проверок), вместо вложенных циклов со сложностью O(n²) лучше использовать объект Соответствие.
Суть метода заключается в том, что вы проходите по таблице один раз. В качестве ключа соответствия используете уникальную комбинацию ваших измерений (например, структуру или строку), а в качестве значения — уже найденную строку таблицы, куда нужно плюсовать данные. Если в соответствии ключа еще нет — добавляем текущую строку. Если есть — суммируем данные и помечаем текущую строку на удаление.
Этот подход позволяет обрабатывать огромные таблицы (десятки тысяч строк) за доли секунды, в то время как вложенные циклы могут заставить пользователя ждать несколько минут.
Подводя итог, разберем несколько рекомендаций, которые помогут избежать ошибок в будущем:
НайтиСтроки(), обязательно добавьте индексы: тпБюджетныеОперации.Индексы.Добавить("Колонка1, Колонка2").Число. В противном случае вместо 10 + 20 = 30 вы можете получить ошибку при попытке сложить число со значением Неопределено.тпБюджетныеОперации.СкопироватьКолонки()) и заполнять ее только нужными данными, чем пытаться удалять строки из исходной.Мы проанализировали различные подходы к обработке данных в таблицах 1С. Выбор конкретного метода зависит от сложности вашей задачи, но всегда стоит стремиться к использованию встроенных механизмов платформы, таких как Свернуть() или механизм Запросов, так как они обеспечивают максимальную производительность и стабильность вашего кода.