Как правильно составить запрос к регистру накопления по нескольким типам документов-регистраторов и оптимизировать его

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

При работе с регистрами накопления в 1С разработчики часто сталкиваются с необходимостью выбрать данные по конкретным видам документов-регистраторов. Поскольку поле Регистратор имеет составной тип данных, стандартные запросы «через точку» могут приводить к серьезному падению производительности СУБД. В этой статье мы подробно разберем, как решить задачу выбора данных по документам «Реализация товаров и услуг» и «Возврат товаров от покупателя», а также проанализируем, как СУБД обрабатывает различные конструкции языка запросов 1С.

Постановка задачи

Нам необходимо получить данные из регистра накопления Продажи за определенный период. В выборку должны попасть только два типа документов-регистраторов:

  1. Реализация товаров и услуг — с дополнительным условием отбора (реквизит документа ОтгрузкаБезПерехода должен быть равен Ложь).
  2. Возврат товаров от покупателя — без дополнительных условий.

Рассмотрим несколько вариантов решения этой задачи, начиная от простых и заканчивая максимально оптимизированными с точки зрения производительности СУБД.

---

Решение 1. Простое решение с использованием оператора ИЛИ (для небольших баз данных)

Если объем данных в вашей базе невелик, самым быстрым в реализации будет использование операторов ИЛИ и ССЫЛКА в секции ГДЕ. Мы проверяем тип регистратора напрямую:


ВЫБРАТЬ
    Продажи.Период КАК Период,
    Продажи.Регистратор КАК Регистратор,
    Продажи.ЗаказПокупателя КАК ЗаказПокупателя,
    Продажи.ДоговорКонтрагента КАК ДоговорКонтрагента,
    Продажи.Проект КАК Проект,
    Продажи.Контрагент КАК Контрагент,
    "ИНН_" + Продажи.Контрагент.ИНН КАК Контрагент_ИНН,
    Продажи.Стоимость КАК Стоимость
ИЗ
    РегистрНакопления.Продажи КАК Продажи
ГДЕ
    Продажи.Период МЕЖДУ &ДатаНач И &ДатаКон
    И (
        (Продажи.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг 
         И ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.РеализацияТоваровУслуг).ОтгрузкаБезПерехода = &ОтгрузкаБезПерехода_ОТБОР)
        ИЛИ 
        Продажи.Регистратор ССЫЛКА Документ.ВозвратТоваровОтПокупателя
    )

Почему это решение может работать медленно?

Использование логического оператора ИЛИ в секции ГДЕ — известная проблема для оптимизатора СУБД. Когда СУБД видит конструкцию ИЛИ, связывающую разные типы условий, она часто отказывается от использования индексов (Index Seek) и переходит к полному сканированию таблицы или индекса (Index Scan / Table Scan). На больших объемах данных такой запрос начнет вызывать серьезные задержки и блокировки.

---

Решение 2. Оптимизированное решение с использованием ОБЪЕДИНИТЬ ВСЕ

Для высоконагруженных систем стандарты разработки 1С настоятельно рекомендуют избегать ИЛИ при работе с альтернативными условиями по типам. Вместо этого запрос следует разбивать на отдельные ветки, соединяя их с помощью оператора ОБЪЕДИНИТЬ ВСЕ. В этом случае СУБД сможет построить оптимальный план выполнения для каждой ветки индивидуально.

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

Рассмотрим шаг за шагом, как будет выглядеть такой запрос:


ВЫБРАТЬ
    ПродажиОбороты.Период КАК Период,
    ВЫРАЗИТЬ(ПродажиОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг) КАК Регистратор,
    ПродажиОбороты.ЗаказПокупателя КАК ЗаказПокупателя,
    ПродажиОбороты.ДоговорКонтрагента КАК ДоговорКонтрагента,
    ПродажиОбороты.Проект КАК Проект,
    ПродажиОбороты.Контрагент КАК Контрагент,
    "ИНН_" + ПродажиОбороты.Контрагент.ИНН КАК Контрагент_ИНН,
    ПродажиОбороты.СтоимостьОборот КАК Стоимость
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК ПродажиОбороты
ГДЕ
    ПродажиОбороты.Регистратор ССЫЛКА Документ.RealizationТоваровУслуг
    И ВЫРАЗИТЬ(ПродажиОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг).ОтгрузкаБезПерехода = &ОтгрузкаБезПерехода_ОТБОР

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ПродажиОбороты.Период,
    ВЫРАЗИТЬ(ПродажиОбороты.Регистратор КАК Документ.ВозвратТоваровОтПокупателя),
    ПродажиОбороты.ЗаказПокупателя,
    ПродажиОбороты.ДоговорКонтрагента,
    ПродажиОбороты.Проект,
    ПродажиОбороты.Контрагент,
    "ИНН_" + ПродажиОбороты.Контрагент.ИНН,
    ПродажиОбороты.СтоимостьОборот
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК ПродажиОбороты
ГДЕ
    ПродажиОбороты.Регистратор ССЫЛКА Документ.ВозвратТоваровОтПокупателя

