При решении задач управленческого и складского учета часто возникает необходимость выбрать по каждому контрагенту только один — самый актуальный или последний оформленный документ. Стандартный подход с использованием соединения по максимальной дате может приводить к дублированию строк в выборке, если в пределах одной секунды зарегистрировано несколько документов по одному и тому же контрагенту. Разберем, почему возникает эта проблема и как правильно составить запрос для гарантированного получения единственного документа.
Проанализируем типовой запрос, с которого часто начинают разработку такого отчета или алгоритма:
ВЫБРАТЬ
ПоследниеДокументыПоКонтрагентам.Ссылка КАК ПоследнийДокумент,
ПоследниеДокументыПоКонтрагентам.Контрагент
ИЗ
Документ.РеализацияТоваровУслуг КАК ПоследниеДокументыПоКонтрагентам
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
Доки.Контрагент КАК Контрагент,
МАКСИМУМ(Доки.Дата) КАК ДатаПоследнегоДокумента
ИЗ
Документ.РеализацияТоваровУслуг КАК Доки
СГРУППИРОВАТЬ ПО
Доки.Контрагент
) КАК ДатыПоследнихДокументовКонтрагентов
ПО ПоследниеДокументыПоКонтрагентам.Контрагент = ДатыПоследнихДокументовКонтрагентов.Контрагент
И ПоследниеДокументыПоКонтрагентам.Дата = ДатыПоследнихДокументовКонтрагентов.ДатаПоследнегоДокумента
Выясним причину возможной некорректности данного решения. Поле Дата в платформе 1С хранит как дату, так и точное время с точностью до секунды. Однако в реальной базе данных возможны ситуации, когда по одному контрагенту созданы два и более документа с абсолютно одинаковой датой и временем (например, при программной загрузке данных, пакетном проведении или дублировании документов оператором). В этом случае внутреннее соединение вернет столько строк по контрагенту, сколько документов имеют максимальную дату, что нарушит уникальность результата.
Также важно помнить об ограничениях языка запросов: тип МоментВремени не поддерживается в запросах 1С. Он доступен исключительно в объектной модели встроенного языка и не может быть использован в секциях ВЫБРАТЬ или СГРУППИРОВАТЬ ПО.
Рассмотрим классический и наиболее производительный подход для платформы 1С:Предприятие 8.3, который гарантирует устранение дублей даже при полном совпадении даты и времени документов.
Разберем алгоритм по шагам:
Контрагент и МАКСИМУМ(Дата) с обязательной индексацией.МАКСИМУМ(Ссылка) для каждого контрагента, разрешая тем самым коллизию одинаковых дат.Посмотрим на пример реализации запроса с пакетом временных таблиц:
ВЫБРАТЬ
Доки.Контрагент КАК Контрагент,
МАКСИМУМ(Доки.Дата) КАК Дата
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
Документ.РеализацияТоваровУслуг КАК Доки
СГРУППИРОВАТЬ ПО
Доки.Контрагент
ИНДЕКСИРОВАТЬ ПО
Контрагент,
Дата
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Доки.Контрагент КАК Контрагент,
МАКСИМУМ(Доки.Ссылка) КАК Ссылка
ПОМЕСТИТЬ ВТ_ПоследниеСсылки
ИЗ
ВТ_МаксимальныеДаты КАК МаксДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Доки
ПО МаксДаты.Контрагент = Доки.Контрагент
И МаксДаты.Дата = Доки.Дата
СГРУППИРОВАТЬ ПО
Доки.Контрагент
ИНДЕКСИРОВАТЬ ПО
Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Доки.Ссылка КАК ПоследнийДокумент,
Доки.Контрагент КАК Контрагент,
Доки.Дата КАК Дата,
Доки.СуммаДокумента КАК СуммаДокумента
ИЗ
ВТ_ПоследниеСсылки КАК ПослСсылки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Доки
ПО ПослСсылки.Ссылка = Доки.Ссылка
Обратите внимание: использование агрегатной функции МАКСИМУМ(Ссылка) позволяет однозначно отсечь дублирующие записи на уровне СУБД. Однако следует учитывать, что сортировка ссылочных типов опирается на внутренний 128-битный идентификатор (UUID). Если порядок ввода документов строго критичен, а даты совпадают, альтернативой может выступать выборка МАКСИМУМ(Номер) при наличии строгой периодической нумерации.
Рассмотрим современный способ, появившийся в платформе 1С:Предприятие начиная с версии 8.3.27. Благодаря поддержке оконных функций задачу поиска последнего документа можно решить без самосоединений и создания цепочки временных таблиц.
Проанализируем пример с функцией ROW_NUMBER():
ВЫБРАТЬ
ВыборкаДокументов.Ссылка КАК ПоследнийДокумент,
ВыборкаДокументов.Контрагент КАК Контрагент,
ВыборкаДокументов.Дата КАК Дата,
ВыборкаДокументов.СуммаДокумента КАК СуммаДокумента
ИЗ
(ВЫБРАТЬ
Доки.Ссылка КАК Ссылка,
Доки.Контрагент КАК Контрагент,
Доки.Дата КАК Дата,
Доки.СуммаДокумента КАК СуммаДокумента,
ROW_NUMBER() OVER (PARTITION BY Доки.Контрагент ORDER BY Доки.Дата УБЫВ, Доки.Ссылка УБЫВ) КАК НомерСтроки
ИЗ
Документ.РеализацияТоваровУслуг КАК Доки) КАК ВыборкаДокументов
ГДЕ
ВыборкаДокументов.НомерСтроки = 1
В данном запросе окно формируется для каждого контрагента отдельно через PARTITION BY, а внутри секции выполняется сортировка по убыванию даты и ссылки. Фильтрация по условию НомерСтроки = 1 оставляет ровно один актуальный документ на каждого контрагента.
Если перед нами стоит задача получить последний документ не по всем контрагентам базы сразу, а только для одного выбранного контрагента, не следует применять ресурсоемкие группировки. Оптимальным решением будет прямое ограничение выборки:
ВЫБРАТЬ ПЕРВЫЕ 1
Доки.Ссылка КАК ПоследнийДокумент,
Доки.Дата КАК Дата,
Доки.СуммаДокумента КАК СуммаДокумента
ИЗ
Документ.РеализацияТоваровУслуг КАК Доки
ГДЕ
Доки.Контрагент = &Контрагент
УПОРЯДОЧИТЬ ПО
Доки.Дата УБЫВ,
Доки.Ссылка УБЫВ
Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 в сочетании с сортировкой УПОРЯДОЧИТЬ ПО Дата УБЫВ, Ссылка УБЫВ обеспечивает минимальное время выполнения за счет использования штатных индексов таблицы документов.