Как эффективно повторно использовать сложную логику запросов в 1С:Предприятии, или Аналоги хранимых процедур в мире 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом IT и автоматизация бизнеса
← К списку

Мы часто сталкиваемся с ситуациями, когда требуется выполнить сложный запрос, состоящий из множества промежуточных шагов, или использовать одну и ту же логику получения данных в разных местах конфигурации. В мире SQL для таких задач обычно применяют хранимые процедуры. Однако в платформе 1С:Предприятие прямого аналога хранимых процедур SQL в традиционном понимании нет. 1С абстрагируется от низкоуровневой работы с базой данных, предлагая свои механизмы для инкапсуляции и повторного использования сложной логики запросов. Давайте вместе разберемся, как мы можем достичь схожей функциональности, используя встроенные средства 1С.

Начнем с анализа основной проблемы: у нас есть запрос, который в конструкторе 1С выглядит как последовательность из пяти-шести запросов, результаты которых помещаются во временные таблицы. Затем эти временные таблицы объединяются в один запрос, который также помещается во временную таблицу, а предыдущие временные таблицы "гасятся". Дальше уже идет своя логика, каждый раз разная. Если бы мы писали это на SQL, мы бы вынесли такой кусок в хранимую процедуру. Но как быть в 1С? Рассмотрим подробнее доступные нам инструменты.

1. Пакет запросов и временные таблицы

Это основной и наиболее часто используемый механизм в 1С для работы со сложными запросами, требующими промежуточных вычислений. Он позволяет разбивать сложную логику на управляемые части, сохраняя результаты промежуточных вычислений.

  1. Как это работает: Мы формируем пакетный запрос, который состоит из нескольких запросов, разделенных точкой с запятой. Результаты одного запроса могут быть помещены во временную таблицу, которую затем можно использовать как источник данных в последующих запросах в том же пакете. Это позволяет последовательно обрабатывать данные, создавая промежуточные наборы, аналогично тому, как это делается в хранимых процедурах SQL с использованием временных таблиц.
  2. Преимущества:
    • Позволяет разбивать сложные запросы на более мелкие, управляемые части, что значительно улучшает читаемость и поддерживаемость кода.
    • Способствует оптимизации, поскольку промежуточные результаты могут быть проиндексированы для более быстрой выборки в последующих запросах.
    • Отлично подходит для сценариев, описанных в исходной проблеме, когда необходимо последовательно обрабатывать данные, создавая "цепочку" временных таблиц.
  3. Пример использования:

    Представим, что нам нужно сначала получить данные о продажах, затем на их основе рассчитать прибыль и только потом использовать эти агрегированные данные. Мы можем сделать это с помощью временных таблиц:

    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Продажи.Номенклатура КАК Номенклатура,
    |    Продажи.Количество КАК Количество,
    |    Продажи.Сумма КАК Сумма
    |ПОМЕСТИТЬ ВТ_Продажи
    |ИЗ
    |    Документ.РеализацияТоваровУслуг.Товары КАК Продажи
    |ГДЕ
    |    Продажи.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода;
    |
    ////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ_Продажи.Номенклатура КАК Номенклатура,
    |    СУММА(ВТ_Продажи.Количество) КАК ОбщееКоличество,
    |    СУММА(ВТ_Продажи.Сумма) КАК ОбщаяСумма,
    |    СУММА(ВТ_Продажи.Сумма) * 0.2 КАК РасчетнаяПрибыль // Пример расчета
    |ПОМЕСТИТЬ ВТ_АнализПродаж
    |ИЗ
    |    ВТ_Продажи КАК ВТ_Продажи
    |СГРУППИРОВАТЬ ПО
    |    ВТ_Продажи.Номенклатура;
    |
    ////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ_АнализПродаж.Номенклатура,
    |    ВТ_АнализПродаж.ОбщееКоличество,
    |    ВТ_АнализПродаж.ОбщаяСумма,
    |    ВТ_АнализПродаж.РасчетнаяПрибыль
    |ИЗ
    |    ВТ_АнализПродаж КАК ВТ_АнализПродаж
    |ГДЕ
    |    ВТ_АнализПродаж.РасчетнаяПрибыль > 1000
    |УПОРЯДОЧИТЬ ПО
    |    ВТ_АнализПродаж.РасчетнаяПрибыль УБЫВ;
    |
    ////////////////////////////////////////////////////////////////////////////////
    |УНИЧТОЖИТЬ ВТ_Продажи;
    |УНИЧТОЖИТЬ ВТ_АнализПродаж;
    |";
    Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
    Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДата()));
            
    РезультатЗапроса = Запрос.Выполнить();
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
            
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        // Обработка полученных данных
        Сообщить("Номенклатура: " + ВыборкаДетальныеЗаписи.Номенклатура + ", Прибыль: " + ВыборкаДетальныеЗаписи.РасчетнаяПрибыль);
    КонецЦикла;
    

    Мы видим, как последовательно создаются и используются временные таблицы ВТ_Продажи и ВТ_АнализПродаж, а затем уничтожаются, освобождая ресурсы.

