При разработке отчетов и обработок в среде 1С:Предприятие часто возникает задача получить записи регистра накопления или сведений, которые были сформированы строго определенным видом документа (например, только документами ПеремещениеТоваров или РеализацияТоваровУслуг). Поле Регистратор в регистрах имеет составной тип данных, включающий ссылки на все документы, способные делать движения по этому регистру. Из-за этой особенности стандартные методы фильтрации «в лоб» часто приводят к синтаксическим ошибкам в запросах или критическому снижению производительности информационной базы.
В этой статье мы подробно разберем, как правильно решать эту задачу в платформе 1С:Предприятие 8.3, проанализируем синтаксис эффективных запросов, сравним различные механизмы фильтрации и рассмотрим передовые методики оптимизации производительности СУБД.
Самым простым, лаконичным и наиболее производительным способом отбора записей по конкретному типу регистратора является использование встроенного оператора языка запросов ССЫЛКА. Этот оператор проверяет, соответствует ли ссылка в поле составного типа указанному метаданному.
Рассмотрим пример запроса, выбирающего движения из регистра накопления ТоварыВРознице, сделанные исключительно документами ПеремещениеТоваров:
ВЫБРАТЬ
ТоварыВРознице.Период КАК Период,
ТоварыВРознице.Регистратор КАК Регистратор,
ТоварыВРознице.Номенклатура КАК Номенклатура,
ТоварыВРознице.Количество КАК Количество
ИЗ
РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
ГДЕ
ТоварыВРознице.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
И ТоварыВРознице.Период МЕЖДУ &НачалоПериода И &КонецПериода
Почему этот подход предпочтителен? При трансляции запроса на уровень СУБД платформа 1С преобразует оператор ССЫЛКА в эффективную SQL-конструкцию, выполняющую быструю проверку идентификатора типа (разделяемого по таблицам СУБД). Это избавляет систему от необходимости выполнять избыточные соединения (JOIN) со всеми таблицами документов-регистраторов.
Другой подход, часто применяемый разработчиками, — использование встроенной функции ТИПЗНАЧЕНИЯ совместно с конструктором ТИП или системным методом ЗНАЧЕНИЕ. Этот способ стал доступен начиная с платформы 8.2 и предоставляет больше гибкости.
Посмотрим на пример реализации такого запроса:
ВЫБРАТЬ
ТоварыВРознице.Период КАК Период,
ТоварыВРознице.Регистратор КАК Регистратор,
ТоварыВРознице.Количество КАК Количество
ИЗ
РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
ГДЕ
ТИПЗНАЧЕНИЯ(ТоварыВРознице.Регистратор) = ТИП(Документ.ПеремещениеТоваров)
Также допускается использование конструкции ЗНАЧЕНИЕ, но в ней обязательно нужно указывать пустую ссылку на документ проверяемого типа:
ГДЕ
ТИПЗНАЧЕНИЯ(ТоварыВРознице.Регистратор) = ЗНАЧЕНИЕ(Документ.ПеремещениеТоваров.ПустаяСсылка)
Важно: Обратите внимание на синтаксис. Распространенной ошибкой новичков является написание вида ТИПЗНАЧЕНИЯ(Регистратор) = ЗНАЧЕНИЕ(Документ.ПеремещениеТоваров). Платформа вернет синтаксическую ошибку, так как в функции ЗНАЧЕНИЕ() для документов необходимо указывать конкретный предопределенный элемент или пустую ссылку типа (например, ПустаяСсылка).
Несмотря на кажущуюся схожесть, между этими двумя инструментами есть концептуальная разница, которую необходимо понимать для написания качественного кода:
ССЫЛКА может использоваться исключительно в секциях условий запроса (таких как ГДЕ, ПО, а также внутри логических веток конструкции ВЫБОР КОГДА ... ТОГДА). Функция ТИПЗНАЧЕНИЯ более универсальна — ее результат можно выводить непосредственно в список выбора секции ВЫБРАТЬ для последующей группировки или передачи в компоновку данных.ССЫЛКА предназначен только для проверки ссылочных типов 1С. Функция ТИПЗНАЧЕНИЯ способна работать в том числе с простыми типами данных (строка, число, дата, булево).ССЫЛКА визуально выглядит более чистой и понятной, что облегчает последующую поддержку кода другими разработчиками.В реальной практике часто возникает ситуация, когда пользователю на форме предоставляется возможность выбрать несколько произвольных типов документов-регистраторов, и система должна вывести движения только по ним. Как реализовать это оптимально?
Устаревший подход (сборка запроса склейкой строк):
Многие программисты собирают текст запроса динамически в цикле, формируя длинную строку условий вида ГДЕ Регистратор ССЫЛКА Документ.А ИЛИ Регистратор ССЫЛКА Документ.Б.... Этот метод плох тем, что СУБД при каждом изменении состава документов вынуждена заново компилировать план SQL-запроса, что снижает общую производительность системы и забивает кэш планов СУБД.
Современный оптимизированный подход (через временную таблицу типов):
Мы можем передать список интересующих нас типов документов через параметр запроса в виде таблицы значений, содержащей пустые ссылки выбранных документов. Запрос при этом остается абсолютно статичным.
Проанализируем пример реализации данного алгоритма:
// 1. Создаем таблицу значений для передачи типов документов
ТаблицаВыбранныхТипов = Новый ТаблицаЗначений;
ТаблицаВыбранныхТипов.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("ДокументСсылка.ПеремещениеТоваров, ДокументСсылка.РеализацияТоваровУслуг"));
// Наполняем таблицу пустыми ссылками нужных типов документов
НоваяСтрока = ТаблицаВыбранныхТипов.Добавить();
НоваяСтрока.Ссылка = Документы.ПеремещениеТоваров.ПустаяСсылка();
НоваяСтрока = ТаблицаВыбранныхТипов.Добавить();
НоваяСтрока.Ссылка = Документы.РеализацияТоваровУслуг.ПустаяСсылка();
// 2. Инициализируем запрос со статическим текстом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТИПЗНАЧЕНИЯ(ВТ_ПараметрыТипов.Ссылка) КАК ТипДокумента
|ПОМЕСТИТЬ ВТ_ВыбранныеТипыРегистраторов
|ИЗ
| &ТаблицаВыбранныхТипов КАК ВТ_ПараметрыТипов
|;
|
|////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТоварыВРознице.Период КАК Период,
| ТоварыВРознице.Регистратор КАК Регистратор,
| ТоварыВРознице.Количество КАК Количество
|ИЗ
| РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
|ГДЕ
| ТИПЗНАЧЕНИЯ(ТоварыВРознице.Регистратор) В (ВЫБРАТЬ ТипДокумента ИЗ ВТ_ВыбранныеТипыРегистраторов)";
Запрос.УстановитьПараметр("ТаблицаВыбранныхТипов", ТаблицаВыбранныхТипов);
РезультатВыполнения = Запрос.Выполнить();
При таком подходе СУБД построит эффективный план выполнения один раз, а фильтрация по типам будет происходить на уровне быстрого соединения в операторе В с предварительно проиндексированной временной таблицей типов.
Разберем классическую ошибку, которая способна замедлить работу крупной информационной базы в сотни раз. Попытка получить свойства регистратора напрямую через разыменование поля составного типа (например, ТоварыВРознице.Регистратор.Дата или ТоварыВРознице.Регистратор.Организация) приводит к тяжелым последствиям на уровне СУБД.
Что происходит «под капотом» СУБД: Поскольку поле Регистратор может ссылаться на десятки различных видов документов, платформа 1С при попытке неявного обращения к реквизиту «через точку» генерирует SQL-запрос, содержащий левые соединения (LEFT OUTER JOIN) со всеми таблицами документов, которые назначены регистраторами для данного регистра. В итоге получается огромный SQL-код с десятками соединений, выполнение которого парализует работу сервера СУБД.
Как правильно решать эту задачу: Если вам действительно необходимо получить реквизит конкретного регистратора, обязательно используйте оператор явного приведения типов ВЫРАЗИТЬ. Он указывает платформе, к какой конкретно таблице нужно выполнить соединение:
ВЫБРАТЬ
ТоварыВРознице.Регистратор КАК Регистратор,
// Явное приведение типов исключает лишние соединения СУБД:
ВЫРАЗИТЬ(ТоварыВРознице.Регистратор КАК Документ.ПеремещениеТоваров).Дата КАК ДатаПеремещения,
ВЫРАЗИТЬ(ТоварыВРознице.Регистратор КАК Документ.ПеремещениеТоваров).СкладОтправитель КАК СкладОтправитель
ИЗ
РегистрНакопления.ТоварыВРознице КАК ТоварыВРознице
ГДЕ
ТоварыВРознице.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
Благодаря использованию функции ВЫРАЗИТЬ, СУБД выполнит ровно одно левое соединение только с физической таблицей документа ПеремещениеТоваров, что гарантирует мгновенное выполнение вашего запроса.
Часто программисты пытаются применить фильтрацию по типу регистратора к виртуальным таблицам остатков и оборотов регистров накопления. Важно помнить следующее правило оптимизации:
Фильтрация в секции ГДЕ после выбора данных из виртуальной таблицы (например, РегистрНакопления.ТоварыВРознице.Обороты(...) КАК Обороты ГДЕ ТИПЗНАЧЕНИЯ(Обороты.Регистратор) = ...) заставит СУБД сначала рассчитать итоги по абсолютно всем документам за указанный период, а затем отсечь ненужное. Это крайне неэффективно.
Рекомендация: Если вам необходима выборка из виртуальной таблицы оборотов с детализацией по конкретному виду регистратора, лучшим решением по скорости работы будет сначала выбрать сами документы нужного типа во временную таблицу за требуемый период, а затем соединить полученный результат с физической таблицей движений регистра (метод «в лоб»), избегая ресурсоемких расчетов виртуальных таблиц СУБД по лишним регистраторам.