При разработке отчетов и обработок разработчики часто сталкиваются с ситуацией, когда простой запрос к документу возвращает одно количество записей, а запрос к регистру накопления за тот же период — совершенно другое. Разберем типичную практическую задачу: почему выборка из регистра накопления возвращает значительно больше строк, чем выборка напрямую из таблицы документов, и как построить корректный запрос к регистру.
Проанализируем исходные варианты запросов, с которыми возникли сложности:
Прямой запрос к документам:
ВЫБРАТЬ
ЗаказПокупателя.Ссылка
ИЗ
Документ.ЗаказПокупателя КАК ЗаказПокупателя
ГДЕ
ЗаказПокупателя.Дата МЕЖДУ &НачДата И &КонДата
Запрос к виртуальной таблице регистра:
ВЫБРАТЬ
ЗаказыПокупателейОстаткиИОбороты.ЗаказПокупателя.Ссылка
ИЗ
РегистрНакопления.ЗаказыПокупателей.ОстаткиИОбороты(&НачДата, &КонДата, Регистратор, , ) КАК ЗаказыПокупателейОстаткиИОбороты
В первом случае запрос вернул один документ, а во втором — 41 запись. Выясним, почему это происходит, и рассмотрим правильные варианты реализации выборки.
Разберем фундаментальные различия в структуре хранения данных, которые приводят к разнице в результатах выборки:
ЗаказПокупателя в регистре может заполняться не только самим заказом, но и закрывающими или корректирующими документами (например, РеализацияТоваровУслуг, КорректировкаЗаказаПокупателя, ЗакрытиеЗаказовПокупателей). Если за выбранный период были отгрузки по старым заказам, они также попадут в движения.Документ.ЗаказПокупателя содержит абсолютно все документы (в том числе помеченные на удаление и непроведенные), тогда как в регистр накопления попадают только проведенные документы, сформировавшие движения.ЗаказПокупателя.Ссылка к полю, которое уже является ссылкой, заставляет платформу выполнять избыточное неявное левое соединение (LEFT JOIN) с таблицей документа, что снижает производительность.Если требуется получить список заказов покупателей, которые сами были оформлены и проведены в указанном периоде (то есть являются регистратором движения типа «Приход»), используем виртуальную таблицу Обороты с фильтрацией по типу регистратора и обязательным ключевым словом РАЗЛИЧНЫЕ:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ЗаказыПокупателейОбороты.ЗаказПокупателя КАК ЗаказПокупателя
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(
&НачДата,
&КонДата,
Регистратор,
Регистратор ССЫЛКА Документ.ЗаказПокупателя
) КАК ЗаказыПокупателейОбороты
Обратим внимание на следующие ключевые моменты этой конструкции:
Регистратор ССЫЛКА Документ.ЗаказПокупателя передается в четвертый параметр виртуальной таблицы, а не в блок ГДЕ. Это позволяет СУБД отсечь ненужные движения до этапа расчета агрегатов.РАЗЛИЧНЫЕ: исключает повторение одного и того же заказа, если он содержал несколько строк в табличной части..Ссылка: измерение ЗаказПокупателя само по себе хранит ссылку на документ, поэтому повторное разыменование через точку не требуется.Когда перед нами стоит задача просто получить список заказов-регистраторов без анализа числовых показателей (сумм и количества), использовать виртуальные таблицы Обороты или ОстаткиИОбороты неоптимально. Построение виртуальной таблицы создает лишнюю вычислительную нагрузку на сервер баз данных.
В этом случае гораздо эффективнее обратиться напрямую к физической таблице движений регистра:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Движения.Регистратор КАК ЗаказПокупателя
ИЗ
РегистрНакопления.ЗаказыПокупателей КАК Движения
ГДЕ
Движения.Период МЕЖДУ &НачДата И &КонДата
И Движения.Регистратор ССЫЛКА Документ.ЗаказПокупателя
Рассмотрим, почему этот способ предпочтительнее для простых списков:
Период входит в кластерный индекс.ССЫЛКА Документ.ЗаказПокупателя гарантирует, что в выборку попадут только первичные заказы, исключая документы отгрузки и закрытия.При передаче интервала дат в запрос необходимо учитывать время. Если параметр &КонДата передается без указания времени (например, '2023-10-31 00:00:00'), документы, проведенные в течение дня 31 октября, не попадут в выборку.
Перед выполнением запроса в коде 1С необходимо обязательно приводить границу к концу дня:
Запрос.УстановитьПараметр("НачДата", НачалоДня(НачДата));
Запрос.УстановитьПараметр("КонДата", КонецДня(КонДата));
Таким образом, для получения точного списка заказов покупателей из регистра накопления следует отсекать дубли строк с помощью РАЗЛИЧНЫЕ, фильтровать тип регистратора и корректно передавать границы периода во встроенный язык запросов.