Параметры виртуальной таблицы vs ГДЕ

Одно и то же условие может сделать запрос молниеносным или мучительно медленным — всё решает, куда его поставить: в параметры виртуальной таблицы или в ГДЕ.

Правило: условие в параметрах виртуальной таблицы применяется до расчёта итогов — платформа поднимает из базы только нужные строки. То же условие в ГДЕ применяется после — итоги считаются по всем данным, а лишнее отбрасывается в конце.

Это, пожалуй, самая частая ошибка производительности у новичков в запросах 1С. Запрос даёт правильный результат в обоих случаях, поэтому на маленькой базе разницы не видно. Но когда в регистре сотни тысяч записей, вариант с условием в ГДЕ начинает «висеть», а точно такой же запрос с условием в параметрах отрабатывает мгновенно. Разберём, почему так, на живом сравнении.

Зачем это нужно на практике

Отчёты и обработки в 1С почти всегда строятся на виртуальных таблицах Остатков и Оборотов по большим регистрам. Неправильно поставленный отбор — самая распространённая причина того, что «отчёт по одному складу за один день считается минуту». Понимание этого правила отличает джуна, у которого отчёты тормозят, от разработчика, у которого они летают, — и его почти всегда спрашивают на собеседовании.

Медленно: условие в ГДЕ

Пусть нужен остаток товаров по одному складу. Наивный вариант — взять виртуальную таблицу Остатки по всем складам, а нужный склад отобрать в ГДЕ:

// МЕДЛЕННО — отбор по складу в ГДЕ
ВЫБРАТЬ
    ОстаткиТоваров.Номенклатура КАК Номенклатура,
    ОстаткиТоваров.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, ) КАК ОстаткиТоваров
ГДЕ
    ОстаткиТоваров.Склад = &Склад

Что делает платформа: сначала виртуальная таблица Остатки(&НаДату, ) с пустым отбором считает остатки по всем складам и всей номенклатуре — это тяжёлый расчёт по итогам всего регистра. И только потом ГДЕ ОстаткиТоваров.Склад = &Склад выбрасывает 95% посчитанного. Мы заставили СУБД сделать огромную работу, чтобы почти всё выкинуть.

Быстро: условие в параметрах виртуальной таблицы

Правильный вариант — передать отбор внутрь виртуальной таблицы, во второй параметр:

// БЫСТРО — отбор по складу в параметрах виртуальной таблицы
ВЫБРАТЬ
    ОстаткиТоваров.Номенклатура КАК Номенклатура,
    ОстаткиТоваров.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &НаДату,
        Склад = &Склад) КАК ОстаткиТоваров

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

Мнемоника: отбор по измерениям — в параметры виртуальной таблицы (Склад, Организация, Номенклатура, ТипЦен). А вот условие по рассчитанному ресурсу (КоличествоОстаток > 0) поставить в параметры нельзя — ресурс ещё не посчитан на входе, поэтому такое условие законно живёт в ГДЕ.

Полный пример из кода

Так выглядит грамотный запрос остатков по складу с отбросом нулей. Отбор по измерению (Склад) — в параметрах, условие по ресурсу (остаток больше нуля) — в ГДЕ:

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

Запрос.УстановитьПараметр("НаДату", КонецДня(ТекущаяДата()));
Запрос.УстановитьПараметр("Склад",  ОсновнойСклад);

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Номенклатура.Наименование + ": " + Выборка.Количество);
КонецЦикла;

Результат:

Мышь Gamma: 220
Смартфон Alpha: 14
Ноутбук Beta: 3

То же правило для Оборотов и СрезПоследних

Принцип общий для всех виртуальных таблиц. Отбор по номенклатуре в Оборотах тоже кладут в параметры:

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

У Оборотов отбор — четвёртый параметр (после начала, конца и периодичности), у Остатков и СрезПоследних — второй. Оператор В (&СписокТоваров) отлично работает и в параметрах виртуальной таблицы — можно ограничить расчёт заранее заданным списком.

Как это работает

Чтобы прочувствовать разницу, полезно представить порядок, в котором платформа собирает запрос. Виртуальная таблица — это не готовая таблица, а инструкция «посчитай итоги вот так». Условие в её параметрах платформа вклеивает внутрь этой инструкции: сгенерированный SQL получает ограничение ещё до агрегации, СУБД идёт по индексу измерения и складывает только отобранные движения. Это то, что называют «ранняя фильтрация» (predicate pushdown) — отбор проталкивается как можно ближе к источнику данных.

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

Частые ошибки

  • «Работает же — и ладно». На тестовой базе из сотни записей оба варианта мгновенны, разница прячется. Проблема вылезает в бою на реальном объёме. Привыкайте писать правильно сразу, а не «когда затормозит».
  • Пытаться отобрать ресурс в параметрах. Остатки(&НаДату, КоличествоОстаток > 0) — так нельзя: на входе виртуальной таблицы ресурс ещё не посчитан. Отбор по измерению — в параметры, условие по ресурсу — в ГДЕ.
  • Соединять с большой виртуальной таблицей без отбора. Если соединить справочник с Остатки(&НаДату, ) без ограничения, платформа посчитает остатки по всему регистру ради нескольких строк соединения. Ограничивайте виртуальную таблицу в параметрах ещё до соединения.
  • Дублировать отбор и там, и там. Ставить одно и то же условие сразу в параметры и в ГДЕ не нужно — достаточно параметров. ГДЕ оставляйте только для того, что нельзя выразить в параметрах (условия по ресурсам, сложная логика по вычисляемым полям).
  • Путать порядок параметров. У Оборотов отбор идёт четвёртым аргументом; если по ошибке передать его третьим, платформа воспримет его как периодичность и выдаст ошибку или неверный результат. Сверяйтесь с конструктором запроса.

Итоги

  • Отбор по измерениям кладите в параметры виртуальной таблицы — он применяется до расчёта итогов и работает быстро.
  • То же условие в ГДЕ считает итоги по всем данным и отбрасывает лишнее в конце — на больших регистрах это медленно.
  • Условие по рассчитанному ресурсу (КоличествоОстаток > 0) в параметры поставить нельзя — оно законно живёт в ГДЕ.
  • Правило общее для Остатков, Оборотов, ОстаткиИОбороты и СрезПоследних; отличается лишь номер параметра-отбора.
  • Результат одинаков в обоих случаях — разница только в скорости, и она огромна на реальных объёмах.
Проверьте себя
1. Почему условие «Склад = &Склад» в параметрах виртуальной таблицы Остатки работает быстрее, чем то же условие в ГДЕ?
AВ параметрах условие проверяется дважды и потому надёжнее
BОтбор в параметрах применяется до расчёта итогов — платформа поднимает из базы только нужные строки, а в ГДЕ фильтрация идёт после расчёта по всем данным
CВ ГДЕ условия по измерениям вообще запрещены
DРазницы в скорости нет, оба варианта одинаковы
2. Какое из условий НЕЛЬЗЯ поставить в параметры виртуальной таблицы Остатки и оно должно быть в ГДЕ?
AСклад = &Склад
BОрганизация = &Организация
CКоличествоОстаток > 0
DНоменклатура В (&СписокТоваров)