Рассмотрим одну из самых распространенных ошибок начинающих и даже опытных разработчиков на платформе 1С:Предприятие — неправильное наложение условий в запросах к виртуальным таблицам. Часто мы сталкиваемся с ситуацией, когда отчет, который прекрасно работал на тестовой базе с небольшим количеством данных, начинает критически "тормозить" на рабочей базе спустя полгода эксплуатации. Проанализируем причины этого явления и разберем методы решения проблемы.
Когда мы пишем запрос к регистрам (накопления, бухгалтерии, сведений), мы часто используем виртуальные таблицы, такие как Обороты, Остатки, СрезПоследних. Интуитивно разработчик может написать условие отбора привычным способом — через конструкцию ГДЕ. Однако в контексте виртуальных таблиц это может стать фатальной ошибкой для производительности.
Давайте разберем ситуацию, когда нам необходимо получить продажи только по одной конкретной номенклатуре. Рассмотрим пример кода, который вызывает проблемы с быстродействием:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПродажиОбороты.Контрагент КАК Контрагент,
| ПродажиОбороты.СуммаОборот КАК Сумма
|ИЗ
| РегистрНакопления.Продажи.Обороты(&НачПериода, &КонПериода, , ) КАК ПродажиОбороты
|ГДЕ
| ПродажиОбороты.Номенклатура = &Номенклатура";
На первый взгляд запрос кажется корректным. Мы выбираем данные и фильтруем их по номенклатуре. Однако давайте выясним, как этот запрос транслируется в SQL и как его выполняет СУБД. При использовании секции ГДЕ платформа 1С сначала получает полную таблицу оборотов за указанный период (фактически рассчитывает обороты по всем товарам базы данных), и только после этого, на полученном огромном массиве данных, применяет фильтр.
Если в вашей базе миллионы записей о продажах, система сначала обработает их все, потратив ресурсы процессора и оперативной памяти, а затем отбросит 99.9% результата, оставив только нужную номенклатуру.
Чтобы избежать сканирования всей таблицы, необходимо накладывать условия отбора на этапе формирования самой виртуальной таблицы. Для этого в языке запросов 1С предусмотрены специальные параметры внутри скобок виртуальной таблицы.
Посмотрим, как должен выглядеть оптимизированный запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПродажиОбороты.Контрагент КАК Контрагент,
| ПродажиОбороты.СуммаОборот КАК Сумма
|ИЗ
| РегистрНакопления.Продажи.Обороты(
| &НачПериода,
| &КонПериода,
| ,
| Номенклатура = &Номенклатура
| ) КАК ПродажиОбороты";
Обратите внимание на различия. Мы убрали секцию ГДЕ и переместили условие Номенклатура = &Номенклатура внутрь скобок функции Обороты, в четвертый параметр (параметр "Условие").
Давайте систематизируем знания о параметрах виртуальной таблицы Обороты, так как порядок параметров критически важен. Если мы перепутаем места, запрос либо не выполнится, либо вернет неверные данные.
Период.Месяц), если нам нужна детализация по времени.При использовании таблицы Остатки порядок параметров отличается:
Важное правило: Всегда старайтесь накладывать отборы на измерения регистра (Номенклатура, Склад, Организация) именно внутри параметров виртуальной таблицы. Это позволяет SQL-серверу использовать индексы и читать только необходимые страницы данных с диска.
Может возникнуть вопрос: нужно ли теперь вообще отказаться от ГДЕ? Нет, секция ГДЕ по-прежнему необходима, но для других задач.
Разделим зоны ответственности:
Склад, Номенклатура, Контрагент.ГДЕ применяется, когда мы работаем с реальными (физическими) таблицами, например Справочник.Номенклатура.Рассмотрим пример комбинированного использования. Допустим, нам нужны продажи по конкретной номенклатуре, но только те, где сумма оборота превысила 1000 рублей.
Запрос.Текст =
"ВЫБРАТЬ
| ПродажиОбороты.Контрагент,
| ПродажиОбороты.СуммаОборот
|ИЗ
| РегистрНакопления.Продажи.Обороты(
| , , ,
| Номенклатура = &Номенклатура
| ) КАК ПродажиОбороты
|ГДЕ
| ПродажиОбороты.СуммаОборот > 1000";
В данном случае мы фильтруем выборку данных по измерению Номенклатура на этапе построения виртуальной таблицы (максимальная оптимизация), а затем полученный (уже маленький) набор данных дополнительно фильтруем по ресурсу СуммаОборот с помощью ГДЕ, так как наложить условие на ресурс внутри параметров виртуальной таблицы технически невозможно.
Часто возникает необходимость отобрать данные не по одному значению, а по списку. Рассмотрим, как это сделать правильно внутри параметров. Допустим, у нас есть массив или список значений СписокТоваров.
// Правильный подход
Запрос.УстановитьПараметр("СписокТоваров", СписокТоваров);
Запрос.Текст =
"ВЫБРАТЬ ...
|ИЗ РегистрНакопления.Продажи.Обороты(,,, Номенклатура В (&СписокТоваров)) ...";
Конструкция В (&Параметр) отлично работает внутри параметров виртуальных таблиц и корректно оптимизируется платформой.
Еще один важный аспект, который мы должны рассмотреть — это работа виртуальных таблиц в левых соединениях. Предположим, мы хотим получить список всех товаров из справочника и при наличии продаж — показать их сумму. Если продаж не было, вывести NULL.
Рассмотрим ошибочный вариант запроса, который часто встречается на практике:
// ОШИБОЧНЫЙ ВАРИАНТ
"ВЫБРАТЬ
| Товары.Ссылка,
| ЕСТЬNULL(Продажи.СуммаОборот, 0)
|ИЗ
| Справочник.Номенклатура КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты КАК Продажи
| ПО Товары.Ссылка = Продажи.Номенклатура
|ГДЕ
| Продажи.Склад = &Склад"
Почему этот код плох?
Во-первых, мы не наложили отбор по складу внутри виртуальной таблицы Обороты. Это значит, что система сначала рассчитает обороты по всем складам.
Во-вторых, наложив условие ГДЕ Продажи.Склад = &Склад на правую таблицу левого соединения, мы фактически превращаем ЛЕВОЕ СОЕДИНЕНИЕ во ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Товары, по которым не было продаж на этом складе, будут отброшены, так как для них Продажи.Склад будет NULL, а NULL = &Склад дает ЛОЖЬ.
Посмотрим на правильный вариант:
// ПРАВИЛЬНЫЙ ВАРИАНТ
"ВЫБРАТЬ
| Товары.Ссылка,
| ЕСТЬNULL(Продажи.СуммаОборот, 0)
|ИЗ
| Справочник.Номенклатура КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(,,, Склад = &Склад) КАК Продажи
| ПО Товары.Ссылка = Продажи.Номенклатура"
Здесь мы сделали две вещи:
1. Перенесли условие по складу внутрь параметров таблицы (оптимизация скорости).
2. Убрали секцию ГДЕ, позволив левому соединению отработать корректно и вывести товары даже без продаж.
Подводя итог нашему анализу, сформулируем краткий чек-лист для проверки ваших запросов:
Остатки, Обороты, СрезПоследних), проверьте наличие секции ГДЕ.ГДЕ есть условия на измерения регистра — перенесите их в параметры виртуальной таблицы.ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей, убедитесь, что условия фильтрации правой таблицы находятся внутри её параметров, а не в общем ГДЕ всего запроса.Соблюдение этих простых правил позволит вашим отчетам и обработкам работать быстро даже при значительном росте объема базы данных.