В практике разработки на платформе 1С:Предприятие мы часто сталкиваемся с ситуациями, когда, казалось бы, простой запрос выполняется неоправданно долго. Особенно это касается отчетов или обработок, использующих данные регистров накопления или сведений. В этой статье мы подробно разберем одну из самых распространенных ошибок при написании запросов — неправильное наложение условий отбора при работе с виртуальными таблицами и соединениями.
Рассмотрим ситуацию, когда необходимо получить данные из справочника и присоединить к ним остатки или обороты из регистра. Мы проанализируем, почему перенос условия в секцию ГДЕ может катастрофически снизить производительность, и научимся писать оптимальный код.
Давайте представим типичную задачу: нам нужно выбрать список товаров и их остатки на конкретном складе. Начинающий разработчик часто пишет запрос, логика которого выглядит следующим образом: "Выбери все товары, присоедини к ним таблицу остатков, а затем оставь только те строки, где склад равен искомому".
Посмотрим на пример кода, который содержит критическую ошибку производительности:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
ТоварыНаСкладахОстатки.КоличествоОстаток
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
ПО Номенклатура.Ссылка = ТоварыНаСкладахОстатки.Номенклатура
ГДЕ
ТоварыНаСкладахОстатки.Склад = &Склад
На первый взгляд, запрос выглядит логичным. Мы соединяем справочник с регистром и фильтруем по складу. Однако, давайте разберем, как этот запрос выполняется платформой и СУБД (SQL Server или PostgreSQL).
При использовании конструкции ГДЕ для полей виртуальной таблицы (в данном случае РегистрНакопления.ТоварыНаСкладах.Остатки) после выполнения соединения происходят следующие процессы:
ГДЕ.Это приводит к тому, что сервер базы данных выполняет колоссальный объем лишней работы, читая с диска данные, которые нам не нужны, и занимая оперативную память.
Помимо производительности, использование ГДЕ на поле правой таблицы при ЛЕВОЕ СОЕДИНЕНИЕ меняет саму логику выборки. Взглянем на этот аспект подробнее.
Когда мы пишем:
...
ЛЕВОЕ СОЕДИНЕНИЕ ...
ГДЕ ТоварыНаСкладахОстатки.Склад = &Склад
Мы неявно превращаем ЛЕВОЕ СОЕДИНЕНИЕ во ВНУТРЕННЕЕ СОЕДИНЕНИЕ (Inner Join). Почему это происходит? Если у товара нет остатка на складе, поле ТоварыНаСкладахОстатки.Склад будет содержать значение NULL. Условие NULL = &Склад вернет "Ложь", и такие строки (товары без остатков) будут исключены из выборки.
Если ваша задача — увидеть все товары, даже те, которых нет на остатках, такой запрос вернет неверный результат.
Теперь давайте перепишем запрос правильно. Язык запросов 1С позволяет передавать параметры отбора непосредственно в виртуальную таблицу. Это фундаментальное отличие.
Разберем синтаксис:
РегистрНакопления.ИмяРегистра.Остатки(<Период>, <Условие>)
Когда мы помещаем условие внутрь параметров виртуальной таблицы, мы даем команду системе: "Сначала отфильтруй записи регистра, рассчитай остатки ТОЛЬКО по нужному складу, и только потом передай эту маленькую таблицу для соединения".
Посмотрим на оптимизированный код:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
ТоварыНаСкладахОстатки.КоличествоОстаток
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
ПО Номенклатура.Ссылка = ТоварыНаСкладахОстатки.Номенклатура
Обратите внимание на конструкцию (, Склад = &Склад). Первый параметр (Период) мы пропустили (запятая стоит), так как нам нужны актуальные остатки, а во втором параметре мы указали отбор.
Преимущества такого подхода:
ЛЕВОЕ СОЕДИНЕНИЕ отрабатывает правильно. Мы получим полный список номенклатуры. Там, где есть остаток на выбранном складе, будет число, там, где нет — NULL.Иногда возникают задачи, когда нужно наложить фильтр не на измерения регистра (как Склад), а на ресурсы или реквизиты, или использовать сложные условия "ИЛИ". Рассмотрим, как действовать в этих случаях.
В параметрах виртуальных таблиц остатков (Остатки) можно накладывать условия только на измерения. Если вы попробуете отобрать записи по реквизиту регистра внутри параметров таблицы остатков, система выдаст ошибку.
Однако, для виртуальных таблиц оборотов (Обороты) или среза последних (СрезПоследних), возможности фильтрации шире. Рассмотрим пример с периодическим регистром сведений "ЦеныНоменклатуры".
Предположим, нам нужно получить текущую цену определенного типа. Сравним два подхода.
Неправильно (фильтр в ГДЕ):
ВЫБРАТЬ
Цены.Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Цены
ГДЕ
Цены.ТипЦен = &ТипЦен
В этом случае система сначала строит срез последних по всем типам цен для каждого товара, и только потом отсекает лишнее. Это очень ресурсоемкая операция.
Правильно (фильтр в параметрах):
ВЫБРАТЬ
Цены.Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ТипЦен = &ТипЦен) КАК Цены
Здесь мы сразу получаем срез только по нужному типу цен.
Рассмотрим ситуацию, когда условий несколько. Например, нам нужны остатки по списку складов и определенной группе номенклатуры.
Синтаксис позволяет использовать логические операторы И внутри параметров:
РегистрНакопления.ТоварыНаСкладах.Остатки(
,
Склад В (&СписокСкладов)
И Номенклатура В ИЕРАРХИИ (&ГруппаТоваров)
)
Давайте проанализируем, почему это важно. Если мы не ограничим выборку по номенклатуре внутри виртуальной таблицы, а сделаем это при соединении, 1С может сначала рассчитать остатки по всей базе (по всем товарам на этих складах), а потом отбросить лишнее. Ограничивая выборку на входе, мы экономим ресурсы сервера.
Не будем категоричны: секция ГДЕ необходима, но использовать ее нужно для фильтрации основной таблицы выборки или для условий, которые технически невозможно поместить в параметры виртуальной таблицы.
Примеры, когда ГДЕ уместно:
ГДЕ Номенклатура.Артикул ПОДОБНО "%123%").Подводя итог нашему анализу, сформируем чек-лист для проверки ваших запросов:
Регистр.Имя.ВидТаблицы КАК Алиас без параметров? Если да, убедитесь, что вы не фильтруете поля этой таблицы (особенно измерения) в секции ГДЕ.ЛЕВОЕ СОЕДИНЕНИЕ и затем накладываете условие ГДЕ на правую таблицу, задайте себе вопрос: "Действительно ли я хочу получить Inner Join?". Если нет — переносите условие в параметры виртуальной таблицы или в условие соединения (ПО ... И ...).Соблюдение этих простых правил позволит вам создавать быстрые и надежные решения, которые будут корректно работать даже на очень больших объемах данных. Оптимизация на этапе написания кода — это лучший способ избежать проблем с производительностью в будущем.