Как выбрать самый первый документ по группировке в запросе 1С при наличии нескольких записей в секунду

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

В практике разработки на платформе 1С часто возникает задача: получить самый первый документ (регистратор) для каждого объекта (например, контрагента). Казалось бы, задача тривиальная — достаточно использовать МИНИМУМ(Период). Однако дьявол кроется в деталях. Если в рамках одной секунды по одному контрагенту было проведено несколько документов, простое соединение по минимальному периоду приведет к «раздуванию» выборки (дублям), так как система не поймет, какой именно документ из нескольких в этой секунде является первым.

Ситуация осложняется тем, что поле МоментВремени, которое гарантирует уникальную последовательность документов, нельзя использовать в агрегатных функциях МИНИМУМ или МАКСИМУМ. В этой статье мы подробно разберем несколько проверенных способов решения этой задачи в рамках языка запросов и программной обработки.

Метод 1. Использование временной таблицы и функции АВТОНОМЕРЗАПИСИ

Проанализируем наиболее изящный способ реализации задачи чисто средствами языка запросов. Поскольку мы не можем применить МИНИМУМ к моменту времени, мы можем искусственно пронумеровать записи, предварительно отсортировав их в нужном порядке. Рассмотрим по шагам, как это сделать.

На первом этапе мы выбираем все необходимые данные и помещаем их во временную таблицу, обязательно используя УПОРЯДОЧИТЬ ПО. Важно помнить, что в 1С АВТОНОМЕРЗАПИСИ() работает корректно только при сохранении результата во временную таблицу.


ВЫБРАТЬ ПЕРВЫЕ 1000000
    Рег.Контрагент КАК Контрагент,
    Рег.Регистратор КАК Регистратор,
    Рег.Регистратор.МоментВремени КАК МоментВремени,
    АВТОНОМЕРЗАПИСИ() КАК НомерСтроки
ПОМЕСТИТЬ ВТ_ВсеЗаписи
ИЗ
    РегистрНакопления.Взаиморасчеты КАК Рег
ГДЕ
    Рег.Период >= &ДатаНачала
УПОРЯДОЧИТЬ ПО
    Контрагент,
    МоментВремени;

Обратите внимание на конструкцию ВЫБРАТЬ ПЕРВЫЕ 1000000. Это техническая хитрость: без ограничения количества записей 1С может проигнорировать УПОРЯДОЧИТЬ ПО при создании временной таблицы в некоторых версиях СУБД. Далее мы находим минимальный номер строки для каждого контрагента:


ВЫБРАТЬ
    ВТ.Контрагент,
    МИНИМУМ(ВТ.НомерСтроки) КАК МинНомер
ПОМЕСТИТЬ ВТ_ПервыеНомера
ИЗ
    ВТ_ВсеЗаписи КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.Контрагент;

Финальным шагом мы соединяем наши таблицы, чтобы получить ссылку на конкретный регистратор:


ВЫБРАТЬ
    ВТ.Контрагент,
    ВТ.Регистратор
ИЗ
    ВТ_ПервыеНомера КАК Номера
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ВсеЗаписи КАК ВТ ПО Номера.МинНомер = ВТ.НомерСтроки;

Метод 2. Левое соединение по условию «меньшества» момента времени

Разберем классический SQL-подход, который позволяет обойтись без автонумерации. Мы выбираем запись из регистра и пытаемся найти такую же запись по тому же контрагенту, у которой МоментВремени меньше. Если такая запись не найдена (результат соединения ЕСТЬ NULL), значит, текущая запись — самая первая.

Хотя напрямую МоментВремени в соединениях использовать иногда проблематично из-за особенностей платформы, мы можем использовать его в блоке ГДЕ или в условии связи, если тип поля — ссылка на документ, поддерживающий этот метод.


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Тек.Контрагент,
    Тек.Регистратор
ИЗ
    РегистрНакопления.ИмяРегистра КАК Тек
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ИмяРегистра КАК Пред 
    ПО Пред.Контрагент = Тек.Контрагент 
    И Пред.Регистратор.МоментВремени < Тек.Регистратор.МоментВремени
ГДЕ
    Пред.Контрагент ЕСТЬ NULL;

Важное замечание: Этот метод очень требователен к ресурсам. На больших таблицах (миллионы записей) такое соединение «каждый с каждым» по неравенству может крайне негативно сказаться на производительности системы.

Метод 3. Трехшаговый алгоритм (Период + Ссылка)

Выясним причину, по которой метод с МИНИМУМ(Регистратор) считается спорным, но часто рабочим. В 1С ссылки (UUID) не всегда генерируются в строгом хронологическом порядке. Однако в пределах одной секунды использование МИНИМУМ(Ссылка) — это отличный способ детерминированно «схлопнуть» записи.

Рассмотрим надежный алгоритм:

  1. Выбираем МИНИМУМ(Период) по каждому контрагенту во временную таблицу ВТ_МинПериоды.
  2. Соединяем исходный регистр с ВТ_МинПериоды по контрагенту и периоду. В этом запросе выбираем МИНИМУМ(Регистратор) (или МИНИМУМ(Ссылка)) по каждому контрагенту. Это позволит нам выбрать ровно один документ, даже если в первую секунду их было три.
  3. В итоговом запросе получаем все нужные данные документа, соединяясь по уже найденной уникальной ссылке.

Метод 4. Программная обработка выборки (Post-processing)

Посмотрим на ситуацию со стороны производительности. Если данных в регистре очень много, а нам нужно получить первых контрагентов для отчета, иногда выгоднее перенести часть логики из SQL в код 1С. Проанализируем пример такого подхода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Рег.Контрагент КАК Контрагент,
    |   Рег.Регистратор КАК Регистратор
    |ИЗ
    |   РегистрНакопления.Взаиморасчеты КАК Рег
    |УПОРЯДОЧИТЬ ПО
    |   Контрагент,
    |   Рег.МоментВремени";

Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();

ТаблицаПервыхДокументов = Новый ТаблицаЗначений;
ТаблицаПервыхДокументов.Колонки.Добавить("Контрагент");
ТаблицаПервыхДокументов.Колонки.Добавить("Регистратор");

ТекущийКонтрагент = Неопределено;
Пока Выборка.Следующий() Цикл
    Если Выборка.Контрагент <> ТекущийКонтрагент Тогда
        // Это первая запись для нового контрагента в отсортированной выборке
        НоваяСтрока = ТаблицаПервыхДокументов.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
        ТекущийКонтрагент = Выборка.Контрагент;
    КонецЕсли;
    // Остальные записи этого же контрагента просто пропускаем
КонецЦикла;

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

Резюме и рекомендации

Подводя итоги, можно выделить следующие правила выбора метода:

Помните, что МоментВремени — это составной тип на уровне базы данных (дата + ссылка), поэтому правильная сортировка по нему всегда обеспечит верную последовательность документов в соответствии с их позицией на временной оси 1С.

← На главную