2. Инкапсуляция логики в Общих модулях и Модулях менеджеров

Для повторного использования сложной логики запросов и их обработки мы можем активно применять стандартные средства 1С по структурированию кода.

  1. Общие модули:

    Общие модули предназначены для хранения процедур и функций, которые могут быть вызваны из любого места конфигурации. Это идеальное место для инкапсуляции часто используемой логики формирования запросов или обработки их результатов. Функции из общих модулей, объявленные с ключевым словом Экспорт, могут быть использованы не только в программном коде, но и в выражениях системы компоновки данных (СКД). Общие модули могут быть клиент-серверными, серверными или клиентскими, что позволяет гибко управлять местом их выполнения.

    Пример: Создадим общий модуль УправлениеЗапросамиСервер (свойство "Сервер" установлено) и добавим в него функцию, которая возвращает данные по продажам:

    
    // Общий модуль: УправлениеЗапросамиСервер
    Функция ПолучитьАнализПродаж(НачалоПериода, КонецПериода) Экспорт
        Запрос = Новый Запрос;
        Запрос.Текст = 
        "ВЫБРАТЬ
        |    Продажи.Номенклатура КАК Номенклатура,
        |    Продажи.Количество КАК Количество,
        |    Продажи.Сумма КАК Сумма
        |ПОМЕСТИТЬ ВТ_Продажи
        |ИЗ
        |    Документ.РеализацияТоваровУслуг.Товары КАК Продажи
        |ГДЕ
        |    Продажи.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода;
        |
        ////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    ВТ_Продажи.Номенклатура КАК Номенклатура,
        |    СУММА(ВТ_Продажи.Количество) КАК ОбщееКоличество,
        |    СУММА(ВТ_Продажи.Сумма) КАК ОбщаяСумма,
        |    СУММА(ВТ_Продажи.Сумма) * 0.2 КАК РасчетнаяПрибыль
        |ИЗ
        |    ВТ_Продажи КАК ВТ_Продажи
        |СГРУППИРОВАТЬ ПО
        |    ВТ_Продажи.Номенклатура
        |ГДЕ
        |    СУММА(ВТ_Продажи.Сумма) * 0.2 > 1000
        |УПОРЯДОЧИТЬ ПО
        |    РасчетнаяПрибыль УБЫВ;
        |
        ////////////////////////////////////////////////////////////////////////////////
        |УНИЧТОЖИТЬ ВТ_Продажи;
        |";
        Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
        Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
                
        Возврат Запрос.Выполнить().Выгрузить();
    КонецФункции
    

    Теперь мы можем вызывать эту функцию из любого места конфигурации, например:

    
    ТаблицаДанных = УправлениеЗапросамиСервер.ПолучитьАнализПродаж(НачалоМесяца(ТекущаяДата()), КонецМесяца(ТекущаяДата()));
    Для Каждого Строка Из ТаблицаДанных Цикл
        Сообщить("Номенклатура: " + Строка.Номенклатура + ", Прибыль: " + Строка.РасчетнаяПрибыль);
    КонецЦикла;
    
  2. Модули менеджеров:

    Модули менеджеров — это модули, связанные с конкретными объектами метаданных (справочниками, документами и т.д.). Они позволяют расширять функциональность этих объектов, описывая методы, относящиеся не к конкретному экземпляру объекта, а к объекту конфигурации в целом (например, к справочнику "Номенклатура" в целом, а не к конкретному элементу номенклатуры). Модули менеджеров выполняются только на сервере. В них удобно размещать процедуры и функции, которые работают с данными всего объекта или выполняют действия, не зависящие от конкретного элемента. Например, можно создать экспортную функцию в модуле менеджера справочника, которая формирует сложный запрос на основе данных этого справочника. Это позволяет создавать программный интерфейс для работы с данными, как это реализовано в типовых конфигурациях (например, в механизмах отчета "Дерево себестоимости продукции" или ресурсных спецификаций в ЗУП).

