Приходилось ли вам сталкиваться с подобными задачами?
- Есть текст запроса, в который нужно добавлять определенные элементы (отборы, группировки, сортировки, итоги и т.п.), в зависимости от некоторых условий.
- Есть тексты двух или более запросов, которые нужно как-то скомбинировать: соединить или объединить.
- Нужно динамически сгенерировать текст запроса в соответствии с некоторым заданным описанием.
Некоторые из этих задач можно решить на уровне прямого манипулирования текстовыми фрагментами. Но это не всегда удобно, а порой и вовсе неприменимо.
В платформе 1С существует объект СхемаЗапроса, который предоставляет объектную модель запроса и, в общем-то, позволяет решать большинство подобных задач. Но и у него есть свои недостатки.
Во-1х, код с использованием схемы запроса получается несколько громоздким и не очень-то интуитивным.
Во-2х, схема запроса не умеет строить объектную модель вложенных запросов выражений (запрос в операторе "В (ВЫБРАТЬ …)").
Ну и в-3х, схема запроса имеет некоторое количество довольно неприятных багов и "особенностей". Пожалуй, самая известная из таких особенностей — это "шибко умное" автоматическое добавление соединений при добавлении источников, совершенно ненужное при программном формировании запроса, но при этом неотключаемое. Ниже будет отдельный раздел, посвященный выявленным ошибкам схемы запроса и путям их обхода.
В конце концов, мне захотелось создать удобный инструмент, использующий возможности схемы запроса и устраняющий ее недостатки. А заодно предоставляющий некоторые дополнительные возможности.
Представляю вам обработку ПроцессорСхемыЗапроса. Приготовиться к лонгриду. Поехали.
Общие принципы структурной обработки
Чтение и модификация существующих объектов
Необходимые условия прямого внедрения
Параметры виртуальных источников. Примеры 11-13
Виртуальные источники как альтернатива динамическому конструированию
Опции процессора схемы запроса
Выявленные ошибки схемы запроса
Несколько слов о производительности
Форма поставки и требования к платформе
Обзор возможностей
ПроцессорСхемыЗапроса стал результатом слияния двух проектов, у которых оказалось слишком много общих алгоритмов. Изначально были две отдельных задачи:
- Сделать удобную обертку для схемы запроса.
- Решить проблему применения запросов в условиях разграничения архитектурных уровней (об этом будет ниже).
Отсюда, собственно, и получился набор функций процессора, состоящий из двух тематических блоков:
- Структурная обработка запроса.
- Виртуальные источники данных.
А теперь обо всем по порядку.
Структурная обработка запроса
Начну с примеров.
Пример 1
Имеем текст запроса:
Текст1 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы";
И хотим добавить в него отбор, сортировку и выбрать "разрешенные первые 50". Изображу желаемый результат вот так:
Текст1 =
"ВЫБРАТЬ
// | РАЗРЕШЕННЫЕ ПЕРВЫЕ 50
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
// |ГДЕ
// | ТранспортныеЕдиницы.КлассТранспорта = &КлассТранспорта
// |
// |УПОРЯДОЧИТЬ ПО
// | Номер
|";
Здесь и далее комментариями буду выделять элементы запроса, которые требуется добавить динамически.
Если использовать непосредственно схему запроса, то потребуется написать вот такой код:
Схема = Новый СхемаЗапроса;
Схема.УстановитьТекстЗапроса(Текст1);
Запрос = Схема.ПакетЗапросов[0];
Запрос.ВыбиратьРазрешенные = Истина;
Оператор = Запрос.Операторы[0];
Оператор.КоличествоПолучаемыхЗаписей = 50;
Оператор.Отбор.Добавить("КлассТранспорта = &КлассТранспорта");
Запрос.Порядок.Добавить("Номер");
Текст2 = Схема.ПолучитьТекстЗапроса();
А вот так это выглядит с применением процессора:
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Разрешенные().Первые(50)
.Где("КлассТранспорта = &КлассТранспорта")
.УпорядочитьПо("Номер")
.ПолучитьТекстЗапроса();
Что мы видим на этом тривиальном примере:
- Процессор предоставляет "текучий интерфейс", что существенно сокращает количество буковок.
- Текст выглядит более приближенным к знакомому и интуитивно понятному тексту языка запросов.
- В целом код получается менее громоздкий, чем при непосредственном использовании схемы запроса.
Пример 2
Теперь мы хотим: добавить к запросу соединение с еще одной таблицей, несколько полей из этой таблицы и превратить запрос в запрос создания временной таблицы.
Текст1 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта,
| ТранспортныеЕдиницы.Грузоподъемность КАК Грузоподъемность
// | Автомобили.Марка КАК Марка,
// | Автомобили.ТипКузова КАК ТипКузова
// |ПОМЕСТИТЬ Автомобили
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
// | ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Автомобили КАК Автомобили
// | ПО ТранспортныеЕдиницы.Ссылка = Автомобили.ТранспортнаяЕдиница
|ГДЕ
| ТранспортныеЕдиницы.КлассТранспорта = &КлассТранспорта";
Вот так это делается при помощи процессора:
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Таблица("ТранспортныеЕдиницы")
.ВнутреннееСоединение().ИзТаблицы("Справочник.Автомобили", "Автомобили")
.ПоУсловию("ТранспортныеЕдиницы.Ссылка = Автомобили.ТранспортнаяЕдиница")
.Поля("Марка,ТипКузова")
.Поместить("Автомобили")
.ПолучитьТекстЗапроса();
А вот как выглядел бы код с использованием схемы запроса:
Схема = Новый СхемаЗапроса;
Схема.УстановитьТекстЗапроса(Текст1);
Запрос = Схема.ПакетЗапросов[0];
Оператор = Запрос.Операторы[0];
Оператор.Источники.Добавить("Справочник.Автомобили", "Автомобили");
Источник = Оператор.Источники.НайтиПоПсевдониму("ТранспортныеЕдиницы");
Оператор.Источники[1].Соединения.Очистить(); // спасибо "интеллектуальному" автодобавлению соединений
Источник.Соединения.Добавить("Автомобили", "ТранспортныеЕдиницы.Ссылка = Автомобили.ТранспортнаяЕдиница");
Соединение = Источник.Соединения[Источник.Соединения.Количество() - 1];
Соединение.ТипСоединения = ТипСоединенияСхемыЗапроса.Внутреннее;
Оператор.ВыбираемыеПоля.Добавить("Марка");
Оператор.ВыбираемыеПоля.Добавить("ТипКузова");
Запрос.ТаблицаДляПомещения = "Автомобили";
Текст2 = Схема.ПолучитьТекстЗапроса();
Здесь, очевидно, процессор вне конкуренции.
Пример 3
Добавляем объединение в запрос.
Текст1 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
|ГДЕ
| ТранспортныеЕдиницы.КлассТранспорта = ЗНАЧЕНИЕ(Перечисление.КлассыТранспорта.Автомобильный)
|
// |ОБЪЕДИНИТЬ ВСЕ
// |
// |ВЫБРАТЬ
// | ТранспортныеЕдиницы.Ссылка,
// | ТранспортныеЕдиницы.Наименование,
// | ТранспортныеЕдиницы.КлассТранспорта
// |ИЗ
// | Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
// |ГДЕ
// | ТранспортныеЕдиницы.КлассТранспорта = ЗНАЧЕНИЕ(Перечисление.КлассыТранспорта.Железнодорожный)
|";
С процессором это выглядит так:
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Объединить().Все()
.ИзТаблицы("Справочник.ТранспортныеЕдиницы", "ТранспортныеЕдиницы")
.Поле("ТранспортныеЕдиницы.Ссылка", "Ссылка")
.Поле("ТранспортныеЕдиницы.Наименование", "Номер")
.Поле("ТранспортныеЕдиницы.КлассТранспорта", "КлассТранспорта")
.Где("ТранспортныеЕдиницы.КлассТранспорта = ЗНАЧЕНИЕ(Перечисление.КлассыТранспорта.Железнодорожный)")
.ПолучитьТекстЗапроса();
Если второй запрос объединения отличается от первого лишь отдельными деталями, можно воспользоваться техникой копирования оператора с последующей точечной модификацией. Схема запроса дает возможность копирования запросов и операторов, а процессор делает это более удобным.
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Оператор(0).Скопировать()
.Отбор().Элемент(0)
.УстановитьТекст("КлассТранспорта = ЗНАЧЕНИЕ(Перечисление.КлассыТранспорта.Железнодорожный)")
.ПолучитьТекстЗапроса();
Пример 4
Теперь такая задача: добавить в запрос группировку, агрегирование, а также удалить ненужные поля. Исходный запрос выглядит так:
Текст1 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта,
| ТранспортныеЕдиницы.Грузоподъемность КАК Грузоподъемность
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы";
А получить мы хотим вот такой текст:
Текст2 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта,
| СРЕДНЕЕ(ТранспортныеЕдиницы.Грузоподъемность) КАК Грузоподъемность
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
|
|СГРУППИРОВАТЬ ПО
| ТранспортныеЕдиницы.КлассТранспорта";
Используем процессор:
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Поле(, "Грузоподъемность").Агрегировать("СРЕДНЕЕ")
.Поле(, "Ссылка").Удалить()
.Поле(, "Номер").Удалить()
//.СгруппироватьПо("ТранспортныеЕдиницы.КлассТранспорта") // это необязательно
.ПолучитьТекстЗапроса();
Здесь мы встречаемся с одним из немногих "узкоспециальных" методов процессора. Метод Агрегировать() предназначен специально для "оборачивания" выражения в агрегатную функцию.
В примере есть вызов СгруппироватьПо(), но он закомментирован как необязательный - при использовании агрегатных функций схема запроса автоматически добавляет необходимые группировки.
Пример 5
Сделаем, для разнообразия, что-нибудь действительно полезное, а заодно посмотрим на генерацию запроса с нуля. Например, как будет выглядеть несколько упрощенный аналог функции БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта с использованием процессора?
Функция ЗначениеРеквизитаОбъекта(Знач Ссылка, Знач ИмяРеквизита)
Возврат Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьПараметр("Ссылка", Ссылка)
.Выбрать().Первые(1)
.ИзТаблицы(Ссылка.Метаданные().ПолноеИмя(), "Т")
.Где("Ссылка = &Ссылка")
.Поле(ИмяРеквизита, "Реквизит")
.ПолучитьЗапрос().Выполнить().Выгрузить()[0].Реквизит;
КонецФункции
Здесь мы впервые видим, что процессор схемы запроса может работать не только со схемой запроса как таковой. Он может хранить параметры запроса и возвращать нам сразу объект Запрос.
Спойлер: он еще и не такое умеет.
Пример 6
Структурная обработка включает не только динамическое конструирование запроса, но и структурный анализ.
Предположим, у нас задача: добавить к запросу группировку с суммированием всех числовых полей.
Исходный текст запроса:
Текст1 =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Количество КАК Количество,
| Продажи.СуммаВал КАК СуммаВал
|ИЗ
| РегистрСведений.Продажи КАК Продажи";
Необходимо найти все поля числового типа и применить к ним агрегатную функцию СУММА. Группировки схема запроса добавит автоматически.
Процессор = Обработки.ПроцессорСхемыЗапроса.Создать().УстановитьТекстЗапроса(Текст1);
Для Каждого Псевдоним Из Процессор.КолонкиЗапроса().ИменаЭлементов() Цикл
ТипЗначения = Процессор.Колонка(Псевдоним).ПолучитьСвойство("ТипЗначения"); // ОписаниеТипов
Если ТипЗначения.СодержитТип(Тип("Число")) Тогда
Процессор.Поле(, Псевдоним).Агрегировать("СУММА");
КонецЕсли;
КонецЦикла;
Текст2 = Процессор.ПолучитьТекстЗапроса();
И в результате получим такой текст запроса:
Текст2 =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| СУММА(Продажи.Количество) КАК Количество,
| СУММА(Продажи.СуммаВал) КАК СуммаВал
|ИЗ
| РегистрСведений.Продажи КАК Продажи
|
|СГРУППИРОВАТЬ ПО
| Продажи.Номенклатура,
| Продажи.Валюта";
Пример 7
Переходим к более продвинутым возможностям. Нередко возникает задача создать в запросе временную таблицу на основе таблицы значений, которая передается как параметр запроса. Зачем писать запрос создания временной таблицы самому?
В данном примере предположим, что мы находимся в контексте формы документа, у которого есть табличная часть Состав. И нам нужно в запросе использовать данные этой табличной части, находящиеся на форме.
Запрос2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьПараметр("Состав", Объект.Состав.Выгрузить())
.ВыбратьИзПараметра("Состав")
.ПолучитьЗапрос();
В результате получим вот такой запрос создания временной таблицы:
Запрос2.Текст =
"ВЫБРАТЬ
| ВЫРАЗИТЬ(Состав.НомерСтроки КАК ЧИСЛО) КАК НомерСтроки,
| ВЫРАЗИТЬ(Состав.ТранспортнаяЕдиница КАК Справочник.ТранспортныеЕдиницы) КАК ТранспортнаяЕдиница,
| ВЫРАЗИТЬ(Состав.ВесГруза КАК ЧИСЛО(10, 0)) КАК ВесГруза,
| ВЫРАЗИТЬ(Состав.ОбъемГруза КАК ЧИСЛО(10, 0)) КАК ОбъемГруза,
| ВЫРАЗИТЬ(Состав.ДатаЗагрузки КАК ДАТА) КАК ДатаЗагрузки
|ПОМЕСТИТЬ Состав
|ИЗ
| &Состав КАК Состав";
Здесь мы с вами обнаруживаем, что процессор умеет типизировать поля временной таблицы, используя конструкцию ВЫРАЗИТЬ. Типизация полей важна, если планируется использовать эту временную таблицу при дальнейшем динамическом конструировании запроса. В частности, для разыменования ссылочных полей они должны быть типизированы, иначе схема запроса выдаст ошибку при попытке создать выражение с разыменованием.
При желании типизацию полей можно отключить, в методе ВыбратьИзПараметра есть необязательный параметр, который за это отвечает.
Пример 8
Вложенные запросы выражений - то, что схема запроса не умеет структурировать. Ну, как не умеет. Главное же - найти правильный подход.
Возьмем запрос и добавим к нему условие отбора - глупое, но зато содержащее вложенный запрос.
Текст1 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
// |ГДЕ
// | ИСТИНА В (
// | ВЫБРАТЬ ПЕРВЫЕ 1
// | ИСТИНА КАК Поле1
// | ИЗ
// | Справочник.Автомобили КАК Автомобили
// | ГДЕ
// | Автомобили.ТранспортнаяЕдиница = ТранспортныеЕдиницы.Ссылка)
|";
И сделаем это динамически:
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Где("ИСТИНА В (ВЫБРАТЬ NULL)")
.ВложенныйВыбрать().Первые(1)
.ИзТаблицы("Справочник.Автомобили", "Автомобили")
.Поле("ИСТИНА")
.Где("Автомобили.ТранспортнаяЕдиница = ТранспортныеЕдиницы.Ссылка")
.ПолучитьТекстЗапроса();
Тут мы видим, что процессор поддерживает полноценную работу с объектной моделью вложенных запросов в выражениях. Причем, используя для этого ту самую схему запроса. Ну, не совсем ту самую, а ее отдельный экземпляр. Но обратите внимание, что во вложенном запросе без проблем используется поле из контекста вышестоящего запроса (ТранспортныеЕдиницы.Ссылка). Признаюсь, с этим пришлось немного попотеть. Пришлось таки немножечко сделать свой небольшой парсер запроса.
Обратите внимание на технику создания вложенного запроса "с чистого листа". Изначально в выражении размещена конструкция "В (ВЫБРАТЬ NULL)". Это необходимо, чтобы "обозначить место", где должен находиться вложенный запрос. Далее используется метод ВложенныйВыбрать(), который "проваливается" во вложенный запрос и очищает его содержимое, подготавливая тот самый "чистый лист". Также у метода ВложенныйВыбрать() есть необязательный параметр Индекс - на случай, если в выражении больше одного вложенного запроса на верхнем уровне вложенности.
Пример 9
Теперь посмотрим, как происходит модификация уже имеющегося вложенного запроса. Возьмем запрос, который получился в предыдущем примере, и добавим во вложенный запрос условие отбора.
Текст1 =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Наименование КАК Номер
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
|ГДЕ
| ИСТИНА В (
| ВЫБРАТЬ ПЕРВЫЕ 1
| ИСТИНА КАК Поле1
| ИЗ
| Справочник.Автомобили КАК Автомобили
| ГДЕ
| Автомобили.ТранспортнаяЕдиница = ТранспортныеЕдиницы.Ссылка
// | И НЕ Автомобили.ПометкаУдаления
| )";
Код будет выглядеть так:
Текст2 = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(Текст1)
.Отбор().Элемент(0)
.ВложенныйЗапрос()
.Где("НЕ Автомобили.ПометкаУдаления")
.ПолучитьТекстЗапроса();
На этом пока закончим с примерами и перейдем к общей теории.
Общие принципы структурной обработки запроса
Процессор схемы запроса содержит всего 100 методов структурной обработки. Вы уже достали валидол? Отлично, положите на место. Функциональность процессора охватывает всю полноту объектной модели запроса, вплоть до элементов компоновки данных и таких пыльных углов, как "доступные таблицы запроса". Но в 80% задач вы будете использовать 20% функций. К тому же, я постарался обеспечить максимальную лингвистическую преемственность: имена методов будут звучать знакомо, если вы имели дело с языком запросов или с объектной моделью схемы запроса.
Работа всех методов структурной обработки базируется на понятии текущего контекста (или фокуса). Всю объектную модель запроса можно представить в виде дерева, где корневым узлом является сам объект СхемаЗапроса, а ниже расположена иерархия разнообразных объектов и коллекций. Фокус — это указатель на узел дерева, в котором мы в текущий момент находимся. Мы можем перемещать фокус по дереву, а относительно текущего узла дерева совершать некоторые действия - создавать новые узлы-объекты, искать, удалять, двигать объекты в коллекциях, а также читать и изменять свойства объектов. В начальном состоянии фокус позиционируется на самом объекте СхемаЗапроса.
Соответственно, все методы структурной обработки делится на 3 основные группы:
- Методы-навигаторы - служат для перемещения фокуса по дереву объектов.
- Методы-конструкторы - для создания новых объектов.
- Методы чтения/записи - для чтения и модификации содержимого существующих объектов.
Базовая навигация
Методов базовой навигации не так много, они универсальны, их можно перечислить все:
- Объект(Имя) - позиционируется на вложенном объекте с указанным именем. Например, Объект("Колонки") позиционируется на коллекции колонок текущего запроса - разумеется, при условии, что текущий фокус указывает на объект типа ЗапроВыбораСхемыЗапроса.
- Родитель() - позиционируется на родительском объекте текущего объекта.
- Элемент(Индекс) - позиционируется на элементе с указанным индексом в текущей коллекции.
- ЭлементПоИмени(Имя) - позиционируется на элементе с указанным именем в текущей коллекции. Разумеется, это должна быть коллекция, элементы которой имеют имена или псевдонимы.
- ЭлементПоПсевдониму(Псевдоним) - позиционируется на элементе с указанным псевдонимом. Служит альтернативой предыдущему методу в тех случаях, когда у элементов есть одновременно имена и псевдонимы (как в коллекции Источники).
- ЭлементПоВыражению(Выражение) - позиционируется на элементе с указанным текстом выражения. Работает только для элементов, содержащих выражение, таких как выбираемые поля, выражения отбора, группировки и т.п.
- Предыдущий() - позиционируется на предыдущем соседе элемента в текущей коллекции.
- Следующий() - позиционируется на следующем соседе элемента в текущей коллекции.
Все методы из этого списка, кроме двух первых, имеют опциональный параметр Обязательный. Если он Истина (а он по умолчанию Истина), то будет вызвано исключение, если элемент коллекции не найден. Если Ложь, то метод просто вернет Неопределено, что негативно скажется на "текучести", но зато пригодится при структурном анализе вариативного запроса.
Предметная навигация
Методов предметной навигации около 40, каждый из них предназначен для позиционирования на объекте или коллекции определенного типа. Их имена почти всегда повторяют (в сокращенном виде) имена объектов схемы запроса. Все они звучат как имена существительные в именительном падеже. В единственном числе - имена объектов, в множественном числе - имена коллекций.
Некоторые методы предметной навигации (не все):
- Запрос() - позиционируется на запросе по умолчанию. Это либо текущий запрос - тогда фокус возвращается из глубин иерархии вверх, к объекту ЗапросВыбораСхемыЗапроса. Либо, если текущий фокус находит не внутри запроса - позиционируется на единственном запросе в текущем пакете запросов. В этом случае, если в пакете больше одного запроса, будет вызвано исключение.
- Запрос(Индекс) - позиционируется на запросе с указанным индексом. Этот вариант следует использовать в пакетных запросах.
- ОсновнойЗапрос() - позиционируется на основном запросе в текущем пакете запросов, т.е. на последнем запросе, который не создает и не уничтожает временную таблицу. Этот тот самый запрос, результат которого возвращается, когда мы пишем Запрос.Выполнить().
- Операторы() - позиционируется на коллекции операторов запроса.
- Оператор() - позиционируется на текущем операторе.
- Оператор(Индекс) - позиционируется на операторе с заданным индексом.
- Источник(Псевдоним) - позиционируется на источнике с заданным псевдонимом в источниках текущего оператора (т.е на объекте типа ИсточникСхемыЗапроса).
- Таблица(Псевдоним) - позиционируется на таблице с заданным псевдонимом в источниках текущего оператора. Таблицей я для краткости называю объект, подчиненный объекту ИсточникСхемыЗапроса. Фактически это один из следующих типов: ТаблицаСхемыЗапроса, ОписаниеВременнойТаблицыСхемыЗапроса, ВложенныйЗапросСхемыЗапроса.
- ВыбираемыеПоля() - позиционируется на коллекции выбираемых полей текущего оператора.
- Поля() - позиционируется на любой текущей коллекции выбираемых полей (оператора или вложенной таблицы).
- Поля(ДобавляемыеПоля) - этот вариант метода является еще и конструктором, он не только позиционируется на коллекции полей, но и добавляет в нее поля, заданные в параметре (это может быть строка со списком полей через запятую, может быть массив имен, а может быть структура, где ключ - псевдоним поля, значение - выражение поля).
- Отбор() - позиционируется на коллекции выражений отбора текущего оператора.
- ВложенныеЗапросыВыражения() - позиционируется на коллекции вложенных запросов текущего выражения. Это квази-объект - в том смысле, что он не является "родным" объектом схемы запроса, а эмулируется на уровне процессора. Элементы этой коллекции также являются квази-объектами. Если фокус находится на таком объекте, то вызов Объект("Схема") позиционирует нас на объект СхемаЗапроса, содержащий объектную модель вложенного запроса выражения. Но гораздо проще использовать метод ВложенныйЗапрос().
- ВложенныйЗапрос() - позиционируется на вложенном запросе. В зависимости от контекста, это может быть вложенный запрос-источник (которые живут в секции запроса "ИЗ"), вложенный запрос выражения, и даже запрос-параметр виртуального источника (об этом см. в соответствующем разделе). В контексте выражения может иметь необязательный параметр Индекс - для указания одного из нескольких запросов выражения.
И т.д. и т.п. Полное описание методов можно найти в программном интерфейсе обработки ПроцессорСхемыЗапроса, там каждый метод снабжен подробным документирующим комментарием.
Конструирование
Методов-конструкторов тоже почти 40 (магическое число однако). Каждый из них предназначен для создания объекта определенного типа. Некоторые сочетают в себе функции конструктора и навигатора, т.е. могут позиционироваться на существующем объекте. Некоторые могут также изменять существующий объект вместо добавления нового.
Названия методов в основном созвучны ключевым словам языка запросов (в пределах возможного, конечно). Практически все методы-конструкторы позиционируют фокус на том объекте, который создают.
Некоторые методы конструирования (не все):
- Выбрать() - создает новый запрос выбора в пакете запросов корневой схемы.
- ВложенныйВыбрать() - позиционируется на вложенном запросе текущего контекста и полностью очищает содержимое запроса. В контексте выражения может иметь необязательный параметр Индекс - для указания одного из нескольких запросов выражения.
- Объединить() - добавляет новый оператор (объект ОператорВыбратьСхемыЗапроса) в текущем запросе. По умолчанию в запросе всегда есть 0-й оператор, поэтому создание любого дополнительного оператора порождает объединение. Вызов эквивалентен конструкции языка запросов "ОБЪЕДИНИТЬ". Цепочка вызовов Объединить().Все() эквивалентна конструкции "ОБЪЕДИНИТЬ ВСЕ". Обратите внимание, что после этого не нужно вызывать метод Выбрать() - оператор выбора и так уже создан методом Объединить(). Вызов метода Выбрать() приведет к созданию нового объекта ЗапросВыбораСхемыЗапроса в пакете запросов.
- ИзТаблицы(ИмяТаблицы, Псевдоним) - добавляет источник-таблицу в источники текущего оператора. Процессор автоматически определяет, является ли таблица одной из доступных таблиц или описанием необъявленной временной таблицы, и создает источник соответствующего типа. В итоге позиционируется на созданном объекте типа ТаблицаСхемыЗапроса или ОписаниеВременнойТаблицыСхемыЗапроса.
- ИзЗапроса(Псевдоним) - добавляет источник-вложенный запрос в источники текущего оператора и позиционируется на созданном объекте ВложенныйЗапросСхемыЗапроса. После этого можно вызвать ВложенныйЗапрос() или ВложенныйВыбрать(), чтобы "провалиться" в сам запрос.
- ВнутреннееСоединение(Псевдоним) - добавляет внутреннее соединение с указанным источником в соединения текущего источника. Источник с таким псевдонимом уже должен существовать. Есть аналогичные методы ЛевоеСоединение, ПравоеСоединение, ПолноеСоединение.
- ВнутреннееСоединение() - без параметра создает т.н. "открытое соединение". На самом деле ничего не создает, просто позиционируется на коллекции соединений текущего источника и включает режим ожидания соединения. Следующим вызовом должен быть добавлен новый источник (методом ИзТаблицы или ИзЗапроса). В результате будет автоматически создано соединение с этим источником (если такой вызов не будет сделан в текущем контексте, открытое соединение теряется). Таким способом можно порождать соединения (в т.ч. вложенные соединения) в синтаксическом стиле, подобном языку запросов (см. Пример 2).
- ПоУсловию(Выражение) - добавляет условие текущего соединения.
- ПоУсловию(Выражение, Индекс) - изменяет условие существующего соединения с указанным индексом. Дело в том, что если условие соединения состоит из нескольких выражений, соединенных операцией "И", схема запроса при разборе текста запроса автоматически раскладывает такое соединение на несколько объектов СоединениеИсточникаЗапросаСхемыЗапроса - получается по сути группа из нескольких соединений между одной и той же парой источников (именно так мы их видим в конструкторе запроса в конфигураторе). Поэтому и нужен параметр Индекс - он указывает элемент в этой группе соединений. Не всегда удобно работать с такой моделью соединений, поэтому в процессоре предусмотрена опция КонкатенацияУсловийСоединений (см. в соответствующем разделе).
- Поле(Выражение, Псевдоним) - добавляет поле в выбираемые поля текущего оператора или текущей вложенной таблицы. Если не указать псевдоним, он будет присвоен автоматически схемой запроса. Если указать псевдоним уже существующего поля, то будет установлено выражение существующего поля. Если не указывать выражение, а только псевдоним, то метод работает как навигатор - просто позиционируется на заданном поле.
- Где(Выражение) - добавляет выражение отбора текущего оператора.
- СгруппироватьПо(Выражение) - добавляет выражение группировки текущего оператора.
- УпорядочитьПо(Выражение, Направление) - добавляет выражение порядка текущего запроса.
И т.д. и т.п.
Чтение и модификация существующих объектов
Этих методов не так много, они универсальны.
- ТипОбъекта() - возвращает тип текущего объекта.
- ИмяОбъекта() - возвращает имя текущего объекта (как он именуется в родительском объекте).
- ИндексОбъекта() - возвращает индекс текущего объекта в родительской коллекции.
- ПолучитьСвойство(Имя) - получить значение свойства текущего объекта.
- УстановитьСвойство(Имя, Значение) - установить значение свойства текущего объекта.
- ПолучитьТекст() - возвращает текст текущего объекта - схемы, запроса или выражения.
- УстановитьТекст(Текст) - устанавливает текст текущего объекта - схемы, запроса или выражения.
Работа с коллекциями:
- Количество() - возвращает количество элементов текущей коллекции.
- Удалить() - удалить текущий объект из родительской коллекции и позиционироваться на коллекции.
- Скопировать() - скопировать текущий объект в родительской коллекции и позиционироваться на созданной копии. Копирование поддерживается только в двух типах коллекций: это пакет запросов и коллекция операторов запроса.
- Переместить(НовыйИндекс) - переместить текущий объект на указанную позицию в родительской коллекции, фокус сохраняется на текущем объекте.
- Сдвинуть(Смещение) - сдвинуть текущий объект на указанное число позиций в родительской коллекции, фокус сохраняется на текущем объекте. Перемещение и сдвиг поддерживаются не во всех коллекциях, см. детальные описания методов в программном интерфейсе.
- Очистить() - очистить текущую коллекцию.
- ИменаЭлементов() - возвращает массив имен или псевдонимов элементов текущей коллекции. Работает не во всех коллекциях, а только там, где у элементов есть имена или псевдонимы.
Общие методы чтения и записи
Существует также набор "глобальных" методов чтения и записи, работающих независимо от текущего контекста, которые служат для установки входных данных процессора и получения выходных результатов. Следует иметь в виду, что многие из этих методов сбрасывают текущий фокус в начальное состояние.
- УстановитьТекстЗапроса(Текст) - установить текст запроса корневой схемы запроса.
- ПолучитьТекстЗапроса() - получить текст запроса корневой схемы запроса.
- УстановитьЗапрос(Запрос) - установить текст запроса, параметры запроса и менеджер временных таблиц из заданного объекта Запрос (ссылка на сам переданный объект Запрос не хранится).
- УстановитьПараметр(Имя, Значение) - установить параметр запроса.
- ПараметрыЗапроса() - получить текущие параметры запроса в виде структуры.
- МенеджерВременныхТаблиц() - получить текущий менеджер временных таблиц.
- ПолучитьЗапрос() - Получить объект Запрос, содержащий текущий текст запроса, установленные параметры и менеджер временных таблиц (если был установлен).
- ПолучитьКонечныйЗапрос() - получить объект Запрос, сформированный в результате встраивания виртуальных источников (мы уже вот-вот до них доберемся).
- Клонировать() - получить копию процессора со всем содержимым.
Этим программный интерфейс процессора не исчерпывается, есть еще ряд специализированных методов, о них можно подробно прочитать в документирующих комментариях.
Виртуальные источники данных
Если вы дочитали до сюда, то не зря. Сейчас на ваших глазах будет совершаться революция в деле использования запросов как таковых. Шутка. Или нет.
Идея виртуальных источников данных родилась в ходе решения задачи разграничения архитектурных уровней.
У запросов, а точнее - у традиционного способа их использования, есть один крохотный недостаток, убивающий на корню любые попытки создать более-менее чистую архитектуру (за определением термина "чистая архитектура" отсылаю к одноименной книге Роберта Мартина). Дело в том, что мы привыкли использовать запросы где угодно - там, где считаем это целесообразным с точки зрения обеспечения высокой производительности при выборке данных из БД. Мы делаем это на любых архитектурных уровнях (неважно, знаем ли мы об их существовании). А запросы при этом лезут на самый нижний архитектурный уровень - непосредственно к структуре БД (ИБ, если быть точным). Тем самым нарушая всякие архитектурные границы и создавая нисходящие зависимости (т.е. зависимости верхних уровней от нижних), чего следует категорически избегать в чистой архитектуре.
В то же время, отказаться от использования запросов - означает убить производительность. Чистая архитектура или высокая производительность? Дилемма.
В ООП задача разграничения архитектурных уровней решается посредством инкапсуляции, абстрагирования и интерфейсов. Это хорошо работает, когда речь идет о вызове процедур и функций. Можно ли то же самое проделать с запросами?
Представим абстрактный источник данных - таблицу. Он имеет определенный состав колонок, типы данных для которых также определены. Также он может иметь определенный набор параметров (также определенных типов), от которых зависит результирующий набор строк в таблице. Все это вместе составляет описание интерфейса.
Реализация источника (т.е. способ получения определенного набора строк в таблице) скрыта за интерфейсом. Вызывающая сторона не обязана ничего знать о деталях реализации. Это инкапсуляция.
Представим теперь запрос, который обращается к этому абстрактному источнику данных как к обычной таблице. Этот запрос не нарушает архитектурных границ, ведь он, по сути, имеет дело лишь с интерфейсом абстрактного источника и не зависит от деталей его внутренней реализации. В то же время, если оптимальным образом трансформировать этот запрос в конечный запрос к БД, использующий уже реальные таблицы, то будет сохранена и производительность.
В описанном подходе запросы из архитектурных хулиганов, нарушающих всякие границы, превращаются в благопристойных членов общества корректный (с позиций чистой архитектуры) инструмент высокопроизводительной выборки данных.
Механизм виртуальных источников в процессоре схемы запроса позволяет реализовать эту концепцию.
Пример 10
Допустим, на нижнем архитектурном уровне у нас есть сущности ТранспортныеЕдиницы и Автомобили, основанные на одноименных справочниках. Мы хотим скрыть техническую особенность 1С - пометку на удаление. Помеченные элементы не должны появляться в поле зрения.
Определим эти источники как запросы:
ИсточникТранспортныеЕдиницы =
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.ПометкаУдаления КАК ПометкаУдаления,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта,
| ТранспортныеЕдиницы.Наименование КАК Номер,
| ТранспортныеЕдиницы.Грузоподъемность КАК Грузоподъемность
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
|ГДЕ
| НЕ ТранспортныеЕдиницы.ПометкаУдаления";
ИсточникАвтомобили =
"ВЫБРАТЬ
| Автомобили.Ссылка КАК Ссылка,
| Автомобили.ПометкаУдаления КАК ПометкаУдаления,
| Автомобили.Наименование КАК Номер,
| Автомобили.ТипКузова КАК ТипКузова,
| Автомобили.Марка КАК Марка,
| Автомобили.ТранспортнаяЕдиница КАК ТранспортнаяЕдиница
|ИЗ
| Справочник.Автомобили КАК Автомобили
|ГДЕ
| НЕ Автомобили.ПометкаУдаления";
На более высоком архитектурном уровне у нас есть сущность Автотранспорт, которая представляет собой транспортную единицу, обогащенную данными из связанной сущности Автомобили.
Определяем запрос, использующий предыдущие два запроса как виртуальные источники. И при этом сразу заворачиваем его внутрь экземпляра процессора. Это будет источник данных более высокого уровня.
ИсточникАвтотранспорт = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьИсточник("ТранспортныеЕдиницы", ИсточникТранспортныеЕдиницы)
.УстановитьИсточник("Автомобили", ИсточникАвтомобили)
.УстановитьТекстЗапроса(
"ВЫБРАТЬ
| ТранспортныеЕдиницы.Ссылка КАК Ссылка,
| ТранспортныеЕдиницы.Номер КАК Номер,
| ТранспортныеЕдиницы.КлассТранспорта КАК КлассТранспорта,
| ТранспортныеЕдиницы.Грузоподъемность КАК Грузоподъемность,
| ЕСТЬNULL(Автомобили.ТипКузова, ЗНАЧЕНИЕ(Справочник.ТипыКузова.ПустаяСсылка)) КАК ТипКузова,
| ЕСТЬNULL(Автомобили.Марка, ЗНАЧЕНИЕ(Справочник.МаркиАвтомобилей.ПустаяСсылка)) КАК Марка
|ИЗ
| ТранспортныеЕдиницы КАК ТранспортныеЕдиницы
| ЛЕВОЕ СОЕДИНЕНИЕ Автомобили КАК Автомобили
| ПО ТранспортныеЕдиницы.Ссылка = Автомобили.ТранспортнаяЕдиница
|ГДЕ
| ТранспортныеЕдиницы.КлассТранспорта = ЗНАЧЕНИЕ(Перечисление.КлассыТранспорта.Автомобильный)");
Здесь мы используем метод процессора УстановитьИсточник() для подключения виртуальных источников. А затем в запросе обращаемся к виртуальным источникам, как к обычным таблицам. Схема запроса принимает их за необъявленные временные таблицы - ну и пускай так думает, до поры.
Кстати, порядок вызовов неважен: можно сначала установить текст запроса, а потом установить источники. Все равно весь анализ процессор произведет в самом конце.
Наконец, на еще более высоком уровне решаем прикладную задачу отбора автотранспорта. Этот запрос будет использовать виртуальный источник Автотранспорт:
ТекстЗапроса =
"ВЫБРАТЬ
| Автотранспорт.Ссылка КАК Ссылка,
| Автотранспорт.Номер КАК Номер,
| Автотранспорт.Грузоподъемность КАК Грузоподъемность,
| Автотранспорт.ТипКузова.Наименование КАК ТипКузова,
| Автотранспорт.Марка КАК Марка
|ИЗ
| Автотранспорт КАК Автотранспорт
|ГДЕ
| Автотранспорт.Грузоподъемность >= &Грузоподъемность
|
|УПОРЯДОЧИТЬ ПО
| Грузоподъемность,
| Автотранспорт.ТипКузова";
Запрос = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(ТекстЗапроса)
.УстановитьИсточник("Автотранспорт", ИсточникАвтотранспорт)
.ПолучитьКонечныйЗапрос();
В самом конце мы используем метод ПолучитьКонечныйЗапрос() для получения запроса, который будет непосредственно исполняться. В этот момент происходит встраивание виртуальных источников на всех уровнях вложенности.
И вот какой получается конечный запрос:
Запрос.Текст =
"ВЫБРАТЬ
| Автотранспорт.Ссылка КАК Ссылка,
| Автотранспорт.Наименование КАК Номер,
| Автотранспорт.Грузоподъемность КАК Грузоподъемность,
| ВЫРАЗИТЬ(ЕСТЬNULL(Автомобили.ТипКузова, ЗНАЧЕНИЕ(Справочник.ТипыКузова.ПустаяСсылка)) КАК Справочник.ТипыКузова).Наименование КАК ТипКузова,
| ЕСТЬNULL(Автомобили.Марка, ЗНАЧЕНИЕ(Справочник.МаркиАвтомобилей.ПустаяСсылка)) КАК Марка
|ИЗ
| Справочник.ТранспортныеЕдиницы КАК Автотранспорт
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Автомобили КАК Автомобили
| ПО (Автотранспорт.Ссылка = Автомобили.ТранспортнаяЕдиница
| И НЕ Автомобили.ПометкаУдаления)
|ГДЕ
| Автотранспорт.Грузоподъемность >= &Грузоподъемность
| И Автотранспорт.КлассТранспорта = ЗНАЧЕНИЕ(Перечисление.КлассыТранспорта.Автомобильный)
| И НЕ Автотранспорт.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
| Грузоподъемность,
| ЕСТЬNULL(Автомобили.ТипКузова, ЗНАЧЕНИЕ(Справочник.ТипыКузова.ПустаяСсылка))";
Что мы здесь видим:
- Каждый виртуальный источник встроился непосредственно по месту его применения (это называется "прямое внедрение").
- Хотя в виртуальных источниках нижнего уровня выбираются данные таблиц практически без ограничений (пометка удаления не в счет), конечный запрос выглядит эффективным, т.к. учитывает все ограничения, наложенные на более высоких уровнях.
- Даже разыменование поля, которое в виртуальном источнике реализовано как сложное выражение, конвертировано в синтаксически корректную конструкцию с использованием ВЫРАЗИТЬ.
Красота же?
Но, конечно, не всегда результат будет выглядеть так красиво. Задача прямого внедрения не имеет решения в общем виде. Чтобы виртуальный источник встроился по месту применения, определенным критериям должны отвечать как запрос виртуального источника, так и запрос, в который он встраивается. Если критерии не выполняются, виртуальный источник будет встроен как временная таблица.
Необходимые условия прямого внедрения
Прежде, чем перейти к определению критериев прямого внедрения, нужно определить некоторые термины:
- Целевой оператор - оператор, в который встраивается виртуальный источник.
- Целевой запрос - запрос, которому принадлежит целевой оператор.
- Запрос-источник - запрос виртуального источника, который встраивается.
- Оператор-источник - оператор запроса-источника, который встраивается.
Есть два режима прямого внедрения, я не придумал ничего лучше, чем назвать их Режим 1 и Режим 2.
Режим 1 применяется, когда целевой оператор использует только один источник (виртуальный), т.е. не имеет никаких соединений (в т.ч. картезианских). В этом случае должны выполняться следующие критерии:
- Запрос-источник может содержать ОБЪЕДИНИТЬ, если целевой оператор не содержит свертку*, не содержит ВЫБРАТЬ ПЕРВЫЕ, а целевой запрос в секциях УПОРЯДОЧИТЬ, ИТОГИ и ИНДЕКСИРОВАТЬ использует только имена колонок (не использует выражения полей).
- Запрос-источник может содержать свертку* в любых своих операторах, если целевой оператор не содержит свертку*, не содержит отбора, а целевой запрос в секциях УПОРЯДОЧИТЬ, ИТОГИ и ИНДЕКСИРОВАТЬ использует только имена колонок.
- Запрос-источник не содержит ВЫБРАТЬ ПЕРВЫЕ ни в одном из своих операторов.
- Модификатор запроса-источника ВЫБРАТЬ РАЗРЕШЕННЫЕ разрешен, если целевой запрос также имеет этот модификатор, либо если целевой запрос содержит только 1 оператор (в этом случае модификатор будет перенесен в целевой запрос).
- Если запрос-источник содержит хотя бы одно ОБЪЕДИНИТЬ (без ВСЕ) или целевой оператор содержит ВЫБРАТЬ РАЗЛИЧНЫЕ и запрос-источник содержит объединение (любого типа), то целевой оператор либо должен быть первым в запросе, либо ему должны предшествовать только операторы ОБЪЕДИНИТЬ (без ВСЕ).
Режим 2 применяется, когда целевой оператор содержит 2 и более источников. В этом случае должны выполняться следующие критерии:
- Запрос-источник не содержит ОБЪЕДИНИТЬ (т.е. имеет только 1 оператор).
- Оператор-источник использует хотя бы 1 таблицу (т.е. имеет непустую секцию ИЗ).
- Оператор-источник не содержит свертку*.
- Оператор-источник не содержит ВЫБРАТЬ ПЕРВЫЕ.
- Модификатор запроса-источника ВЫБРАТЬ РАЗРЕШЕННЫЕ разрешен, только если целевой запрос также имеет этот модификатор.
*) Под сверткой понимается: наличие группировок, использование агрегатных функций (даже без группировок), использование ВЫБРАТЬ РАЗЛИЧНЫЕ (т.к. это частный случай группировки).
В запросе-источнике игнорируются: ДЛЯ ИЗМЕНЕНИЯ, УПОРЯДОЧИТЬ, ИТОГИ, ИНДЕКСИРОВАТЬ, а также любые элементы компоновки данных (всё, что в тексте запроса пишется в фигурных скобках).
Хотя упорядочивание и игнорируется, но оно, тем не менее, может пригодиться в запросе виртуального источника. Если источник не отвечает критериям прямого внедрения, он будет встроен как временная таблица. А в этом случае секция УПОРЯДОЧИТЬ ПО будет автоматически конвертирована в ИНДЕКСИРОВАТЬ ПО. Это такой хитрый способ задать индексирование там, где оно не разрешено правилами языка запросов.
Нет ограничений на использование виртуальным источником своих собственных временных таблиц. Все они будут корректно импортированы в конечный запрос. Даже те, которые находятся в менеджере временных таблиц - для них будут созданы запросы создания временной таблицы из параметра, а их данные будут включены в параметры конечного запроса.
Здесь я не уделяю внимания вопросам формального описания интерфейсов виртуальных источников. Но на практике интерфейс каждого виртуального источника должен быть описан: имя источника, имена и типы колонок, имена и типы параметров, а также все важные ограничения и условия применимости. Например, гарантируется ли прямое внедрение источника или он при некоторых условиях может превратиться во временную таблицу - важное условие с точки зрения эффективности конечного запроса.
В данном примере я использовал два типа виртуальных источников: первые два были реализованы как простые тексты запросов, а третий был реализован как экземпляр процессора. Вообще процессор поддерживает следующие способы реализации виртуальных источников:
- Текст запроса
- Объект Запрос. Этот может содержать, кроме текста запроса, еще и собственные внутренние параметры, и даже использовать временные таблицы из собственного менеджера временных таблиц.
- Объект ТаблицаЗначений или РезультатЗапроса. Этот всегда встраивается в виде временной таблицы, создаваемой из параметра (соответствующий параметр добавляется автоматически).
- ОбработкаОбъект.ПроцессорСхемыЗапроса. Экземпляр должен быть подготовлен к финальному вызову метода ПолучитьКонечныйЗапрос(). Причем в данном случае применяется "ленивый" подход: этот метод будет вызван в самый последний момент, при формировании верхнеуровневого конечного запроса.
Кстати, благодаря ленивому встраиванию виртуальных источников можно использовать следующий подход. Какая-нибудь функция может возвращать экземпляр процессора, начиненный сразу целой библиотекой виртуальных источников (хоть сотней их). Останется только приделать к нему запрос. В итоге будут обработаны только те виртуальные источники, которые реально используются в запросе.
Пример 11
Виртуальные источники не были бы полноценными, если бы не могли иметь параметров.
Пора сменить тематику примеров с транспорта на что-нибудь финансовое. Возьмем для примера регистр сведений КурсыВалют. Мы хотим виртуальный источник, который будет давать нам курсы валют на заданную дату с заданным отбором по валютам.
Определим его следующим образом:
ИсточникКурсыВалют =
"ВЫБРАТЬ
| КурсыВалют.Период КАК Период,
| КурсыВалют.Валюта КАК Валюта,
| КурсыВалют.Курс КАК Курс
|ИЗ
| РегистрСведений.КурсыВалют.СрезПоследних(&Дата, &Отбор) КАК КурсыВалют";
Источник имеет параметры Дата и Отбор, которые мы декларируем в его интерфейсе. Это будут его внешние параметры.
Теперь используем наш виртуальный источник КурсыВалют в запросе:
ТекстЗапроса =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Период КАК Период,
| Продажи.СуммаВал КАК СуммаВал,
| ВЫРАЗИТЬ(Продажи.СуммаВал * КурсыВалют.Курс КАК Число(15, 2)) КАК Сумма
|ИЗ
| РегистрСведений.Продажи КАК Продажи
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ КурсыВалют КАК КурсыВалют
| ПО Продажи.Валюта = КурсыВалют.Валюта";
Запрос = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьИсточник("КурсыВалют", ИсточникКурсыВалют)
.ПараметрИсточника("Дата", "&ДатаКурса")
.ПараметрИсточника("Отбор", "Валюта В (ВЫБРАТЬ NULL)")
.ВложенныйВыбрать().Различные().ИзТаблицы("РегистрСведений.Продажи").Поле("Валюта")
.УстановитьТекстЗапроса(ТекстЗапроса)
.ПолучитьКонечныйЗапрос();
К сожалению, синтаксис языка запросов не позволяет задать параметры виртуального источника прямо в тексте запроса, это разрешено только для настоящих виртуальных таблиц. Поэтому параметры задаются при помощи метода ПараметрИсточника(), сразу же после вызова УстановитьИсточник(). В данном примере значения параметров заданы в виде выражений.
- Параметр Дата задан простым выражением "&ДатаКурса", которое ссылается уже на параметр нашего прикладного запроса.
- Параметр Отбор задан выражением, содержащим вложенный запрос. И, как видим, в этом контексте процессор также поддерживает динамическое конструирование запроса (хотя можно было задать его и просто текстом, но захотелось повыпендриваться).
И вот такой мы получаем конечный запрос:
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Период КАК Период,
| Продажи.СуммаВал КАК СуммаВал,
| ВЫРАЗИТЬ(Продажи.СуммаВал * КурсыВалют.Курс КАК ЧИСЛО(15, 2)) КАК Сумма
|ИЗ
| РегистрСведений.Продажи КАК Продажи
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(
| &ДатаКурса,
| Валюта В
| (ВЫБРАТЬ РАЗЛИЧНЫЕ
| РегистрСведенийПродажи.Валюта КАК Валюта
| ИЗ
| РегистрСведений.Продажи КАК РегистрСведенийПродажи)) КАК КурсыВалют
| ПО (Продажи.Валюта = КурсыВалют.Валюта)";
Отмечу здесь, что виртуальный источник может иметь также и внутренние параметры. В этом случае он не может быть реализован как просто текст запроса, он должен быть либо объектом Запрос, в котором установлены значения всех внутренних параметров, либо экземпляром процессора, также с установленными значениями внутренних параметров. Внутренние параметры считаются деталями реализации и не должны упоминаться в описании интерфейса. Но можно описать параметр как внешний, и при этом задать его как внутренний - в этом случае внутреннее значение будет считаться значением параметра по умолчанию.
Пример 12
В прошлом примере значения параметров были заданы контекстно-независимыми выражениями. Но выражения могут быть и контекстно-зависимыми, т.е. использовать поля таблиц целевого оператора. С этим связан ряд особенностей.
Определим вот такой виртуальный источник:
ИсточникКурсыВалют =
"ВЫБРАТЬ
| КурсыВалют.Период КАК Период,
| КурсыВалют.Валюта КАК Валюта,
| КурсыВалют.Курс КАК Курс
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
|ГДЕ
| КурсыВалют.Валюта = &Валюта
| И КурсыВалют.Период = НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)";
И используем его следующим образом:
ТекстЗапроса =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Период КАК Период,
| Продажи.СуммаВал КАК СуммаВал,
| ВЫРАЗИТЬ(Продажи.СуммаВал * КурсыВалют.Курс КАК Число(15, 2)) КАК Сумма
|ИЗ
| РегистрСведений.Продажи КАК Продажи
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ КурсыВалют КАК КурсыВалют
| ПО ИСТИНА";
Запрос = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьИсточник("КурсыВалют", ИсточникКурсыВалют)
.ПараметрИсточника("Дата", "Продажи.Период")
.ПараметрИсточника("Валюта", "Продажи.Валюта")
.УстановитьТекстЗапроса(ТекстЗапроса)
.ПолучитьКонечныйЗапрос();
Здесь мы задали значения параметров в виде контекстно-зависимых выражений. В результате получим такой конечный запрос:
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Период КАК Период,
| Продажи.СуммаВал КАК СуммаВал,
| ВЫРАЗИТЬ(Продажи.СуммаВал * КурсыВалют.Курс КАК ЧИСЛО(15, 2)) КАК Сумма
|ИЗ
| РегистрСведений.Продажи КАК Продажи
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
| ПО (КурсыВалют.Валюта = Продажи.Валюта
| И КурсыВалют.Период = НАЧАЛОПЕРИОДА(Продажи.Период, ДЕНЬ))";
Как видим, отбор превратился в условие соединения, это нормально. Но использование контекстно-зависимых выражений в параметрах имеет ряд ограничений.
- В запросе виртуального источника такие параметры не должны использоваться во вложенном запросе (который в секции "ИЗ"), т.к. по правилам языка запросов он изолирован от пространства имен внешнего контекста. Произойдет ошибка.
- Такие параметры не должны использоваться в параметрах виртуальных таблиц (т.к. по сути этот тот же вложенный запрос).
- Если виртуальный источник будет встроен как временная таблица, использование таких параметров также приведет к ошибке, т.к. запрос создания временной таблицы будет вне контекста целевого оператора.
Для таких параметров в описании интерфейса виртуального источника должен быть явно прописан запрет на использование контекстно-зависимых выражений.
Пример 13
Сделаем уже нормальный источник КурсыВалют, который может давать нам курсы одновременно на множество разных дат и разных валют.
ИсточникКурсыВалют =
"ВЫБРАТЬ
| КурсыВалютКлючи.Период КАК Период,
| КурсыВалютКлючи.Валюта КАК Валюта,
| КурсыВалют.Курс КАК Курс
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
| ВЫБРАТЬ
| ТаблицаОтбора.Валюта КАК Валюта,
| ТаблицаОтбора.Период КАК Период,
| МАКСИМУМ(КурсыВалют.Период) КАК ПериодКлюча
| ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаОтбора КАК ТаблицаОтбора
| ПО КурсыВалют.Валюта = ТаблицаОтбора.Валюта
| И КурсыВалют.Период <= ТаблицаОтбора.Период
| СГРУППИРОВАТЬ ПО
| ТаблицаОтбора.Валюта,
| ТаблицаОтбора.Период
| ) КАК КурсыВалютКлючи
| ПО КурсыВалют.Период = КурсыВалютКлючи.ПериодКлюча
| И КурсыВалют.Валюта = КурсыВалютКлючи.Валюта";
Видим, что в запросе появилась ТаблицаОтбора. Это тоже параметр виртуального источника, но на этот раз табличного типа. И вот как мы его используем:
ТекстЗапроса =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Период КАК Период,
| Продажи.СуммаВал КАК СуммаВал,
| ВЫРАЗИТЬ(Продажи.СуммаВал * КурсыВалют.Курс КАК Число(15, 2)) КАК Сумма
|ИЗ
| РегистрСведений.Продажи КАК Продажи
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ КурсыВалют КАК КурсыВалют
| ПО Продажи.Валюта = КурсыВалют.Валюта
| И Продажи.Период = КурсыВалют.Период";
Запрос = Обработки.ПроцессорСхемыЗапроса.Создать()
.УстановитьТекстЗапроса(ТекстЗапроса)
.УстановитьИсточник("КурсыВалют", ИсточникКурсыВалют)
.ПараметрИсточника("ТаблицаОтбора",
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Продажи.Период КАК Период,
| Продажи.Валюта КАК Валюта
|ИЗ
| РегистрСведений.Продажи КАК Продажи
|УПОРЯДОЧИТЬ ПО
| Период,
| Валюта")
.ПолучитьКонечныйЗапрос();
В качестве значения параметра мы передали текст запроса. Но это не единственно возможный вариант. На самом деле, для виртуального источника табличный параметр является… тоже виртуальным источником. Называется "виртуальный источник 2-го порядка". И может быть задан любым типом виртуального источника.
В результате получим вот такой конечный запрос:
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Продажи.Период КАК Период,
| Продажи.Валюта КАК Валюта
|ПОМЕСТИТЬ ТаблицаОтбора_1
|ИЗ
| РегистрСведений.Продажи КАК Продажи
|
|ИНДЕКСИРОВАТЬ ПО
| Период,
| Валюта
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Валюта КАК Валюта,
| Продажи.Период КАК Период,
| Продажи.СуммаВал КАК СуммаВал,
| ВЫРАЗИТЬ(Продажи.СуммаВал * КурсыВалют.Курс КАК ЧИСЛО(15, 2)) КАК Сумма
|ИЗ
| РегистрСведений.Продажи КАК Продажи
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
| ТаблицаОтбора.Валюта КАК Валюта,
| ТаблицаОтбора.Период КАК Период,
| МАКСИМУМ(КурсыВалют.Период) КАК ПериодКлюча
| ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаОтбора_1 КАК ТаблицаОтбора
| ПО (КурсыВалют.Валюта = ТаблицаОтбора.Валюта
| И КурсыВалют.Период <= ТаблицаОтбора.Период)
|
| СГРУППИРОВАТЬ ПО
| ТаблицаОтбора.Валюта,
| ТаблицаОтбора.Период) КАК КурсыВалютКлючи
| ПО (КурсыВалют.Период = КурсыВалютКлючи.ПериодКлюча
| И КурсыВалют.Валюта = КурсыВалютКлючи.Валюта)
| ПО (Продажи.Валюта = КурсыВалютКлючи.Валюта
| И Продажи.Период = КурсыВалютКлючи.Период)
|;
|
|////////////////////////////////////////////////////////////////////////////////
|УНИЧТОЖИТЬ ТаблицаОтбора_1";
Здесь мы видим пример того, как виртуальный источник встроился в виде временной таблицы (это не потому, что он передан как параметр, а потому, что содержит ВЫБРАТЬ РАЗЛИЧНЫЕ, см. критерии прямого внедрения). И видим, как УПОРЯДОЧИТЬ ПО превратилось в ИНДЕКСИРОВАТЬ ПО.
При использовании текста запроса в качестве значения табличного параметра есть нюанс. Этот запрос может обращаться к временным таблицам и параметрам главного запроса и, таким образом, быть зависимым от контекста главного запроса. Такой запрос-параметр называется локальным. Он встроится в запрос виртуального источника в неизменном виде, и в столь же неизменном виде вернется в главный запрос, уже в составе виртуального источника.
Но этот запрос может быть также и независимым от контекста главного запроса, он может объявлять и использовать свои собственные временные таблицы, имена которых могут по чистой случайности совпасть с именами временных таблиц главного запроса. Такой запрос называется нелокальным, при встраивании он подвергнется локализации, все конфликтующие имена будут изменены на бесконфликтные.
Процессору важно понимать, имеет ли он дело с локальным или с нелокальным запросом в табличном параметре. По умолчанию запрос, заданный обычным текстом, считается локальным. Чтобы объявить его нелокальным, у метода ПараметрИсточника есть опциональный параметр ЗначениеЛокально, который нужно установить в Ложь. Если же табличный параметр задан как объект Запрос или экземпляр процессора, то он всегда считается нелокальным.
Еще одно ограничение: в локальном запросе-параметре нельзя использовать виртуальные источники главного запроса. Они просто не будут встроены и так и останутся неизвестными временными таблицами. Эту задачу мне пока не удалось корректно решить. При необходимости нужно в главном запросе явно создать временную таблицу из виртуального источника, и ее использовать в запросе-параметре.
Виртуальные источники как альтернатива динамическому конструированию
Впрочем, даже если вас не волнуют вопросы чистой архитектуры (да-да, где 1С и где архитектура), виртуальным источникам можно найти применение. Например, во многих случаях они могут служить альтернативой динамическому "допиливанию" запросов.
Вспомним примеры структурной обработки, где мы обвешивали базовый запрос отборами, сортировками группировками и т.п. А можно взять базовый запрос и использовать его как виртуальный источник в прикладном запросе. Получится тот же результат.
Опции процессора схемы запроса
У процессора есть несколько опций, сделанных в виде реквизитов типа Булево. Все они сейчас устанавливаются по умолчанию в состояние Истина.
- АвтодобавлениеДоступныхПолей
При динамическом конструировании запроса, использующего необъявленные временные таблицы (имеются в виду источники типа ОписаниеВременнойТаблицыСхемыЗапроса, к ним относятся в т.ч. имена виртуальных источников), возникает проблема: чтобы использовать поле такой таблицы в выражении, его необходимо сначала добавить в доступные поля таблицы, иначе схема запроса выдаст ошибку. Делать это каждый раз в явном виде довольно неудобно. При включенном режиме автодобавления доступных полей процессор анализирует новые выражения и автоматически добавляет используемые в них поля в доступные поля таблиц. Это несколько увеличивает время выполнения за счет дополнительного парсинга. Если вы работаете только с известными таблицами, этот режим можно отключить.
- ТипизацияПолейВременныхТаблиц
При использовании метода ВыбратьИзПараметра() процессор автоматически типизирует поля временной таблицы через конструкцию ВЫРАЗИТЬ. Работа этого режима была продемонстрирована в примере 7. Если этого не требуется делать по умолчанию, режим можно отключить.
- КонкатенацияУсловийСоединений
Если условие соединения содержит несколько условий, соединенных операцией "И", то при установке текста запроса схема запроса автоматически раскладывает такое соединение на несколько элементов СоединениеИсточникаЗапросаСхемыЗапроса. Работать с такой моделью не всегда удобно, поэтому предусмотрен режим конкатенации условий соединений, при котором условия соединения автоматически сливаются в одно выражение.
Но есть причина, по которой этот режим категорически не рекомендуется отключать. В схеме запроса есть глюк (будет описан ниже), который не удалось обойти другим способом, кроме как включением режима конкатенации.
- ИзолированнаяОбработкаВложенныхЗапросов
Данный режим включается для обхода еще одного глюка схемы запроса, связанного с обработкой вложенных запросов в секции "ИЗ" (не путать с вложенными запросами выражений). В этом режиме вложенный запрос обрабатывается в отдельном экземпляре схемы запроса, несмотря на то, что номинально схема позволяет работать с ним напрямую. Это несколько замедляет обработку вложенного запроса, т.к. приходится перегонять его в текст, потом в схему, а потом все в обратном порядке. Но отключать этот режим не рекомендуется.
Выявленные ошибки схемы запроса
В ходе работы над процессором была выявлена масса ошибок и логических нестыковок в работе схемы запроса, о многих их которых я раньше даже не догадывался и нигде не встречал их описания. Пользуясь случаем, опишу здесь все известные мне проблемы и пути их обхода.
Кстати, многие их этих ошибок воспроизводятся в интерактивном конструкторе запросов в конфигураторе.
1. Схема самовольно добавляет соединения при добавлении новых источников в оператор.
Это давно известная проблема: схема запроса повторяет стиль работы конструктора запросов, и отключение этого поведения не предусмотрено. Путь обхода также известен: перед добавлением источника запомнить количество соединений у каждого из имеющихся источников, а после - удалить появившиеся лишние соединения (благо, они всегда добавляются в конец коллекции). При использовании процессора вам не нужно об этом беспокоиться, он делает это сам. Что, конечно же, увеличивает время выполнения, хоть и незначительно.
2. Невозможно добавить доступное поле составного типа.
В объекте ОписаниеВременнойТаблицыСхемыЗапроса мы можем добавлять доступные поля. Но в коллекции ДоступныеПоляСхемыЗапроса метод Добавить(Имя, Тип) ожидает 2-м параметром, внезапно, объект Тип, вместо ОписаниеТипов (хотя в самом поле тип представлен объектом ОписаниеТипов). Очевидная ошибка проектирования, но она существует уже столько, сколько существует схема запроса. В результате нет возможности создать доступное поле составного типа. Более того, нельзя даже корректно создать поле типа Строка, т.к. получится строка неограниченной длины, что далее приведет к ошибкам сравнения.
По этой причине процессор создает нетипизированные поля вместо полей составного и строкового типа. Это может приводить к ошибкам, если поле участвует в разыменовании. В этом случае следует модифицировать запрос, выполнив явное приведение типа через ВЫРАЗИТЬ.
3. Потеря соединений при модификации вложенного запроса.
Если напрямую модифицировать вложенный запрос, содержащийся в объекте ВложенныйЗапросСхемыЗапроса, это в ряде случаев приводит к потере соединений этого источника с другими источниками (конкретные условия не выяснены). Обойти этот баг можно, обрабатывая вложенный запрос в отдельном экземпляре схемы запроса. Это увеличивает время выполнения.
4. Потеря полей выборки при замене таблицы на доступную временную таблицу.
В коллекции ИсточникиСхемыЗапроса есть метод Заменить, позволяющий в источнике типа ТаблицаСхемыЗапроса заменить одну доступную таблицу на другую. Замена на таблицы ИБ проходит как положено. А вот при попытке заменить на доступную временную таблицу, пускай и с полностью идентичной структурой полей, происходит потеря всех полей в коллекции ВыбираемыеПоля. Способ обхода пока не искал, но напрашивается сохранение всех полей выборки с последующим их восстановлением. Хотя там не все так просто, если запрос содержит группировки или участвует в объединении. В текущей версии процессор просто выдает исключение при попытке выполнить такую замену.
5. Потеря полей выборки при замене имени таблицы в описании временной таблицы.
Баг похож на предыдущий, но связан с использованием объекта ОписаниеВременнойТаблицыСхемыЗапроса. Если динамически сконструировать запрос создания временной таблицы из параметра (то, что делает метод процессора ВыбратьИзПараметра), переместить этот запрос в нужную позицию в пакете, а затем попытаться изменить свойство ИмяТаблицы (например, с "&ВТ" на "&ВТ_1"), происходит потеря всех полей в коллекции ВыбираемыеПоля.
Путь обхода: перед изменением свойства ИмяТаблицы нужно пересоздать этот запрос из текста, т.е. выполнить ЗапросСхемы.УстановитьТекстЗапроса(ЗапросСхемы.ПолучитьТекстЗапроса()). После этого изменение имени проходит без проблем. Разумеется, это увеличивает время выполнения.
6. Ошибка при добавлении соединения
Если условие соединения содержит несколько условий, соединенных операцией "И", то при установке текста запроса схема запроса автоматически раскладывает такое соединение на несколько элементов СоединениеИсточникаЗапросаСхемыЗапроса. Образуется то, что называется "группа соединений". Но вот создать такую же конструкцию динамически не всегда возможно.
Первое соединение группы добавится без проблем, а вот при добавлении 2-го схема запроса выдаст ошибку "Противоречивая связь". Это происходит в случае, когда тип 1-го соединения отличается от ЛевоеВнешнее. Предполагаемая причина: все новые соединения создаются по умолчанию с типом ЛевоеВнешнее, тип соединения можно изменить только после его создания. А тип группы соединений определяется типом 1-го соединения. Понятно, что когда мы в группу соединений с типом Внутреннее пытаемся добавить ЛевоеВнешнее, схема запроса видит противоречие. Но при этом нет никакого способа указать тип соединения непосредственно в момент его добавления.
Напрашивался такой путь обхода: перед добавлением нового соединения установить тип 1-го соединения группы в ЛевоеВнешнее, а после добавления - восстановить исходный тип. Но, как показали тесты, это работает от случая к случаю. До исправления бага рекомендуется всегда включать режим КонкатенацияУсловийСоединений. Что, снова-таки, увеличивает время выполнения.
7. Ошибки при переименовании псевдонима таблицы.
Очень много глюков схемы запроса вылезает при переименовании псевдонимов таблиц. Исчезают выражения в выбираемых полях, отборах, условиях соединений. Искажаются вложенные запросы выражений. В силу этого пришлось использовать более сложную технику прямого внедрения виртуального источника, исключающую переименование псевдонимов. Она получилась более многошаговой и требующей больше парсинга. Время выполнения… ну, вы поняли.
Если 1С когда-нибудь исправит все эти баги, можно будет сократить затраты времени на внедрение виртуального источника на 10-15% а при использовании вложенных запросов - и того больше. По тем багам, где удалось выявить четкие условия воспроизведения, я отправил багрепорты в 1С - будем ждать.
Несколько слов о производительности
Замеры производительности выполнялись на машине с такими параметрами: Intel(R) Core(TM) i5-10500 CPU @ 3.10GHz, 16,0 ГБ ОЗУ, Win 10, 1С 8.3.27.
Систематизированного нагрузочного тестирования пока не выполнялось. Прогон набора юнит-тестов показывает время выполнения отдельного теста в диапазоне 1-80 мс (без режима отладки разумеется). Разброс обусловлен разной сложностью тестов. На основе имеющейся статистики можно утверждать следующее:
- Примеры со структурной обработкой (без вложенных запросов, без виртуальных источников) - единицы мс.
- Примеры с виртуальными источниками, без вложенных запросов - первые десятки мс.
- Примеры, сочетающие виртуальные источники с вложенными запросами - до 80 мс и выше.
Пока выглядит приемлемо, по сравнению с временем выполнения самого запроса при сколь-нибудь существенном объеме данных. В случае активного использования виртуальных источников можно подумать в сторону кэширования при помощи общих модулей с повторным использованием (разумеется, речь о кэшировании текстов запросов, а не результатов).
Форма поставки и требования к платформе
Процессор поставляется как конфигурация поставки. Конфигурация содержит только одну обработку ПроцессорСхемыЗапроса. Обработка полностью автономна, никаких зависимостей от БСП или чего-то подобного.
Процессор использует регулярные выражения, поэтому теоретически минимальная версия платформы - 8.3.23. Но практически не тестировался ниже 8.3.27.
Вступайте в нашу телеграмм-группу Инфостарт
