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

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

При разработке отчетов и обработок в среде 1С:Предприятие часто возникает задача получить записи регистра накопления или сведений, которые были сформированы строго определенным видом документа (например, только документами ПеремещениеТоваров или РеализацияТоваровУслуг). Поле Регистратор в регистрах имеет составной тип данных, включающий ссылки на все документы, способные делать движения по этому регистру. Из-за этой особенности стандартные методы фильтрации «в лоб» часто приводят к синтаксическим ошибкам в запросах или критическому снижению производительности информационной базы.

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

Решение 1. Использование логического оператора ССЫЛКА

Самым простым, лаконичным и наиболее производительным способом отбора записей по конкретному типу регистратора является использование встроенного оператора языка запросов ССЫЛКА. Этот оператор проверяет, соответствует ли ссылка в поле составного типа указанному метаданному.

Рассмотрим пример запроса, выбирающего движения из регистра накопления ТоварыВРознице, сделанные исключительно документами ПеремещениеТоваров:


ВЫБРАТЬ
    ТоварыВРознице.Период КАК Период,
    ТоварыВРознице.Регистратор КАК Регистратор,
    ТоварыВРознице.Номенклатура КАК Номенклатура,
    ТоварыВРознице.Количество КАК Количество
ИЗ
    РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
ГДЕ
    ТоварыВРознице.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
    И ТоварыВРознице.Период МЕЖДУ &НачалоПериода И &КонецПериода

Почему этот подход предпочтителен? При трансляции запроса на уровень СУБД платформа 1С преобразует оператор ССЫЛКА в эффективную SQL-конструкцию, выполняющую быструю проверку идентификатора типа (разделяемого по таблицам СУБД). Это избавляет систему от необходимости выполнять избыточные соединения (JOIN) со всеми таблицами документов-регистраторов.

Решение 2. Использование функции ТИПЗНАЧЕНИЯ

Другой подход, часто применяемый разработчиками, — использование встроенной функции ТИПЗНАЧЕНИЯ совместно с конструктором ТИП или системным методом ЗНАЧЕНИЕ. Этот способ стал доступен начиная с платформы 8.2 и предоставляет больше гибкости.

Посмотрим на пример реализации такого запроса:


ВЫБРАТЬ
    ТоварыВРознице.Период КАК Период,
    ТоварыВРознице.Регистратор КАК Регистратор,
    ТоварыВРознице.Количество КАК Количество
ИЗ
    РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
ГДЕ
    ТИПЗНАЧЕНИЯ(ТоварыВРознице.Регистратор) = ТИП(Документ.ПеремещениеТоваров)

Также допускается использование конструкции ЗНАЧЕНИЕ, но в ней обязательно нужно указывать пустую ссылку на документ проверяемого типа:


ГДЕ
    ТИПЗНАЧЕНИЯ(ТоварыВРознице.Регистратор) = ЗНАЧЕНИЕ(Документ.ПеремещениеТоваров.ПустаяСсылка)

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

Сравнение оператора ССЫЛКА и функции ТИПЗНАЧЕНИЯ

Несмотря на кажущуюся схожесть, между этими двумя инструментами есть концептуальная разница, которую необходимо понимать для написания качественного кода:

Динамический отбор по нескольким типам документов

В реальной практике часто возникает ситуация, когда пользователю на форме предоставляется возможность выбрать несколько произвольных типов документов-регистраторов, и система должна вывести движения только по ним. Как реализовать это оптимально?

Устаревший подход (сборка запроса склейкой строк):
Многие программисты собирают текст запроса динамически в цикле, формируя длинную строку условий вида ГДЕ Регистратор ССЫЛКА Документ.А ИЛИ Регистратор ССЫЛКА Документ.Б.... Этот метод плох тем, что СУБД при каждом изменении состава документов вынуждена заново компилировать план SQL-запроса, что снижает общую производительность системы и забивает кэш планов СУБД.

Современный оптимизированный подход (через временную таблицу типов):
Мы можем передать список интересующих нас типов документов через параметр запроса в виде таблицы значений, содержащей пустые ссылки выбранных документов. Запрос при этом остается абсолютно статичным.

Проанализируем пример реализации данного алгоритма:


