При переходе с платформы 1С 7.7 на 1С 8.x программисты часто сталкиваются с изменением парадигмы обхода коллекций. В старой версии обращение к строкам было процедурным (через метод ПолучитьСтроку()), а в актуальных версиях мы работаем с объектами и индексами. Одной из типовых задач является получение данных из предыдущей строки для расчета разницы дат, вычисления сальдо или накопленного итога. Рассмотрим подробно все способы решения этой задачи.
Самый очевидный способ обратиться к предыдущему элементу — это использовать цикл со счетчиком. В 1С 8.x индексация коллекций (табличных частей, таблиц значений, массивов) начинается с 0. Это критически важно помнить, так как свойство НомерСтроки у табличных частей начинается с 1. Следовательно, индекс строки всегда на единицу меньше её номера: Индекс = НомерСтроки - 1.
Проанализируем пример кода, где нам нужно рассчитать количество дней между датами в текущей и предыдущей строках:
Для Инд = 1 По ТЧ.Количество() - 1 Цикл
ТекущаяСтрока = ТЧ[Инд];
ПредыдущаяСтрока = ТЧ[Инд - 1];
// Вычисляем разницу в секундах и переводим в дни
РазницаВДнях = (ТекущаяСтрока.ДатаДокумента - ПредыдущаяСтрока.ДатаДокумента) / (24 * 60 * 60);
ТекущаяСтрока.Кол_во_Дней = РазницаВДнях;
КонецЦикла;
Разберем, почему мы начинаем цикл с 1, а не с 0. Если мы начнем с нуля, то при попытке обратиться к ТЧ[Инд - 1] система выдаст ошибку «Индекс находится за границами массива», так как индекса -1 не существует. Начиная со второй строки (индекс 1), мы гарантируем наличие предыдущего элемента.
Если нам удобнее использовать цикл Для каждого, который работает быстрее и выглядит лаконичнее, мы можем завести переменную для хранения ссылки на строку из предыдущей итерации. Это наиболее производительный и «чистый» способ с точки зрения кода.
Рассмотрим реализацию этого алгоритма:
ПеременнаяПредСтрока = Неопределено;
Для каждого СтрокаТЧ Из ТЧ Цикл
Если ПеременнаяПредСтрока = Неопределено Тогда
// Это первая итерация, предыдущей строки еще нет
СтрокаТЧ.ТекущееСальдо = СтрокаТЧ.СуммаРасход - СтрокаТЧ.СуммаПриход;
Иначе
// Работаем с данными из предыдущей итерации
СтрокаТЧ.ТекущееСальдо = ПеременнаяПредСтрока.ТекущееСальдо + СтрокаТЧ.СуммаРасход - СтрокаТЧ.СуммаПриход;
// Пример записи значения в ПРЕДЫДУЩУЮ строку
ПеременнаяПредСтрока.Кол_во_Дней = (СтрокаТЧ.Дата - ПеременнаяПредСтрока.Дата) / 86400;
КонецЕсли;
// В конце итерации запоминаем текущую строку как «предыдущую» для следующего шага
ПеременнаяПредСтрока = СтрокаТЧ;
КонецЦикла;
Этот метод хорош тем, что нам не нужно постоянно обращаться к коллекции по индексу, что экономит ресурсы системы при работе с очень большими таблицами.
Иногда разработчики пытаются получить индекс внутри цикла Для каждого, используя метод ТЧ.Индекс(СтрокаТЧ). Проанализируем, почему это плохая практика. Метод Индекс() каждый раз перебирает таблицу с самого начала, чтобы найти адрес конкретной строки. В результате сложность алгоритма возрастает в геометрической прогрессии. Для таблицы в 10 000 строк это приведет к ощутимому зависанию программы. Рекомендация: если вам нужен индекс, используйте цикл Для Счетчик = 0 По... или просто инкрементируйте переменную-счетчик внутри цикла Для каждого.
Выясним причину часто возникающей ошибки «Индексированное значение доступно только для чтения». Она часто проявляется, когда программист пытается присвоить значение всей строке или неправильно обращается к коллекции. Например, попытка написать ТЧ[i] = НоваяСтрока вызовет ошибку. Нужно всегда указывать конкретное поле: ТЧ[i].ИмяПоля = Значение.
Также помните, что если ваша таблица является результатом запроса (РезультатЗапроса.Выбрать()), вы не сможете менять в ней данные. Для редактирования значений «на лету» результат запроса необходимо сначала выгрузить в таблицу значений методом Выгрузить().
Посмотрим на ситуацию шире: иногда задачу эффективнее решить на уровне базы данных. В стандартном SQL для этого существуют функции LAG и LEAD, но в языке запросов 1С их нет. Чтобы получить данные из соседней строки в запросе, мы применяем соединение таблицы самой с собой.
НомерСтроки (используя автоупорядочивание).Т1.НомерСтроки = Т2.НомерСтроки + 1.Этот подход работает значительно быстрее, чем циклы, если требуется обработать десятки тысяч записей для формирования сложного отчета.
Если ваша цель — вывести расчет разницы между строками в отчете, воспользуйтесь встроенными функциями СКД. Функция ВычислитьВыражение() позволяет обращаться к любым строкам группировки.
Пример выражения для ресурса в СКД:
ВычислитьВыражение("Сумма(СуммаОборот)", "Период", , "Предыдущая", "Предыдущая")
Это выражение вернет сумму из предыдущей строки группировки по периоду без написания кода на языке 1С.
Таким образом, выбор метода зависит от контекста задачи: для простых расчетов в форме документа идеально подойдет вспомогательная переменная ПредСтрока, для сложных алгоритмов с индексами — цикл Для...По, а для высокопроизводительных отчетов — механизмы запросов и СКД.