3. Механизм представлений запросов (Виртуальные таблицы ЗУП)

Это более продвинутый механизм, который позволяет значительно уменьшить количество повторяющегося кода запросов и упростить работу разработчика с данными. Он впервые появился в конфигурации 1С:ЗУП (Зарплата и управление персоналом), а затем распространился на другие "тяжелые" конфигурации, такие как ERP и Управление Холдингом.

  1. Суть: Механизм представлений является своего рода программным интерфейсом (API), который скрывает сложную логику получения данных. Он позволяет обращаться к "виртуальным таблицам" в запросах, которые на самом деле представляют собой результат выполнения сложной логики, инкапсулированной в коде 1С. Разработчику не нужно "ломать голову над тем, откуда и как правильно получать данные", механизм представлений делает это за него.
  2. Преимущества:
    • Кратное уменьшение кода запросов: Вместо того чтобы каждый раз вручную собирать пакетный запрос с временными таблицами, мы просто обращаемся к виртуальной таблице, которая уже содержит нужные данные.
    • Гибкая модификация физических таблиц: Если структура физических таблиц базы данных меняется, нам не нужно переписывать все запросы, использующие эти данные. Достаточно изменить логику формирования виртуальной таблицы в одном месте.
    • Упрощение разработки: Разработчик работает с высокоуровневыми представлениями данных, не вникая в низкоуровневые детали их получения.
  3. Как это выглядит: В запросе мы просто указываем имя виртуальной таблицы, например:
    
    ВЫБРАТЬ
        ВиртуальнаяТаблицаПродаж.Номенклатура,
        ВиртуальнаяТаблицаПродаж.СуммаПродаж
    ИЗ
        ВиртуальнаяТаблицаПродаж(&Параметры) КАК ВиртуальнаяТаблицаПродаж
    ГДЕ
        ВиртуальнаяТаблицаПродаж.СуммаПродаж > 0;
    

    За кадром платформа 1С и конфигурация выполняют сложную логику, собирают необходимые данные, возможно, используют пакетные запросы с временными таблицами, и возвращают результат в виде этой "виртуальной таблицы".

