При работе с регистрами накопления в 1С разработчики часто сталкиваются с необходимостью выбрать данные по конкретным видам документов-регистраторов. Поскольку поле Регистратор имеет составной тип данных, стандартные запросы «через точку» могут приводить к серьезному падению производительности СУБД. В этой статье мы подробно разберем, как решить задачу выбора данных по документам «Реализация товаров и услуг» и «Возврат товаров от покупателя», а также проанализируем, как СУБД обрабатывает различные конструкции языка запросов 1С.
Нам необходимо получить данные из регистра накопления Продажи за определенный период. В выборку должны попасть только два типа документов-регистраторов:
ОтгрузкаБезПерехода должен быть равен Ложь).Рассмотрим несколько вариантов решения этой задачи, начиная от простых и заканчивая максимально оптимизированными с точки зрения производительности СУБД.
---
Если объем данных в вашей базе невелик, самым быстрым в реализации будет использование операторов ИЛИ и ССЫЛКА в секции ГДЕ. Мы проверяем тип регистратора напрямую:
ВЫБРАТЬ
Продажи.Период КАК Период,
Продажи.Регистратор КАК Регистратор,
Продажи.ЗаказПокупателя КАК ЗаказПокупателя,
Продажи.ДоговорКонтрагента КАК ДоговорКонтрагента,
Продажи.Проект КАК Проект,
Продажи.Контрагент КАК Контрагент,
"ИНН_" + Продажи.Контрагент.ИНН КАК Контрагент_ИНН,
Продажи.Стоимость КАК Стоимость
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Период МЕЖДУ &ДатаНач И &ДатаКон
И (
(Продажи.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
И ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.РеализацияТоваровУслуг).ОтгрузкаБезПерехода = &ОтгрузкаБезПерехода_ОТБОР)
ИЛИ
Продажи.Регистратор ССЫЛКА Документ.ВозвратТоваровОтПокупателя
)
Почему это решение может работать медленно?
Использование логического оператора ИЛИ в секции ГДЕ — известная проблема для оптимизатора СУБД. Когда СУБД видит конструкцию ИЛИ, связывающую разные типы условий, она часто отказывается от использования индексов (Index Seek) и переходит к полному сканированию таблицы или индекса (Index Scan / Table Scan). На больших объемах данных такой запрос начнет вызывать серьезные задержки и блокировки.
---
Для высоконагруженных систем стандарты разработки 1С настоятельно рекомендуют избегать ИЛИ при работе с альтернативными условиями по типам. Вместо этого запрос следует разбивать на отдельные ветки, соединяя их с помощью оператора ОБЪЕДИНИТЬ ВСЕ. В этом случае СУБД сможет построить оптимальный план выполнения для каждой ветки индивидуально.
Также вместо реальной таблицы регистра накопления гораздо эффективнее использовать виртуальную таблицу Обороты, так как она содержит предварительно рассчитанные итоги и обрабатывается платформой быстрее.
Рассмотрим шаг за шагом, как будет выглядеть такой запрос:
ВЫБРАТЬ
ПродажиОбороты.Период КАК Период,
ВЫРАЗИТЬ(ПродажиОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг) КАК Регистратор,
ПродажиОбороты.ЗаказПокупателя КАК ЗаказПокупателя,
ПродажиОбороты.ДоговорКонтрагента КАК ДоговорКонтрагента,
ПродажиОбороты.Проект КАК Проект,
ПродажиОбороты.Контрагент КАК Контрагент,
"ИНН_" + ПродажиОбороты.Контрагент.ИНН КАК Контрагент_ИНН,
ПродажиОбороты.СтоимостьОборот КАК Стоимость
ИЗ
РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК ПродажиОбороты
ГДЕ
ПродажиОбороты.Регистратор ССЫЛКА Документ.RealizationТоваровУслуг
И ВЫРАЗИТЬ(ПродажиОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг).ОтгрузкаБезПерехода = &ОтгрузкаБезПерехода_ОТБОР
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ПродажиОбороты.Период,
ВЫРАЗИТЬ(ПродажиОбороты.Регистратор КАК Документ.ВозвратТоваровОтПокупателя),
ПродажиОбороты.ЗаказПокупателя,
ПродажиОбороты.ДоговорКонтрагента,
ПродажиОбороты.Проект,
ПродажиОбороты.Контрагент,
"ИНН_" + ПродажиОбороты.Контрагент.ИНН,
ПродажиОбороты.СтоимостьОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК ПродажиОбороты
ГДЕ
ПродажиОбороты.Регистратор ССЫЛКА Документ.ВозвратТоваровОтПокупателя
---
Давайте разберемся, почему мы написали запрос именно так и в чем разница между популярными конструкциями платформы.
В базе данных SQL поле составного типа (такое как Регистратор) физически хранится в виде нескольких колонок. Для ссылочных типов это обычно:
_RecorderTRef — идентификатор типа таблицы (какому документу принадлежит ссылка)._RecorderRRef — уникальный идентификатор конкретной записи (GUID).Когда мы пишем условие ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг, платформа 1С транслирует это в SQL как простейшую фильтрацию по колонке типа:
WHERE T1._RecorderTRef = 0x000002D8 (где шестнадцатеричный код — это внутренний ID таблицы документа).
Это максимально быстрая операция, при которой СУБД мгновенно выполняет поиск по индексу (Index Seek).
Конструкция ТИПЗНАЧЕНИЯ(Регистратор) = ТИП(Документ.РеализацияТоваровУслуг) транслируется практически аналогично. Однако ССЫЛКА считается более лаконичным и стандартным оператором для проверки конкретного типа в условиях запросов.
Иногда разработчики используют конструкцию ГДЕ ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг) ЕСТЬ НЕ NULL для фильтрации типов. Делать так крайне не рекомендуется:
ВЫРАЗИТЬ СУБД вынуждена использовать тяжелую условную конструкцию CASE WHEN на уровне SQL.NULL заставляет СУБД вычислять это выражение для каждой строки таблицы. В итоге СУБД отказывается от быстрого поиска по индексу и переходит к полному сканированию таблицы, что убивает производительность на больших базах данных.Зачем же тогда вообще нужен оператор ВЫРАЗИТЬ? Его главная задача — оптимизировать обращение к реквизитам через точку (разыменование).
Представим, что в базе настроено 50 видов документов, которые могут выступать регистраторами для регистра Продажи. Если вы напишете в запросе напрямую: Регистратор.ОтгрузкаБезПерехода, платформа 1С будет вынуждена сгенерировать SQL-запрос, содержащий левые соединения (LEFT OUTER JOIN) со всеми 50 таблицами документов!
Если же мы принудительно ограничиваем тип: ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).ОтгрузкаБезПерехода, платформа четко понимает, какой тип документа нас интересует. В итоговом SQL-запросе останется только одно левое соединение с таблицей реализации, а остальные 49 соединений будут отброшены. Это колоссально снижает нагрузку на оптимизатор СУБД.
---
Чтобы ваши запросы всегда работали быстро, придерживайтесь следующих правил:
ГДЕ всегда используйте оператор ССЫЛКА. Это работает напрямую с метаданными СУБД без вычисления функций.ВЫРАЗИТЬ(Поле КАК Тип).Реквизит. Это избавляет СУБД от десятков лишних соединений таблиц.ИЛИ. Разделяйте запрос на независимые части и объединяйте их через ОБЪЕДИНИТЬ ВСЕ.