Как в запросе 1С получить последнюю запись по конкретному полю (группе)?

Программист 1С v8.3 (Управляемые формы)
← На главную

В практике разработки на платформе 1С:Предприятие часто возникает задача получения «среза» последних данных. Например, когда у нас есть список документов или записей бизнес-процесса по разным адресам, и нам нужно выгрузить для каждого адреса только одну, самую актуальную строку (с максимальной датой), включая связанные с ней реквизиты, такие как телефон или ответственное лицо.

Простая группировка в лоб здесь не поможет, так как включение дополнительных полей (телефон, заявитель) в секцию СГРУППИРОВАТЬ ПО приведет к тому, что система выдаст все уникальные комбинации этих полей, а не одну строку с максимальной датой. В этой статье мы разберем правильные подходы к решению этой задачи.

Разбор типичной ошибки при использовании группировки

Рассмотрим ситуацию, которую описывает автор: в базе есть записи с реквизитами Адрес, Дата, Заявитель и Телефон. По одному адресу может быть несколько записей на разные даты.

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


ВЫБРАТЬ
    Док.Адрес КАК Адрес,
    Док.Заявитель КАК Заявитель,
    Док.Телефон КАК Телефон,
    МАКСИМУМ(Док.Дата) КАК ДатаДок
ИЗ
    БизнесПроцесс.Док КАК Док
СГРУППИРОВАТЬ ПО
    Док.Адрес,
    Док.Заявитель,
    Док.Телефон

Почему это не работает? Механизм СГРУППИРОВАТЬ ПО работает так, что он схлопывает только те строки, в которых значения всех перечисленных полей совпадают. Если по одному адресу были разные заявители, запрос выдаст столько строк по этому адресу, сколько было разных заявителей. Наша же цель — получить строго одну строку по адресу, соответствующую максимальной дате.

Эталонное решение: Использование временной таблицы и соединения

Чтобы решить задачу корректно, мы разделим процесс на два этапа. Сначала мы определим, какая дата является максимальной для каждого адреса, а затем «подтянем» остальные данные, соответствующие этой дате и адресу.

Рассмотрим алгоритм по шагам:

  1. Создание временной таблицы с максимумами. Мы выбираем Адрес и применяем агрегатную функцию МАКСИМУМ к полю Дата. Группируем только по полю Адрес. Результат помещаем во временную таблицу.
  2. Соединение с основной таблицей. Мы выбираем данные из исходной таблицы (документа или бизнес-процесса) и соединяем их с нашей временной таблицей по двум полям: Адрес и Дата.

Посмотрим на пример реализации такого запроса:


// Шаг 1: Получаем максимальные даты по каждому адресу
ВЫБРАТЬ
    БизнесПроцессДок.Адрес КАК Адрес,
    МАКСИМУМ(БизнесПроцессДок.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
    БизнесПроцесс.Док КАК БизнесПроцессДок
СГРУППИРОВАТЬ ПО
    БизнесПроцессДок.Адрес
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем все данные, соединяясь с временной таблицей
ВЫБРАТЬ
    БизнесПроцессДок.Адрес КАК Адрес,
    БизнесПроцессДок.Дата КАК Дата,
    БизнесПроцессДок.Заявитель КАК Заявитель,
    БизнесПроцессДок.Телефон КАК Телефон
ИЗ
    ВТ_МаксимальныеДаты КАК МаксимальныеДаты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ БизнесПроцесс.Док КАК БизнесПроцессДок
        ПО МаксимальныеДаты.Адрес = БизнесПроцессДок.Адрес
            И МаксимальныеДаты.МаксДата = БизнесПроцессДок.Дата

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

Проанализируем критическую уязвимость вышеописанного метода. В системе 1С время хранится с точностью до секунды. Существует вероятность, что по одному и тому же адресу в одну и ту же секунду будут созданы две разные записи. В этом случае внутреннее соединение по Адрес и Дата вернет обе эти записи, и условие «одна строка на адрес» будет нарушено.

Для гарантированной уникальности профессиональные разработчики используют ссылку на объект (Ссылка). Усложним наш алгоритм для исключения дублей:

  1. Находим максимальную дату для каждого адреса (как в предыдущем примере).
  2. Находим максимальную ссылку среди тех записей, у которых дата совпадает с максимальной. Это позволит выбрать ровно один объект, даже если даты идентичны.
  3. Соединяемся по этой ссылке с основной таблицей.

Пример продвинутого запроса:


ВЫБРАТЬ
    Док.Адрес КАК Адрес,
    МАКСИМУМ(Док.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_Даты
ИЗ
    БизнесПроцесс.Док КАК Док
СГРУППИРОВАТЬ ПО
    Док.Адрес
;

ВЫБРАТЬ
    Док.Адрес КАК Адрес,
    МАКСИМУМ(Док.Ссылка) КАК МаксСсылка
ПОМЕСТИТЬ ВТ_Ссылки
ИЗ
    ВТ_Даты КАК Даты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ БизнесПроцесс.Док КАК Док
        ПО Даты.Адрес = Док.Адрес
            И Даты.МаксДата = Док.Дата
СГРУППИРОВАТЬ ПО
    Док.Адрес
;

ВЫБРАТЬ
    Док.Адрес,
    Док.Дата,
    Док.Заявитель,
    Док.Телефон
ИЗ
    ВТ_Ссылки КАК ВТ_Ссылки
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ БизнесПроцесс.Док КАК Док
        ПО ВТ_Ссылки.МаксСсылка = Док.Ссылка

Альтернатива: Использование регистров сведений

Выясним, нельзя ли упростить архитектуру. Если ваша задача предполагает частое получение «последних значений» (телефона, заявителя) по какому-то объекту (адресу), то использование документов или бизнес-процессов для этих целей не совсем оптимально.

В 1С для этого предназначен объект Регистр сведений с установленным свойством Периодичность. Если хранить данные в таком регистре, задача решается максимально просто и быстро с помощью виртуальной таблицы СрезПоследних.

Запрос в таком случае выглядел бы так:


ВЫБРАТЬ
    РегистрСрезПоследних.Адрес,
    РегистрСрезПоследних.Заявитель,
    РегистрСрезПоследних.Телефон,
    РегистрСрезПоследних.Период
ИЗ
    РегистрСведений.КонтактыПоАдресам.СрезПоследних(&Период, ) КАК РегистрСрезПоследних

Этот метод работает на уровне СУБД гораздо эффективнее, чем ручные соединения временных таблиц, так как платформа 1С сама оптимизирует выборку последних записей.

Нюансы производительности и индексация

Проанализируем производительность нашего запроса на больших объемах данных (десятки и сотни тысяч записей):

Таким образом, мы выяснили, что получение последней записи — это задача, требующая четкого понимания механизмов группировки и соединений в 1С. Использование временных таблиц является стандартом разработки, гарантирующим корректность и производительность вашего кода.

← На главную