// 1. Создаем таблицу значений для передачи типов документов
ТаблицаВыбранныхТипов = Новый ТаблицаЗначений;
ТаблицаВыбранныхТипов.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("ДокументСсылка.ПеремещениеТоваров, ДокументСсылка.РеализацияТоваровУслуг"));

// Наполняем таблицу пустыми ссылками нужных типов документов
НоваяСтрока = ТаблицаВыбранныхТипов.Добавить();
НоваяСтрока.Ссылка = Документы.ПеремещениеТоваров.ПустаяСсылка();

НоваяСтрока = ТаблицаВыбранныхТипов.Добавить();
НоваяСтрока.Ссылка = Документы.РеализацияТоваровУслуг.ПустаяСсылка();

// 2. Инициализируем запрос со статическим текстом
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТИПЗНАЧЕНИЯ(ВТ_ПараметрыТипов.Ссылка) КАК ТипДокумента
    |ПОМЕСТИТЬ ВТ_ВыбранныеТипыРегистраторов
    |ИЗ
    |    &ТаблицаВыбранныхТипов КАК ВТ_ПараметрыТипов
    |;
    |
    |////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ТоварыВРознице.Период КАК Период,
    |    ТоварыВРознице.Регистратор КАК Регистратор,
    |    ТоварыВРознице.Количество КАК Количество
    |ИЗ
    |    РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
    |ГДЕ
    |    ТИПЗНАЧЕНИЯ(ТоварыВРознице.Регистратор) В (ВЫБРАТЬ ТипДокумента ИЗ ВТ_ВыбранныеТипыРегистраторов)";

Запрос.УстановитьПараметр("ТаблицаВыбранныхТипов", ТаблицаВыбранныхТипов);
РезультатВыполнения = Запрос.Выполнить();

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

Критическая проблема производительности: обращение к реквизитам «через точку»

Разберем классическую ошибку, которая способна замедлить работу крупной информационной базы в сотни раз. Попытка получить свойства регистратора напрямую через разыменование поля составного типа (например, ТоварыВРознице.Регистратор.Дата или ТоварыВРознице.Регистратор.Организация) приводит к тяжелым последствиям на уровне СУБД.

Что происходит «под капотом» СУБД: Поскольку поле Регистратор может ссылаться на десятки различных видов документов, платформа 1С при попытке неявного обращения к реквизиту «через точку» генерирует SQL-запрос, содержащий левые соединения (LEFT OUTER JOIN) со всеми таблицами документов, которые назначены регистраторами для данного регистра. В итоге получается огромный SQL-код с десятками соединений, выполнение которого парализует работу сервера СУБД.

Как правильно решать эту задачу: Если вам действительно необходимо получить реквизит конкретного регистратора, обязательно используйте оператор явного приведения типов ВЫРАЗИТЬ. Он указывает платформе, к какой конкретно таблице нужно выполнить соединение:


ВЫБРАТЬ
    ТоварыВРознице.Регистратор КАК Регистратор,
    // Явное приведение типов исключает лишние соединения СУБД:
    ВЫРАЗИТЬ(ТоварыВРознице.Регистратор КАК Документ.ПеремещениеТоваров).Дата КАК ДатаПеремещения,
    ВЫРАЗИТЬ(ТоварыВРознице.Регистратор КАК Документ.ПеремещениеТоваров).СкладОтправитель КАК СкладОтправитель
ИЗ
    РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
ГДЕ
    ТоварыВРознице.Регистратор ССЫЛКА Документ.ПеремещениеТоваров

Благодаря использованию функции ВЫРАЗИТЬ, СУБД выполнит ровно одно левое соединение только с физической таблицей документа ПеремещениеТоваров, что гарантирует мгновенное выполнение вашего запроса.

Особенности работы с виртуальными таблицами

Часто программисты пытаются применить фильтрацию по типу регистратора к виртуальным таблицам остатков и оборотов регистров накопления. Важно помнить следующее правило оптимизации:

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

Рекомендация: Если вам необходима выборка из виртуальной таблицы оборотов с детализацией по конкретному виду регистратора, лучшим решением по скорости работы будет сначала выбрать сами документы нужного типа во временную таблицу за требуемый период, а затем соединить полученный результат с физической таблицей движений регистра (метод «в лоб»), избегая ресурсоемких расчетов виртуальных таблиц СУБД по лишним регистраторам.

← На главную