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

«Вот запрос к остаткам. Как ускорить его, не меняя результат?» — и половина кандидатов проваливается на этом вопросе.

Виртуальная таблица — это не реальная таблица в базе, а «представление», которое платформа 1С разворачивает в сложный SQL-запрос по вашим параметрам в момент выполнения. Условие, переданное в её параметры, попадает внутрь этого разворота; то же условие в ГДЕ применяется уже к готовому результату.

Это один из любимых вопросов на производительность на собеседовании 1С-разработчика. Спрашивают его потому, что ответ мгновенно показывает, понимает ли человек, что запрос 1С — это не то же самое, что таблица в базе, и что за красивым языком запросов скрывается реальный SQL, который платформа генерирует за вас.

Вопрос-крючок

Дают два запроса, которые возвращают одинаковый результат — остатки товаров на складе «Основной» — и просят сказать, какой быстрее и почему. Вот «медленный» вариант, где условие вынесено в ГДЕ:

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

А вот «быстрый» — то же условие, но переданное в параметры виртуальной таблицы:

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

Второй параметр метода Остатки() — это условие отбора (первый параметр — момент времени, поэтому перед запятой пусто). Оба запроса вернут одно и то же. Но работают они принципиально по-разному.

В чём разница на самом деле

Ключ к ответу — понять, когда применяется отбор.

Условие в ГДЕ: сначала посчитать всё, потом отсеять

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

Условие в параметрах: считать сразу только нужное

Когда тот же отбор передан в параметры, платформа встраивает его внутрь генерируемого SQL — в подзапросы к таблицам движений и итогов. Она считает остатки сразу только по складу «Основной». Меньше строк читается с диска, меньше данных сворачивается, меньше промежуточный результат. Особенно заметно на регистрах с миллионами движений и десятками складов: разница может быть в десятки раз.

Как отвечать

Хороший ответ звучит так: «Условие в параметрах виртуальной таблицы платформа встраивает внутрь SQL-запроса, который она генерирует для расчёта остатков, поэтому лишние данные вообще не читаются и не сворачиваются. Условие в ГДЕ применяется уже к готовому результату — платформа сперва рассчитывает остатки по всем складам, а потом отсеивает ненужное. Результат одинаковый, но во втором случае мы заставляем СУБД делать лишнюю работу. Поэтому всё, что можно, я выношу в параметры виртуальной таблицы.»

Если хотят копнуть глубже, добавьте, что посмотреть реальный SQL можно в технологическом журнале или через план запроса в консоли запросов — там видно, как отбор из параметров превращается в условие внутри вложенного SELECT, а не в наружный WHERE поверх всего.

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

Ещё пример — регистр сведений

Тот же принцип работает и для СрезПоследних() у периодического регистра сведений. Хотим цены только по одному виду цен на дату:

ВЫБРАТЬ
    Цены.Номенклатура,
    Цены.Цена
ИЗ
    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ВидЦены = &ВидЦены) КАК Цены

Первый параметр СрезПоследних() — дата среза, второй — условие отбора. Отбор по ВидЦены внутри параметров означает, что срез последних значений платформа строит сразу только по нужному виду цен, а не по всем видам с последующим отсевом.

Частые ошибки на собеседовании

Чего не стоит говорить (и делать в реальном коде):

  • «Разницы нет, результат-то одинаковый». Результат одинаковый, а нагрузка на СУБД — нет. Это и есть суть вопроса.
  • Путать параметры и ГДЕ местами. Помните: параметры — это то, что в скобках после имени виртуальной таблицы; они сужают выборку до расчёта.
  • Сначала соединить остатки с чем-то, а потом фильтровать по складу. Тогда до фильтра платформа посчитает остатки по всем складам и ещё и соединит их — двойная лишняя работа.
  • Игнорировать индексы и типы полей. Отбор в параметрах ускоряет, но если в него передать не измерение, эффекта не будет — условие всё равно применится снаружи.

Итоги-шпаргалка

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