При работе с регистрами накопления часто возникает задача получить не только итоговые цифры по остаткам и оборотам, но и увидеть, какие именно документы сформировали эти движения. Интуитивно кажется, что для этого нужно соединить виртуальную таблицу регистра с таблицей документов. Однако этот подход в корне неверен и приводит к серьезным проблемам с производительностью. Рассмотрим, как правильно и эффективно получить регистратор в запросе к остаткам и оборотам.
Проанализируем типичную ошибку, с которой сталкиваются разработчики. Исходная задача — получить остатки и приход товаров на складе, а также документ, который этот приход сформировал. Первый вариант запроса, который приходит на ум, может выглядеть так:
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Склад,
ТоварыНаСкладахОстаткиИОбороты.Номенклатура,
ТоварыНаСкладахОстаткиИОбороты.КоличествоКонечныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.КоличествоПриход
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Год) КАК ТоварыНаСкладахОстаткиИОбороты,
Документ.ПеремещениеТоваров КАК ПеремещениеТоваров
ГДЕ
ТоварыНаСкладахОстаткиИОбороты.Склад = &Склад
В этом запросе есть критическая ошибка. В секции ИЗ перечислены две таблицы: виртуальная таблица регистра и таблица документов. Однако между ними нет условия соединения (через ключевое слово СОЕДИНЕНИЕ ... ПО ...). Такое построение запроса приводит к так называемому кросс-соединению (или декартову произведению).
Что это значит? Каждая строка из первой таблицы соединяется с каждой строкой из второй. Если в результате запроса к регистру получилось 100 строк, а в таблице `Документ.ПеремещениеТоваров` за все время есть 1000 документов, то итоговый результат будет содержать 100 * 1000 = 100 000 строк. Это не только абсолютно некорректный результат, но и колоссальная нагрузка на систему. Неудивительно, что такие запросы выполняются в десятки раз медленнее, чем стандартные отчеты.
Платформа 1С предоставляет мощный и элегантный механизм для решения подобных задач без лишних соединений. Секрет кроется в параметрах самой виртуальной таблицы ОстаткиИОбороты.
Синтаксис виртуальной таблицы выглядит так:
РегистрНакопления.<ИмяРегистра>.ОстаткиИОбороты(<НачалоПериода>, <КонецПериода>, <Периодичность>, <МетодДополнения>, <Условие>, <Измерения>)
Нас интересует третий параметр — Периодичность. Он определяет, в каком разрезе будут сгруппированы обороты. Мы можем указать не только стандартные периоды (День, Неделя, Месяц, Год), но и специальное значение — Регистратор.
Если в качестве периодичности указать Регистратор, то система сгруппирует все движения за указанный период в разрезе документов-регистраторов, и в выборке станет доступно поле Регистратор.
Давайте исправим наш запрос:
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Склад,
ТоварыНаСкладахОстаткиИОбороты.Номенклатура,
ТоварыНаСкладахОстаткиИОбороты.КоличествоКонечныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.КоличествоПриход,
ТоварыНаСкладахОстаткиИОбороты.Регистратор
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода,
Регистратор,
, // Метод дополнения не используем
Склад = &Склад) КАК ТоварыНаСкладахОстаткиИОбороты
Проанализируем изменения:
ИЗ убрали таблицу Документ.ПеремещениеТоваров, тем самым избавившись от кросс-соединения.ОстаткиИОбороты третьим параметром мы указали Регистратор.ВЫБРАТЬ мы добавили поле ТоварыНаСкладахОстаткиИОбороты.Регистратор. Теперь оно доступно.Обратите внимание на еще один важный аспект в исправленном запросе. Условие отбора по складу Склад = &Склад перенесено из секции ГДЕ в пятый параметр виртуальной таблицы — Условие.
Почему это важно?
Когда вы накладываете условие в секции ГДЕ, система сначала получает все данные из виртуальной таблицы, а уже потом фильтрует их. Если же вы используете параметр Условие, то фильтрация происходит на более низком уровне, еще до формирования итогового набора данных. Это позволяет СУБД максимально эффективно использовать индексы таблиц регистра и значительно ускоряет выполнение запроса.
Всегда старайтесь переносить условия по измерениям регистра в параметры виртуальной таблицы, а не оставлять их в секции ГДЕ.
Окончательный, правильный и производительный вариант запроса, который решает поставленную задачу, может выглядеть так:
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Склад,
ТоварыНаСкладахОстаткиИОбороты.Номенклатура,
ТоварыНаСкладахОстаткиИОбороты.КоличествоКонечныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.КоличествоПриход,
ТоварыНаСкладахОстаткиИОбороты.Регистратор
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода,
Регистратор,
Движения, // Используем "Движения", чтобы получить только записи с оборотами
Склад = &Склад) КАК ТоварыНаСкладахОстаткиИОбороты
ГДЕ
ТоварыНаСкладахОстаткиИОбороты.КоличествоКонечныйОстаток > 0 // Отбор по итоговому значению оставляем в ГДЕ
Важный момент: Поле Регистратор будет заполнено только для тех строк, которые отражают обороты (приход или расход) за выбранный период. Начальные и конечные остатки — это агрегированные, итоговые значения на начало и конец периода, они не привязаны к одному конкретному документу. Поэтому в строке, где нет оборотов, а есть только остаток, поле Регистратор будет пустым.
Использование параметра Движения в качестве четвертого параметра (МетодДополнения) позволяет получить только те записи из измерений, по которым были движения в периоде, что может быть полезно для отсечения лишних данных.
Таким образом, для получения регистратора в запросах к остаткам и оборотам всегда используйте встроенные возможности виртуальных таблиц. Это не только правильный подход с точки зрения архитектуры платформы, но и залог высокой производительности ваших отчетов и обработок.