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