Разработчики, имеющие опыт работы с классическими реляционными СУБД (например, MS SQL Server), часто привыкают использовать конструкции управления потоком данных, такие как IF...ELSE, прямо внутри SQL-скриптов. При переходе на платформу 1С:Предприятие многие пытаются применить этот же подход в языке запросов. Однако язык запросов 1С является строго декларативным и не поддерживает процедурные конструкции управления выполнением пакета.
В рамках этой статьи мы подробно разберем, почему стандартные попытки использовать условный выбор для управления структурой пакета запросов приводят к ошибкам, и рассмотрим три надежных способа реализации условной логики (аналога IF) в запросах 1С и Системе компоновки данных (СКД).
Проанализируем типичную ошибку, которую часто совершают начинающие разработчики. Посмотрим на фрагмент кода, где автор пытается управлять созданием временной таблицы:
// ОШИБОЧНЫЙ КОД! ТАК ПИСАТЬ НЕЛЬЗЯ!
ВЫБОР КОГДА &Распределить
ТОГДА
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ТабРаспределения.Характер КАК Характер,
...
ПОМЕСТИТЬ ВТ_ФинРез
ИЗ ...
ИНАЧЕ
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВТ_ФинРезВрем.Характер КАК Характер,
...
ПОМЕСТИТЬ ВТ_ФинРезВрем
ИЗ ВТ_ФинРезВрем
Конец;
Выясним причину, почему этот код вызовет синтаксическую ошибку при компиляции запроса. Конструкция ВЫБОР КОГДА ТОГДА ИНАЧЕ КОНЕЦ в языке запросов 1С (как и оператор CASE в T-SQL) является выражением, возвращающим конкретное значение (строку, число, дату или ссылку). Она может находиться только внутри секций получения или фильтрации данных (ВЫБРАТЬ, ГДЕ, ПО, ИМЕЮЩИЕ). Использовать ее как управляющую логическую обертку для целых запросов или для определения того, какую именно временную таблицу создавать, категорически запрещено.
Кроме того, если в последующих частях пакета вы обращаетесь к временной таблице (например, ВТ_ФинРез), она обязательно должна быть задекларирована и создана в рамках пакета. Если система попытается пропустить ее создание, транслятор запроса выдаст ошибку: «Таблица не найдена» еще на этапе проверки синтаксиса.
Рассмотрим самый популярный обходной путь, который позволяет реализовать условное выполнение логики непосредственно внутри одного запроса без привлечения программного кода 1С. Этот метод идеально подходит, если ваш отчет должен работать в виде чистой схемы СКД (например, во внешнем отчете без модуля).
Идея заключается в том, чтобы временная таблица ВТ_ФинРез создавалась в пакете всегда, но наполнялась по-разному в зависимости от значения параметра &Распределить. Для этого мы объединяем две ветки расчетов через оператор ОБЪЕДИНИТЬ ВСЕ, регулируя выборку условием ГДЕ:
// Создаем результирующую таблицу ВТ_ФинРез в любом случае
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВТ_ФинРезВрем.Характер КАК Характер,
ВТ_ФинРезВрем.Период КАК Период,
ВТ_ФинРезВрем.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
ВТ_ФинРезВрем.Подразделение КАК Подразделение,
ВТ_ФинРезВрем.Сумма КАК Сумма
ПОМЕСТИТЬ ВТ_ФинРез
ИЗ
ВТ_ФинРезВрем КАК ВТ_ФинРезВрем
ГДЕ
НЕ &Распределить // Ветка 1: Если распределение отключено, просто переносим данные как есть
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТабРаспределения.Характер,
ТабРаспределения.Период,
// Если распределяем - подставляем аналитику из коэффициентов
ВЫБОР
КОГДА ТабРаспределения.НоменклатурнаяГруппа = ЗНАЧЕНИЕ(Справочник.НоменклатурныеГруппы.ПустаяСсылка)
ТОГДА инф_Коэффициенты.Аналитика
ИНАЧЕ ТабРаспределения.НоменклатурнаяГруппа
КОНЕЦ,
ВЫБОР
КОГДА ТабРаспределения.Подразделение = ЗНАЧЕНИЕ(Справочник.Подразделения.ПустаяСсылка)
ТОГДА инф_КоэффициентыПодр.Аналитика
ИНАЧЕ ТабРаспределения.Подразделение
КОНЕЦ,
ТабРаспределения.Сумма * ЕСТЬNULL(инф_Коэффициенты.Коэффициент, 100) / 100
ИЗ
ВТ_ФинРезВрем КАК ТабРаспределения
// Тяжелые левые соединения для распределения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.инф_КоэффициентыРаспределения КАК инф_Коэффициенты
ПО (ТИПЗНАЧЕНИЯ(инф_Коэффициенты.Аналитика) = ТИП(Справочник.НоменклатурныеГруппы))
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.инф_КоэффициентыРаспределения КАК инф_КоэффициентыПодр
ПО (ТИПЗНАЧЕНИЯ(инф_КоэффициентыПодр.Аналитика) = ТИП(Справочник.Подразделения))
ГДЕ
&Распределить // Ветка 2: Выполняется только тогда, когда флаг Истина
;
Проанализируем производительность этого решения. Может показаться, что СУБД в любом случае будет тратить ресурсы на выполнение тяжелых левых соединений из второй ветки объединения. На самом деле это не так. Современные оптимизаторы СУБД (MS SQL Server, PostgreSQL, Oracle) при построении плана выполнения видят жесткое константное условие в секции ГДЕ (например, WHERE 0 = 1, когда параметр равен Ложь).
Для неактивной ветки планировщик строит план со специальным узлом (например, Constant Scan) и физически не производит чтение таблиц и вычисление соединений неактивной части запроса. Таким образом, производительность не страдает.
Если отчет разрабатывается в конфигурации и вы имеете доступ к его модулю объекта, наиболее профессиональным и чистым решением будет динамическая модификация текста запроса перед его выполнением. Это позволяет полностью исключить передачу "лишних" конструкций на уровень СУБД.
Разберем по шагам, как настроить этот механизм в модуле объекта отчета:
ПриКомпоновкеРезультата.Посмотрим на пример практической реализации данного метода:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь; // Берем управление формированием отчета на себя
// Получаем значение параметра "Распределить" из настроек компоновщика
ПараметрРаспределить = КомпоновщикНастроек.ПолучитьНастройки().ПараметрыДанных.Элементы.Найти("Распределить");
ЗначениеРаспределить = Ложь;
Если ПараметрРаспределить <> Неопределено И ПараметрРаспределить.Использование Тогда
ЗначениеРаспределить = ПараметрРаспределить.Значение;
КонецЕсли;
// Получаем схему компоновки данных
СхемаКомпоновки = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
НаборДанных = СхемаКомпоновки.НаборыДанных.Найти("НаборДанныхЗапрос");
// В зависимости от флага подставляем нужный вариант текста запроса
Если ЗначениеРаспределить Тогда
// Подставляем полную версию запроса с механизмами распределения
НаборДанных.Запрос = ПолучитьТекстЗапросаСРаспределением();
Иначе
// Подставляем легкую версию запроса без лишних соединений
НаборДанных.Запрос = ПолучитьТекстЗапросаБезРаспределения();
КонецЕсли;
// Выполняем стандартную компоновку с обновленной схемой
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновки, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Этот способ гарантирует максимальную производительность, так как база данных получает только тот код, который действительно необходим для построения текущей версии отчета.
Система компоновки данных 1С обладает мощным встроенным оптимизатором запросов. Если вы правильно используете расширение языка запросов СКД (фигурные скобки {}), то система сможет самостоятельно выбрасывать неиспользуемые таблицы и соединения из результирующего SQL-запроса.
Если распределение аналитик требуется только при условии, что пользователь выводит определенные детальные поля в отчет (например, распределенную номенклатурную группу или подразделение), мы можем объявить эти соединения как необязательные:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ТабРаспределения.Характер КАК Характер,
ТабРаспределения.Период КАК Период,
ТабРаспределения.Сумма КАК Сумма,
// Необязательные поля, которые СКД подключит только при необходимости
{инф_Коэффициенты.Аналитика КАК НоменклатурнаяГруппаРаспр,
инф_КоэффициентыПодр.Аналитика КАК ПодразделениеРаспр}
ИЗ
ВТ_ФинРезВрем КАК ТабРаспределения
// Помещаем соединения в фигурные скобки, делая их необязательными для СКД
{ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.инф_КоэффициентыРаспределения КАК инф_Коэффициенты
ПО (ТИПЗНАЧЕНИЯ(инф_Коэффициенты.Аналитика) = ТИП(Справочник.НоменклатурныеГруппы))
И (ТабРаспределения.НоменклатурнаяГруппа = ЗНАЧЕНИЕ(Справочник.НоменклатурныеГруппы.ПустаяСсылка))}
{ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.инф_КоэффициентыРаспределения КАК инф_КоэффициентыПодр
ПО (ТИПЗНАЧЕНИЯ(инф_КоэффициентыПодр.Аналитика) = ТИП(Справочник.Подразделения))
И (ТабРаспределения.Подразделение = ЗНАЧЕНИЕ(Справочник.Подразделения.ПустаяСсылка))}
Важный нюанс: Если пользователь в настройках структуры отчета (на вкладке "Поля" или в группировках) не выберет поля НоменклатурнаяГруппаРаспр и ПодразделениеРаспр, СКД автоматически вырежет секции ЛЕВОЕ СОЕДИНЕНИЕ из итогового текста запроса, отправляемого в СУБД. Это полностью избавит систему от необходимости выполнять тяжелые операции связывания данных в тех случаях, когда распределение не требуется.
Таким образом, комбинируя подходы с использованием условий ГДЕ, динамической модификации схем и возможностей расширения языка запросов СКД, вы сможете реализовать гибкую условную логику любой сложности, не уступающую возможностям операторов IF...ELSE в традиционных СУБД.