---

Глубокий анализ: как СУБД работает с ССЫЛКА, ТИПЗНАЧЕНИЯ и ВЫРАЗИТЬ

Давайте разберемся, почему мы написали запрос именно так и в чем разница между популярными конструкциями платформы.

1. Хранение составных типов данных на уровне SQL

В базе данных SQL поле составного типа (такое как Регистратор) физически хранится в виде нескольких колонок. Для ссылочных типов это обычно:

2. Операторы ССЫЛКА и ТИПЗНАЧЕНИЯ

Когда мы пишем условие ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг, платформа 1С транслирует это в SQL как простейшую фильтрацию по колонке типа:

WHERE T1._RecorderTRef = 0x000002D8 (где шестнадцатеричный код — это внутренний ID таблицы документа).

Это максимально быстрая операция, при которой СУБД мгновенно выполняет поиск по индексу (Index Seek).

Конструкция ТИПЗНАЧЕНИЯ(Регистратор) = ТИП(Документ.РеализацияТоваровУслуг) транслируется практически аналогично. Однако ССЫЛКА считается более лаконичным и стандартным оператором для проверки конкретного типа в условиях запросов.

3. Конструкция ВЫРАЗИТЬ... ЕСТЬ НЕ NULL — почему это антипаттерн в секции ГДЕ?

Иногда разработчики используют конструкцию ГДЕ ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг) ЕСТЬ НЕ NULL для фильтрации типов. Делать так крайне не рекомендуется:

4. Разыменование полей составного типа «через точку»

Зачем же тогда вообще нужен оператор ВЫРАЗИТЬ? Его главная задача — оптимизировать обращение к реквизитам через точку (разыменование).

Представим, что в базе настроено 50 видов документов, которые могут выступать регистраторами для регистра Продажи. Если вы напишете в запросе напрямую: Регистратор.ОтгрузкаБезПерехода, платформа 1С будет вынуждена сгенерировать SQL-запрос, содержащий левые соединения (LEFT OUTER JOIN) со всеми 50 таблицами документов!

Если же мы принудительно ограничиваем тип: ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).ОтгрузкаБезПерехода, платформа четко понимает, какой тип документа нас интересует. В итоговом SQL-запросе останется только одно левое соединение с таблицей реализации, а остальные 49 соединений будут отброшены. Это колоссально снижает нагрузку на оптимизатор СУБД.

---

Резюме стандартов разработки 1С (Стандарт #std654)

Чтобы ваши запросы всегда работали быстро, придерживайтесь следующих правил:

  1. Для фильтрации по типу в секции ГДЕ всегда используйте оператор ССЫЛКА. Это работает напрямую с метаданными СУБД без вычисления функций.
  2. Для получения реквизитов составного поля всегда используйте конструкцию ВЫРАЗИТЬ(Поле КАК Тип).Реквизит. Это избавляет СУБД от десятков лишних соединений таблиц.
  3. Если нужно выбрать данные по нескольким типам документов, избегайте оператора ИЛИ. Разделяйте запрос на независимые части и объединяйте их через ОБЪЕДИНИТЬ ВСЕ.
  4. Для максимального быстродействия рассмотрите возможность денормализации данных. Если вам слишком часто требуются реквизиты регистратора (например, договор или проект), добавьте их в качестве измерений или реквизитов непосредственно в сам регистр накопления при проведении документа. Тогда в запросах вам вообще не придется обращаться к таблицам документов.
← На главную