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

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

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

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

Постановка практической задачи

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

Для решения этой задачи нам нужно получить дату последней активности по каждому контрагенту и сопоставить ее с граничной датой (3 месяца назад). Разберем по шагам, как спроектировать такой запрос максимально эффективно.

Анализ неэффективных подходов и типичные ошибки

Рассмотрим подробнее подходы, которые часто используют начинающие разработчики, и разберем, почему они работают неоптимально.

1. Использование физической таблицы регистра вместо виртуальной.
Попытка выбрать данные напрямую из таблицы движений регистра накопления (например, РегистрНакопления.Потолки) с последующей группировкой по максимальной дате приводит к сканированию всей таблицы на уровне СУБД (Table Scan или Index Scan). На базах данных большого объема такой запрос гарантированно вызовет просадку производительности и блокировки таблиц.

2. Коррелированные подзапросы.
Иногда разработчики пытаются использовать коррелированные (связанные) подзапросы в секции ГДЕ или в списке выборки. Напомним, что платформа «1С:Предприятие» имеет существенные ограничения на использование коррелированных запросов:

3. Попытка наложить взаимоисключающие условия в одном запросе.
Типичная ошибка выглядит следующим образом: разработчик пытается соединить справочник контрагентов с виртуальной таблицей оборотов левым соединением, одновременно накладывая условие на наличие оборотов и на то, что период равен NULL (чтобы найти тех, у кого не было продаж):


// Пример ошибочной логики запроса
ГДЕ
    ПотолкиОбороты.КолФотоПечать > 0
    И ПотолкиОбороты.Период ЕСТЬ NULL

Проанализируем ситуацию: СУБД не сможет вернуть ни одной строки по такому условию, поскольку поле КолФотоПечать не может быть одновременно больше нуля и содержать значение NULL. В результате запрос всегда будет возвращать пустой результат.

Оптимальное решение: Пакетный запрос и временные таблицы

Наиболее эффективным и технологически правильным способом решения данной задачи в 1С является использование пакетных запросов с временными таблицами. Этот подход позволяет разделить логику на два простых и быстрых шага:

  1. Шаг 1: Собрать во временную таблицу всех контрагентов, у которых были заказы фотопечати за последние 3 месяца. Для этого мы задействуем виртуальную таблицу Обороты регистра накопления. Виртуальные таблицы оптимизированы платформой и используют предварительно рассчитанные итоги, что гарантирует высокую скорость выборки.
  2. Шаг 2: Получить полный список контрагентов, у которых заполнен ответственный дизайнер (поле Печатник), и связать его левым соединением с нашей временной таблицей. Те контрагенты, для которых связь не установилась (значение ключевого поля из временной таблицы равно NULL), и есть наши целевые клиенты, не совершавшие покупок более 3 месяцев.

Посмотрим на пример реализации этого алгоритма в коде 1С:


// Вычисляем граничную дату (3 месяца назад от текущей даты)
НачДата = ТекущаяДата() - (3 * 30 * 24 * 60 * 60); // Примерно 90 дней в секундах
КонДата = ТекущаяДата();

Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ РАЗЛИЧНЫЕ
|    ПотолкиОбороты.Заказчик КАК Заказчик
|ПОМЕСТИТЬ врАктивныеКонтрагенты
|ИЗ
|    РегистрНакопления.Потолки.Обороты(&НачДата, &КонДата, , ) КАК ПотолкиОбороты
|ГДЕ
|    ПотолкиОбороты.КолФотоПечать <> 0
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    Контрагент.Ссылка КАК Ссылка
|ИЗ
|    Справочник.Контрагент КАК Контрагент
|        ЛЕВОЕ СОЕДИНЕНИЕ врАктивныеКонтрагенты КАК Активные
|        ПО Контрагент.Ссылка = Активные.Заказчик
|ГДЕ
|    Контрагент.Печатник <> ЗНАЧЕНИЕ(Справочник.Контрагент.ПустаяСсылка)
|    И Активные.Заказчик ЕСТЬ NULL
|
|УПОРЯДОЧИТЬ ПО
|    Ссылка";

Запрос.УстановитьПараметр("НачДата", НачДата);
Запрос.УстановитьПараметр("КонДата", КонецДня(КонДата));

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

Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    // Здесь мы получаем контрагентов, которым нужно очистить дизайнера
    Сообщить("Удаляем дизайнера у контрагента: " + ВыборкаДетальныеЗаписи.Ссылка);
    
    // В реальной задаче здесь будет код изменения элемента справочника:
    // ОбъектКонтрагент = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
    // ОбъектКонтрагент.Печатник = Справочники.Контрагент.ПустаяСсылка();
    // ОбъектКонтрагент.Записать();
КонецЦикла;

Разбор работы запроса на уровне СУБД

Давайте подробно разберем, почему данный пакетный запрос работает во много раз быстрее любых альтернативных вариантов:

Срез последних на каждую дату (Регистратор и Номер строки)

Иногда перед разработчиком стоит более сложная задача: получить не просто факт отсутствия движений, а выгрузить строго последнюю запись (последний физический документ-регистратор) по каждому аналитическому разрезу. В регистрах накопления движения могут происходить в одну и ту же секунду. Чтобы однозначно определить, какая запись была сделана последней, используется классическое трио полей в порядке приоритета:

  1. Период — дата и время совершения движения.
  2. Регистратор — ссылка на документ, сделавший запись.
  3. НомерСтроки — уникальный порядковый номер записи внутри документа.

Для решения такой задачи в современных версиях платформы (начиная с версии 8.3.13) можно использовать функцию языка запросов АВТОНОМЕРЗАПИСИ() во временных таблицах. Она позволяет пронумеровать строки внутри группы. Однако помните о важном предостережении из официальной документации 1С: начальное значение этой функции зависит от настроек конкретной СУБД и может начинаться не с единицы. Поэтому используйте ее для упорядочивания и последующего сопоставления, но избегайте жестких условий вида ГДЕ НомерСтрокиЗапроса = 1 без предварительного анализа структуры данных.

Рекомендации разработчику

Для поддержания высокой производительности вашей системы при работе с регистрами накопления всегда следуйте правилам:

← На главную