4. Использование шаблонов запросов и параметров

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

  1. Параметры запроса:

    В языке запросов 1С параметры обозначаются символом & (например, &ДатаДокумента). Значения для этих параметров устанавливаются программно с помощью метода Запрос.УстановитьПараметр(). Это позволяет многократно использовать один и тот же текст запроса с разными входными данными, что является базовым способом повышения гибкости запросов.

    
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Документ.РасходныйКассовыйОрдер ГДЕ Дата = &Дата";
    Запрос.УстановитьПараметр("Дата", ТекущаяДата());
    Результат = Запрос.Выполнить();
    
  2. Строковые шаблоны:

    Для более сложной динамической модификации текста запроса, когда нам нужно менять не только значения, но и целые фрагменты запроса (например, имена временных таблиц, сложные условия или даже источники данных), мы можем хранить шаблон запроса (например, в макете, регистре сведений или просто в строковой переменной) и заменять в нем определенные "заглушки" или "переменные" на нужные части кода непосредственно перед выполнением. Такие "заглушки" могут быть обозначены как @какаятохерня или &ТутИмяТаблицыДляЗамены, как обсуждалось на форуме.

    Пример: Предположим, у нас есть общий шаблон для получения данных из некоторой временной таблицы, имя которой может меняться.

    
    Функция ПолучитьДанныеИзВТ(ИмяВременнойТаблицы, УсловиеОтбора = "") Экспорт
        ТекстЗапросаШаблон = 
        "ВЫБРАТЬ
        |    ВТ.Поле1,
        |    ВТ.Поле2
        |ИЗ
        |    @ИмяВТ КАК ВТ
        |ГДЕ
        |    1 = 1 @ДополнительноеУсловие";
                
        ТекстЗапроса = СтрЗаменить(ТекстЗапросаШаблон, "@ИмяВТ", ИмяВременнойТаблицы);
                
        Если Не ПустаяСтрока(УсловиеОтбора) Тогда
            ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "@ДополнительноеУсловие", " И " + УсловиеОтбора);
        Иначе
            ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "@ДополнительноеУсловие", "");
        КонецЕсли;
                
        Запрос = Новый Запрос(ТекстЗапроса);
        // Если в запросе есть параметры, их также нужно установить
        // Запрос.УстановитьПараметр("МойПараметр", ЗначениеПараметра);
                
        Возврат Запрос.Выполнить().Выгрузить();
    КонецФункции
    

    Используя эту функцию, мы можем динамически формировать запрос:

    
    // Допустим, ранее мы создали временную таблицу 'ВТ_МоиДанные'
    // и хотим получить из нее данные с определенным условием.
    ТаблицаРезультат = ПолучитьДанныеИзВТ("ВТ_МоиДанные", "ВТ.Поле1 > 100");
    

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

Почему прямые хранимые процедуры SQL не используются в 1С

Возможно, у вас возникнет вопрос: "Зачем мне функции 1С? Я хочу все на стороне сервера ваять, средствами так скажем SQL." Это вполне логичное желание для тех, кто привык к разработке на уровне СУБД. Однако платформа 1С:Предприятие специально разработана таким образом, чтобы абстрагироваться от конкретной СУБД, и это имеет свои причины.

  1. Лицензионные соглашения и архитектура:

    Использование хранимых процедур SQL, созданных или модифицированных напрямую разработчиком, часто нарушает лицензионные соглашения 1С. Сама 1С генерирует и пересоздает необходимые хранимые процедуры при реструктуризации базы данных, а также обфусцирует (скрывает) имена таблиц и полей в каждом экземпляре базы. Это сделано для обеспечения кроссплатформенности (работа с PostgreSQL, MS SQL, Oracle, IBM DB2), целостности данных и защиты интеллектуальной собственности. Прямое вмешательство в структуру базы данных или создание собственных хранимых процедур может привести к неработоспособности конфигурации после обновления или реструктуризации.

  2. Ограничения внешних источников данных:

    Даже при работе с внешними источниками данных, 1С имеет серьезные ограничения: отсутствует возможность вызова хранимых процедур с возвратом значений для OUTPUT-параметров или получения набора данных из них, а также недоступно выполнение произвольных SQL-скриптов. Хотя существуют "костыльные" обходные пути, они не рекомендуются для использования в рабочем окружении из-за потенциальных проблем с надежностью, безопасностью и поддержкой.

  3. Управление кэшем:

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

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

← К списку