При разработке отчетов и обработок в среде 1С часто возникает специфическая задача: необходимо получить данные не из всей табличной части (ТЧ) документа, а только из её последней строки. Это может быть актуально для получения актуального статуса, последнего начисленного значения или итоговой записи в сложном документе. Прямое использование агрегатных функций или простых условий в соединении часто приводит к ошибкам или неверным результатам.
В этой статье мы подробно разберем, почему стандартные подходы могут не работать, и проанализируем наиболее эффективные способы решения этой задачи с помощью языка запросов 1С.
Рассмотрим типичную ошибку начинающего разработчика. Попытка написать условие в соединении вида ДокТЗ.НомерСтроки = Количество(ДокТЗ.НомерСтроки) приведет к тому, что система выдаст ошибку. Язык запросов 1С не позволяет использовать агрегатные функции в условиях связи (ПО) или в секции ГДЕ без предварительной группировки.
Использование конструкции ВЫБРАТЬ ПЕРВЫЕ 1 вместе с УПОРЯДОЧИТЬ ПО НомерСтроки УБЫВ отлично работает, если нам нужно получить строку из одного конкретного документа. Однако, если задача стоит получить последнюю строку для целого списка документов или для каждого сотрудника в выборке, этот метод не подойдет, так как он ограничит всю итоговую выборку одной строкой.
Это самый надежный и производительный способ. Разберем его по шагам. Идея заключается в том, чтобы сначала вычислить максимальный номер строки для каждого документа, сохранить его во временную таблицу, а затем соединить эту таблицу с исходной табличной частью.
Шаг 1: Получаем максимальные номера строк
Проанализируем первый этап запроса. Мы выбираем ссылку на документ и применяем функцию МАКСИМУМ к полю НомерСтроки.
ВЫБРАТЬ
ДокТЗ.Ссылка КАК Ссылка,
МАКСИМУМ(ДокТЗ.НомерСтроки) КАК МаксНомерСтроки
ПОМЕСТИТЬ втПоследниеСтроки
ИЗ
Документ.НачислениеЗарплаты.Начисления КАК ДокТЗ
ГДЕ
ДокТЗ.Ссылка.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
ДокТЗ.Ссылка;
Шаг 2: Соединение для получения данных строки
Теперь, когда у нас есть "адреса" последних строк (пары Ссылка + НомерСтроки), мы выполняем соединение с исходной табличной частью, чтобы вытащить нужные нам реквизиты (например, Сотрудник или Сумма).
ВЫБРАТЬ
втПоследниеСтроки.Ссылка КАК Документ,
ДокТЗ.Сотрудник КАК Сотрудник,
ДокТЗ.Результат КАК Результат
ИЗ
втПоследниеСтроки КАК втПоследниеСтроки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.НачислениеЗарплаты.Начисления КАК ДокТЗ
ПО втПоследниеСтроки.Ссылка = ДокТЗ.Ссылка
И втПоследниеСтроки.МаксНомерСтроки = ДокТЗ.НомерСтроки;
Ситуация усложняется, если нам нужно получить последнюю строку не просто документа, а последнюю запись по конкретному сотруднику среди всех его документов за период. Рассмотрим, как проанализировать такую ситуацию. Здесь недостаточно просто найти МАКСИМУМ(НомерСтроки), так как нам сначала нужно определить "самый последний" документ.
В этом случае алгоритм будет следующим:
МоментВремени) документа для каждого сотрудника.Посмотрим на пример реализации такого сложного отбора:
// Сначала найдем последние документы сотрудников
ВЫБРАТЬ
Док.Сотрудник КАК Сотрудник,
МАКСИМУМ(Док.МоментВремени) КАК МаксМомент
ПОМЕСТИТЬ втПоследниеДокументы
ИЗ
Документ.НачислениеЗарплаты КАК Док
ГДЕ
Док.Дата МЕЖДУ &НачДата И &КонДата
СГРУППИРОВАТЬ ПО
Док.Сотрудник;
////////////////////////////////////////////////////////////////////////////////
// Теперь найдем максимальный номер строки в этих документах
ВЫБРАТЬ
втПоследниеДокументы.Сотрудник,
ДокТЗ.Ссылка,
МАКСИМУМ(ДокТЗ.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ втПоследниеСтрокиПоСотрудникам
ИЗ
втПоследниеДокументы КАК втПоследниеДокументы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.НачислениеЗарплаты.Начисления КАК ДокТЗ
ПО втПоследниеДокументы.МаксМомент = ДокТЗ.Ссылка.МоментВремени
И втПоследниеДокументы.Сотрудник = ДокТЗ.Сотрудник
СГРУППИРОВАТЬ ПО
втПоследниеДокументы.Сотрудник,
ДокТЗ.Ссылка;
////////////////////////////////////////////////////////////////////////////////
// Получаем финальные данные
ВЫБРАТЬ
вт.Сотрудник,
ДокТЗ.Начисление,
ДокТЗ.Результат
ИЗ
втПоследниеСтрокиПоСотрудникам КАК вт
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.НачислениеЗарплаты.Начисления КАК ДокТЗ
ПО вт.Ссылка = ДокТЗ.Ссылка
И вт.НомерСтроки = ДокТЗ.НомерСтроки;
Существует альтернативный подход без использования агрегатных функций МАКСИМУМ. Мы можем попытаться соединить таблицу саму с собой по условию "номер строки меньше". Если для строки Т1 не найдется строки Т2 с таким же значением Ссылка, но большим НомерСтроки, значит Т1 — последняя.
Рассмотрим подробнее этот прием:
ВЫБРАТЬ
Т1.Ссылка,
Т1.НомерСтроки,
Т1.Сотрудник
ИЗ
Документ.НачислениеЗарплаты.Начисления КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ Документ.НачислениеЗарплаты.Начисления КАК Т2
ПО Т1.Ссылка = Т2.Ссылка
И Т1.НомерСтроки < Т2.НомерСтроки
ГДЕ
Т2.НомерСтроки ЕСТЬ NULL;
Важное замечание: Этот метод может работать медленнее на огромных табличных частях (с тысячами строк в одном документе), так как количество сравнений в SQL-запросе растет нелинейно. Однако он элегантен тем, что позволяет избежать использования временных таблиц в простых сценариях.
При работе с табличными частями следует помнить о нескольких критически важных моментах:
НомерСтроки не является индексированным. Если вы выполняете такие запросы часто по очень большим таблицам, это может создавать нагрузку. Старайтесь сначала ограничивать выборку по полю Ссылка (которое всегда индексировано) или по периоду.МоментВремени вместо Дата. Это позволит корректно обрабатывать ситуацию, когда два документа созданы в одну и ту же секунду.НомерСтроки может "сбиваться", если в конфигурации реализовано нетиповое удаление или перенумерация строк. В 99% случаев МАКСИМУМ(НомерСтроки) — это именно последняя введенная строка, но в специфических системах стоит это проверить.СрезПоследних всегда будет быстрее и правильнее, чем ручной поиск последней строки в документах.Подводя итог, можно сказать, что наиболее универсальным и рекомендуемым "золотым стандартом" в 1С является Метод №1 с временной таблицей. Он обеспечивает отличную читаемость кода и предсказуемую производительность на любых объемах данных.