Мы часто сталкиваемся с ситуациями, когда требуется выполнить сложный запрос, состоящий из множества промежуточных шагов, или использовать одну и ту же логику получения данных в разных местах конфигурации. В мире SQL для таких задач обычно применяют хранимые процедуры. Однако в платформе 1С:Предприятие прямого аналога хранимых процедур SQL в традиционном понимании нет. 1С абстрагируется от низкоуровневой работы с базой данных, предлагая свои механизмы для инкапсуляции и повторного использования сложной логики запросов. Давайте вместе разберемся, как мы можем достичь схожей функциональности, используя встроенные средства 1С.
Начнем с анализа основной проблемы: у нас есть запрос, который в конструкторе 1С выглядит как последовательность из пяти-шести запросов, результаты которых помещаются во временные таблицы. Затем эти временные таблицы объединяются в один запрос, который также помещается во временную таблицу, а предыдущие временные таблицы "гасятся". Дальше уже идет своя логика, каждый раз разная. Если бы мы писали это на SQL, мы бы вынесли такой кусок в хранимую процедуру. Но как быть в 1С? Рассмотрим подробнее доступные нам инструменты.
Это основной и наиболее часто используемый механизм в 1С для работы со сложными запросами, требующими промежуточных вычислений. Он позволяет разбивать сложную логику на управляемые части, сохраняя результаты промежуточных вычислений.
Представим, что нам нужно сначала получить данные о продажах, затем на их основе рассчитать прибыль и только потом использовать эти агрегированные данные. Мы можем сделать это с помощью временных таблиц:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Количество КАК Количество,
| Продажи.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Продажи
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Продажи
|ГДЕ
| Продажи.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода;
|
////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Продажи.Номенклатура КАК Номенклатура,
| СУММА(ВТ_Продажи.Количество) КАК ОбщееКоличество,
| СУММА(ВТ_Продажи.Сумма) КАК ОбщаяСумма,
| СУММА(ВТ_Продажи.Сумма) * 0.2 КАК РасчетнаяПрибыль // Пример расчета
|ПОМЕСТИТЬ ВТ_АнализПродаж
|ИЗ
| ВТ_Продажи КАК ВТ_Продажи
|СГРУППИРОВАТЬ ПО
| ВТ_Продажи.Номенклатура;
|
////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_АнализПродаж.Номенклатура,
| ВТ_АнализПродаж.ОбщееКоличество,
| ВТ_АнализПродаж.ОбщаяСумма,
| ВТ_АнализПродаж.РасчетнаяПрибыль
|ИЗ
| ВТ_АнализПродаж КАК ВТ_АнализПродаж
|ГДЕ
| ВТ_АнализПродаж.РасчетнаяПрибыль > 1000
|УПОРЯДОЧИТЬ ПО
| ВТ_АнализПродаж.РасчетнаяПрибыль УБЫВ;
|
////////////////////////////////////////////////////////////////////////////////
|УНИЧТОЖИТЬ ВТ_Продажи;
|УНИЧТОЖИТЬ ВТ_АнализПродаж;
|";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДата()));
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Обработка полученных данных
Сообщить("Номенклатура: " + ВыборкаДетальныеЗаписи.Номенклатура + ", Прибыль: " + ВыборкаДетальныеЗаписи.РасчетнаяПрибыль);
КонецЦикла;
Мы видим, как последовательно создаются и используются временные таблицы ВТ_Продажи и ВТ_АнализПродаж, а затем уничтожаются, освобождая ресурсы.
Для повторного использования сложной логики запросов и их обработки мы можем активно применять стандартные средства 1С по структурированию кода.
Общие модули предназначены для хранения процедур и функций, которые могут быть вызваны из любого места конфигурации. Это идеальное место для инкапсуляции часто используемой логики формирования запросов или обработки их результатов. Функции из общих модулей, объявленные с ключевым словом Экспорт, могут быть использованы не только в программном коде, но и в выражениях системы компоновки данных (СКД). Общие модули могут быть клиент-серверными, серверными или клиентскими, что позволяет гибко управлять местом их выполнения.
Пример: Создадим общий модуль УправлениеЗапросамиСервер (свойство "Сервер" установлено) и добавим в него функцию, которая возвращает данные по продажам:
// Общий модуль: УправлениеЗапросамиСервер
Функция ПолучитьАнализПродаж(НачалоПериода, КонецПериода) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Количество КАК Количество,
| Продажи.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Продажи
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Продажи
|ГДЕ
| Продажи.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода;
|
////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Продажи.Номенклатура КАК Номенклатура,
| СУММА(ВТ_Продажи.Количество) КАК ОбщееКоличество,
| СУММА(ВТ_Продажи.Сумма) КАК ОбщаяСумма,
| СУММА(ВТ_Продажи.Сумма) * 0.2 КАК РасчетнаяПрибыль
|ИЗ
| ВТ_Продажи КАК ВТ_Продажи
|СГРУППИРОВАТЬ ПО
| ВТ_Продажи.Номенклатура
|ГДЕ
| СУММА(ВТ_Продажи.Сумма) * 0.2 > 1000
|УПОРЯДОЧИТЬ ПО
| РасчетнаяПрибыль УБЫВ;
|
////////////////////////////////////////////////////////////////////////////////
|УНИЧТОЖИТЬ ВТ_Продажи;
|";
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Теперь мы можем вызывать эту функцию из любого места конфигурации, например:
ТаблицаДанных = УправлениеЗапросамиСервер.ПолучитьАнализПродаж(НачалоМесяца(ТекущаяДата()), КонецМесяца(ТекущаяДата()));
Для Каждого Строка Из ТаблицаДанных Цикл
Сообщить("Номенклатура: " + Строка.Номенклатура + ", Прибыль: " + Строка.РасчетнаяПрибыль);
КонецЦикла;
Модули менеджеров — это модули, связанные с конкретными объектами метаданных (справочниками, документами и т.д.). Они позволяют расширять функциональность этих объектов, описывая методы, относящиеся не к конкретному экземпляру объекта, а к объекту конфигурации в целом (например, к справочнику "Номенклатура" в целом, а не к конкретному элементу номенклатуры). Модули менеджеров выполняются только на сервере. В них удобно размещать процедуры и функции, которые работают с данными всего объекта или выполняют действия, не зависящие от конкретного элемента. Например, можно создать экспортную функцию в модуле менеджера справочника, которая формирует сложный запрос на основе данных этого справочника. Это позволяет создавать программный интерфейс для работы с данными, как это реализовано в типовых конфигурациях (например, в механизмах отчета "Дерево себестоимости продукции" или ресурсных спецификаций в ЗУП).
Это более продвинутый механизм, который позволяет значительно уменьшить количество повторяющегося кода запросов и упростить работу разработчика с данными. Он впервые появился в конфигурации 1С:ЗУП (Зарплата и управление персоналом), а затем распространился на другие "тяжелые" конфигурации, такие как ERP и Управление Холдингом.
ВЫБРАТЬ
ВиртуальнаяТаблицаПродаж.Номенклатура,
ВиртуальнаяТаблицаПродаж.СуммаПродаж
ИЗ
ВиртуальнаяТаблицаПродаж(&Параметры) КАК ВиртуальнаяТаблицаПродаж
ГДЕ
ВиртуальнаяТаблицаПродаж.СуммаПродаж > 0;
За кадром платформа 1С и конфигурация выполняют сложную логику, собирают необходимые данные, возможно, используют пакетные запросы с временными таблицами, и возвращают результат в виде этой "виртуальной таблицы".
Для повторяющихся запросов, отличающихся только значениями некоторых условий или источников данных, мы можем использовать параметризованные запросы или строковые шаблоны. Этот подход позволяет хранить некий шаблон текста запроса и динамически модифицировать его перед выполнением.
В языке запросов 1С параметры обозначаются символом & (например, &ДатаДокумента). Значения для этих параметров устанавливаются программно с помощью метода Запрос.УстановитьПараметр(). Это позволяет многократно использовать один и тот же текст запроса с разными входными данными, что является базовым способом повышения гибкости запросов.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Документ.РасходныйКассовыйОрдер ГДЕ Дата = &Дата";
Запрос.УстановитьПараметр("Дата", ТекущаяДата());
Результат = Запрос.Выполнить();
Для более сложной динамической модификации текста запроса, когда нам нужно менять не только значения, но и целые фрагменты запроса (например, имена временных таблиц, сложные условия или даже источники данных), мы можем хранить шаблон запроса (например, в макете, регистре сведений или просто в строковой переменной) и заменять в нем определенные "заглушки" или "переменные" на нужные части кода непосредственно перед выполнением. Такие "заглушки" могут быть обозначены как @какаятохерня или &ТутИмяТаблицыДляЗамены, как обсуждалось на форуме.
Пример: Предположим, у нас есть общий шаблон для получения данных из некоторой временной таблицы, имя которой может меняться.
Функция ПолучитьДанныеИзВТ(ИмяВременнойТаблицы, УсловиеОтбора = "") Экспорт
ТекстЗапросаШаблон =
"ВЫБРАТЬ
| ВТ.Поле1,
| ВТ.Поле2
|ИЗ
| @ИмяВТ КАК ВТ
|ГДЕ
| 1 = 1 @ДополнительноеУсловие";
ТекстЗапроса = СтрЗаменить(ТекстЗапросаШаблон, "@ИмяВТ", ИмяВременнойТаблицы);
Если Не ПустаяСтрока(УсловиеОтбора) Тогда
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "@ДополнительноеУсловие", " И " + УсловиеОтбора);
Иначе
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "@ДополнительноеУсловие", "");
КонецЕсли;
Запрос = Новый Запрос(ТекстЗапроса);
// Если в запросе есть параметры, их также нужно установить
// Запрос.УстановитьПараметр("МойПараметр", ЗначениеПараметра);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Используя эту функцию, мы можем динамически формировать запрос:
// Допустим, ранее мы создали временную таблицу 'ВТ_МоиДанные'
// и хотим получить из нее данные с определенным условием.
ТаблицаРезультат = ПолучитьДанныеИзВТ("ВТ_МоиДанные", "ВТ.Поле1 > 100");
Этот подход дает нам большую гибкость, но требует внимательного отношения к формированию текста запроса, чтобы избежать SQL-инъекций и ошибок синтаксиса.
Возможно, у вас возникнет вопрос: "Зачем мне функции 1С? Я хочу все на стороне сервера ваять, средствами так скажем SQL." Это вполне логичное желание для тех, кто привык к разработке на уровне СУБД. Однако платформа 1С:Предприятие специально разработана таким образом, чтобы абстрагироваться от конкретной СУБД, и это имеет свои причины.
Использование хранимых процедур SQL, созданных или модифицированных напрямую разработчиком, часто нарушает лицензионные соглашения 1С. Сама 1С генерирует и пересоздает необходимые хранимые процедуры при реструктуризации базы данных, а также обфусцирует (скрывает) имена таблиц и полей в каждом экземпляре базы. Это сделано для обеспечения кроссплатформенности (работа с PostgreSQL, MS SQL, Oracle, IBM DB2), целостности данных и защиты интеллектуальной собственности. Прямое вмешательство в структуру базы данных или создание собственных хранимых процедур может привести к неработоспособности конфигурации после обновления или реструктуризации.
Даже при работе с внешними источниками данных, 1С имеет серьезные ограничения: отсутствует возможность вызова хранимых процедур с возвратом значений для OUTPUT-параметров или получения набора данных из них, а также недоступно выполнение произвольных SQL-скриптов. Хотя существуют "костыльные" обходные пути, они не рекомендуются для использования в рабочем окружении из-за потенциальных проблем с надежностью, безопасностью и поддержкой.
1С оптимизирует запросы на уровне платформы, и прямое вмешательство в управление кэшем SQL-сервера (например, очистка процедурного кэша) может не только не дать ожидаемого эффекта, но и навредить производительности, поскольку платформа имеет свои механизмы оптимизации и кэширования.
Таким образом, мы видим, что 1С предлагает мощные и гибкие механизмы для решения задач, аналогичных хранимым процедурам. Это пакетные запросы с временными таблицами для пошаговой обработки данных, инкапсуляция логики в общих модулях и модулях менеджеров для повторного использования кода, а также механизмы представлений и шаблонов запросов для динамического формирования и упрощения работы со сложными данными. Понимание и правильное применение этих инструментов позволит вам эффективно управлять сложной логикой запросов в ваших конфигурациях 1С.
← К списку