Объектная модель запроса "Схема запроса" 2

06.12.23

Разработка - Запросы

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

Мне лично очень нравится статья - //infostart.ru/1c/articles/307045/

Также я использовал принципы из библиотеки тестирования YAXUNIT

В комментариях к оригиналу - возникли обоснованные сомнения в рациональности использования объектной схемы запроса, я надеюсь не создавать повод для очередных споров, а попытаться создать более "Юзабельный" пример.

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

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

Мне понадобятся 2 общих модуля, создающих более удобную "обертку" вокруг типовых функций построения схемы запроса.

1. Общий модуль - ТекучийКод - Серверный, ПовторноеИспользование - на время сеанса.
(Совсем простенький, чтобы сохранять текущий контекст)

Функция Контекст(Строка_УИД) Экспорт
	Возврат Новый Структура();
КонецФункции

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

 
 Общий модуль ОбъектЗапрос

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

ТестЗапрос = Новый Запрос;
ТестЗапрос.Текст = 
"ВЫБРАТЬ
|	Товары.Ссылка КАК Номенклатура
|ИЗ
|	Справочник.Номенклатура КАК Товары";

ОбъектЗапрос.СхемаЗапроса(ТестЗапрос)
	.Пакет(0).Выборка(0)
	.Разрешенные()
	.ДобавитьЛевоеСоединение("Товары", "РегистрНакопления.Закупки.Обороты", "Закупки", "Закупки.Номенклатура = Товары.Ссылка")
	.УстановитьПараметрыОбороты("&Начало", "&Окончание", "Месяц")
		.ДобавитьПоле("Закупки.Период", "Период")
		.ДобавитьПоле("ЕСТЬNULL(Закупки.СуммаОборот, 0)", "СуммаЗакупок")
		.ДобавитьПоле("0", "СуммаПродаж")
		.Поместить("ТаблицаОбороты")
		.ДобавитьГде("НЕ Товары.ЭтоГруппа")
		
	.ДобавитьОбъединение("Справочник.Номенклатура", "Товары")
		.Различные().Первые(100)                                        
	.ДобавитьЛевоеСоединение("Товары", "РегистрНакопления.Продажи.Обороты", "Продажи", "Продажи.Номенклатура = Товары.Ссылка")
	.УстановитьПараметрыОбороты("&Начало", "&Окончание", "Месяц")  
		.ДобавитьПоле("Товары.Ссылка", "Номенклатура") // Объединяется по псевдониму
		.ДобавитьПоле("Продажи.Период", "Период")
		.ДобавитьПоле("0", "СуммаЗакупок")
		.ДобавитьПоле("ЕСТЬNULL(Продажи.СуммаОборот, 0)", "СуммаПродаж")
		.ДобавитьГде("НЕ Товары.ЭтоГруппа")
	    .Индекс("Номенклатура, Период")          
		
	.ДобавитьПакет("ТаблицаОбороты", "ТаблицаОбороты")
		.ДобавитьПоле("ТаблицаОбороты.Номенклатура", "Номенклатура")
		.ДобавитьПоле("ТаблицаОбороты.Период", 		 "Период")
		.ДобавитьПоле("СУММА(ТаблицаОбороты.СуммаЗакупок)", "СуммаЗакупок")
		.ДобавитьПоле("СУММА(ТаблицаОбороты.СуммаПродаж)", "СуммаПродаж")
		.Группировка("ТаблицаОбороты.Номенклатура, ТаблицаОбороты.Период")
		.ДобавитьГде("СУММА(ТаблицаОбороты.СуммаЗакупок) > 0") // Говорят схема запроса сама определит раздел "ИМЕЮЩИЕ"? 
		.Порядок("Номенклатура, Период")
		.ИтогиОбщие()
		.ИтогиПо("2, 3")
		.ИтогТолькоИерархия("Номенклатура")
	.Уничтожить("ТаблицаОбороты")
.Завершить();

Обратите внимание, что форматирование отступов - произвольное, я оформил так, как удобнее читать лично мне, надеюсь и вам тоже.

Идеи для улучшения:

В данный момент селекторы реализованы через индексы, в случае с большими запросами - это может быть не слишком удобно. Кажется, лучше было бы реализовать поиск по псевдонимам, это должно быть более читаемо, когда мы будем анализировать код изменяющий запрос. Нам ведь важно, что именно изменилось? Сравните варианты:

Выборка(0).ДобавитьПоле("Закупки.Период", "Период") 

Выборка("Товары").ДобавитьПоле("Закупки.Период", "Период")

Да и в целом код, наверное, можно еще причесать, у меня, к сожалению, нет много времени на это, надо работать :) 

Надеюсь, что это станет хорошей "точкой отсчета", если вы захотите использовать этот функционал.

запрос объектная модель запроса модификация запроса текучий интерфейс

См. также

Infostart Toolkit: Инструменты разработчика 1С 8.3 на управляемых формах

Инструментарий разработчика Роли и права Запросы СКД Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Конфигурации 1cv8 Платные (руб)

Набор инструментов программиста и специалиста 1С для всех конфигураций на управляемых формах. В состав входят инструменты: Консоль запросов, Консоль СКД, Консоль кода, Редактор объекта, Анализ прав доступа, Метаданные, Поиск ссылок, Сравнение объектов, Все функции, Подписки на события и др. Редактор запросов и кода с раскраской и контекстной подсказкой. Доработанный конструктор запросов тонкого клиента. Продукт хорошо оптимизирован и обладает самым широким функционалом среди всех инструментов, представленных на рынке.

10000 руб.

02.09.2020    124401    680    389    

731

Пропорциональное распределение в запросе с использованием АвтоНомерЗаписи()

Запросы Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Часто поступают задачи по произвольному распределению общих сумм. После распределения иногда пропадают копейки. Суть решения добавить АвтоНомерЗаписи() в ВТ распределения, и далее используя функции МАКСИМУМ или МИНИМУМ можем положить разницу копеек в первую или последнюю строку знаменателя распределения.

11.04.2024    1659    andrey_sag    5    

23

Для чего используют конструкцию запроса "ГДЕ ЛОЖЬ" в СКД на примере конфигурации 1С:ERP

Запросы СКД Платформа 1С v8.3 Запросы Система компоновки данных 1С:ERP Управление предприятием 2 Бесплатно (free)

В типовых конфигурациях разработчики компании 1С иногда используют в отчетах, построенных на СКД, такую конструкцию, как "ГДЕ ЛОЖЬ". Такая конструкция говорит о том, что данные в запросе не будут получены совсем. Для чего же нужен тогда запрос?

13.02.2024    5950    KawaNoNeko    23    

25

Набор-объект для СКД по тексту или запросу

Запросы СКД Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Абонемент ($m)

Есть список полей в виде текста, или запрос - закидываем в набор СКД.

1 стартмани

31.01.2024    2118    2    Yashazz    0    

30

Запрос 1С copilot

Инструментарий разработчика Запросы Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Абонемент ($m)

Пишем на человеческом языке, что нам надо, и получаем текст запроса на языке 1С. Используются большие языковые модели (LLM GPT) от OpenAI или Яндекс на выбор.

5 стартмани

15.01.2024    6553    31    mkalimulin    25    

51

PrintWizard: поддержка представлений ЗУП в конструкторе

Инструментарий разработчика Запросы Платформа 1С v8.3 Бесплатно (free)

Одной из интересных задач, стоящих в процессе разработки, была поддержка механизма представлений в ЗУП. Но не просто возможность исполнения запросов с ними. Основная проблема была в том, чтобы с ними было удобно работать, а именно: создавать, модифицировать и отлаживать. Кратко о том, что в итоге получилось...

14.12.2023    1863    vandalsvq    7    

29

Начните уже использовать хранилище запросов

HighLoad оптимизация Запросы

Очень немногие из тех, кто занимается поддержкой MS SQL, работают с хранилищем запросов. А ведь хранилище запросов – это очень удобный, мощный и, главное, бесплатный инструмент, позволяющий быстро найти и локализовать проблему производительности и потребления ресурсов запросами. В статье расскажем о том, как использовать хранилище запросов в MS SQL и какие плюсы и минусы у него есть.

11.10.2023    16524    skovpin_sa    14    

101
Комментарии
Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. infosoft-v 875 06.12.23 09:40 Сейчас в теме
Отличная статья, спасибо.
Ссылка указанная в начале статьи не работает
10. Неопределено 90 07.12.23 06:27 Сейчас в теме
2. frkbvfnjh 787 06.12.23 10:07 Сейчас в теме
Очень интересные подходы :) Возвращать объект общего модуля для краткости записи и использования собственного "диалекта" синтаксиса и хранить контекст используя механизм повторного использования. Огонь! Только модуль наверное должен называться ТекущийКод, а не ТекучийКод, или он все таки текучий?
9. German 413 07.12.23 05:49 Сейчас в теме
(2) Необычный подход. Вот только работать будет в моменте для 1 сеанса
user1923546; +1 Ответить
16. user1923546 46 07.12.23 12:21 Сейчас в теме
(9) Вот про это, можно поподробнее? Тут есть проблема которую я не понимаю?
Сеанс ведь существует в любом случае, даже для регламентного задания... а при вызове ОбъектЗапрос.СхемаЗапроса(ТестЗапрос) - контекст очищается...

Вы предполагаете, что повторное использование на время сеанса - не изолирует получение контекста между сеансами?
То есть... ТекучийКод.Контекст("ОбъектныеЗапросы") - вызванный "одновременно" из разных сеансов может вызвать сбой?
Я предполагал, что разные сеансы получат разные структуры... если это не так, то для получения контекста следует использовать конструкцию вроде...?

ТекучийКод.Контекст("ОбъектныеЗапросы"+НомерСоединенияИнформационнойБазы());


Меня немного смущает, что такой вариант, вероятно, будет создавать мусор, но структуру контекста можно очищать при вызове функции Завершить() - я предполагаю, что если кто-то начал модифицировать запрос, то он хочет получить результат, а значит вызовет эту функцию непременно.
20. German 413 08.12.23 06:49 Сейчас в теме
(16) Это я затупил что то, изолирует конечно
3. brr 182 06.12.23 10:58 Сейчас в теме
.Пакет(0).Выборка(0)
вот тут было бы не плохо имена задавать, а не индекс. А уже в самом контексте хранить связи между именем и индексом.
4. ImHunter 315 06.12.23 11:46 Сейчас в теме
(2) Текучий:) Fluent, иначе.
(0) Поплюсую... Еще бы пример с модификацией на убирание или замену каких-то составляющих запроса.
6. frkbvfnjh 787 06.12.23 14:20 Сейчас в теме
(4) Да, все таки это Fluent...
5. maksa2005 532 06.12.23 12:27 Сейчас в теме
Мне лично очень нравится статья - //infostart.ru/1c/articles/307045/

не корректная ссылка
7. naf2000 06.12.23 18:01 Сейчас в теме
Linq при помощи синей изоленты
so-quest; +1 Ответить
8. DrZombi 290 06.12.23 18:05 Сейчас в теме
Очень интересно, но ни черта не понял.
Запрос может состоять из многих запросов, чет не нашел об этом упоминания :)


Короче в вашей статье примера кода есть море ограничений в использовании, вы об этом не стали упоминать...

Пишите еще, тема си...ек не развернута...
11. dhurricane 07.12.23 10:26 Сейчас в теме
Лично для меня тема объектной модели запроса по-прежнему остается актуальной. Увидев заголовок статьи сперва обрадовался - очень надеялся, что под капотом Вашей разработки не используется платформенная СхемаЗапроса... но нет, используется. Жаль.
12. dhurricane 07.12.23 10:29 Сейчас в теме
Не хватает "текучести" в описании соединения, на мой взгляд. Удобнее было бы описывать условия соединения отдельными методами построчно, а не одним большим текстом со всеми условиями.
13. user1923546 46 07.12.23 12:08 Сейчас в теме
(12) Да, описание соединения, мне тоже показалось громоздким, особенно на общем фоне... Однако все его элементы должны быть определены одновременно, иначе "остановившись" после вызова очередной функции, вы рискуете получить нежизнеспособный запрос... Зачем создавать дополнительную уязвимость, для пользователя интерфейса?
Я не хочу каждый раз помнить о том, что после условного метода...
.ДобавитьСоединение() - мне НЕОБХОДИМО вызвать еще какой-то метод... например .УсловиеОбъединения()

Методы должны делать то, что вы скорее всего "предполагаете" они делают, исходя из их наименований.

Почему вы считаете, что не следует использовать платформенную СхемаЗапроса? Она вроде предоставляет весь необходимый функционал, для построения полноценного запроса... и вероятно используется "под капотом" той же СКД.
У меня лично были претензии конкретно к ее интерфейсу... давайте я продублирую ссылку на статью на которую я ориентировался.
https://infostart.ru/1c/articles/307045/

К тому же после написания статьи, я наткнулся на похожие публикации, они более объемные и развернутые, и "текучесть" похоже не только мне показалась подходящим приемом для работы с запросами.
https://infostart.ru/1c/articles/1390402/
https://infostart.ru/1c/articles/1871072/

(8) Действительно, без ограничений не обошлось, но это вопрос расширения модуля ОбъектЗапрос там вроде не так уж трудно добавить дополнительные функции если они вам нужны.

Запрос из многих запросов - в моем примере реализовано через функции
.ДобавитьОбъединение() = это дополнительный запрос в пакете
.ДобавитьПакет() = это дополнительный пакет и запрос в нем
.Поместить() = это создание временной таблицы

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

С другой стороны вложенные запросы - на мой взгляд сами по себе выглядят как своеобразный ХАК системы запросов, они сложно читаются и к счастью не так уж часто используются. Лично я чаще всего использую прием вложенного запроса к временной таблице в условии, чтобы получить список для отбора... ну знаете, вот эта штука:
ГДЕ Аналитика1, Аналитика2 В (Выбрать ВТ.ЧетТам, ВТ.ЧетЕще Из ВТ как ВТ)
21. dhurricane 08.12.23 10:14 Сейчас в теме
(13)
Зачем создавать дополнительную уязвимость, для пользователя интерфейса?
Не могу тут давать советы, вероятно Вы правы. Лишь отметил, что здесь тоже частно приходится конструировать запрос, а следовательно от текучей схемы придется вновь вернуться к конструированию строки соединения. Только не понял, почему запрос будет нежизнеспособный?

Почему вы считаете, что не следует использовать платформенную СхемаЗапроса?
Просто неудачный опыт, мне она создавала больше проблем, нежели помогала решать мои задачи. Коротко на вскидку:
- портит исходное форматирование текста;
- добавляет самостоятельно соединения, о которых "не просят";
- самостоятельно добавляет псевдонимы полей;
- "ломает" выражения расширения языка запросов для компоновки данных, добавляя свои псевдонимы и им.
14. DrZombi 290 07.12.23 12:12 Сейчас в теме
(13) Автор конечно молодец, не так много статей описывающих Схему запроса.

Я к примеру столкнулся с такой вещью, что Схема запроса автоматический подставляется "Псевдоним".

Пришлось вставить
	
        //Уберем лишние "Поле"... увы... оно тут лишнее..
	Для Каждого Выражение Из Оператор.ВыраженияОтбораКомпоновкиДанных Цикл 
		Псевдоним = Выражение.Псевдоним;
		Если Лев(Псевдоним,4) = "Поле" Тогда
			Выражение.Псевдоним = "";
		КонецЕсли;
	КонецЦикла;
	
Показать
19. user1923546 46 07.12.23 12:34 Сейчас в теме
(14) Да, поэтому функция ДобавитьПоле() получилась неожиданно сложной.
Она обязательно требует Псевдоним, чтобы было меньше путаницы, а еще пришлось учитывать вариант когда мы добавляем поле ко второму запросу в объединении

Функция ДобавитьПоле(Поле, Псевдоним) Экспорт
	Контекст = ТекучийКод.Контекст("ОбъектныеЗапросы");
	
	// ++ 001 Добавление поля - в объединение: 
	Если Контекст.Пакет.Операторы.Количество() > 1 Тогда
		// Ищем уже сущестующий индекс по псевдониму, в основной выборке.
		Индекс = 0;
		Найден = Ложь;
		Для Каждого Колонка Из Контекст.Пакет.Колонки Цикл
			Если Колонка.Псевдоним = Псевдоним Тогда
				Найден = Истина;
				Прервать;
			КонецЕсли;
			Индекс = Индекс+1;	
		КонецЦикла; 
		
		Выражение = Контекст.Выборка.ВыбираемыеПоля.Добавить(Поле); 
		// Сопоставляем 
		Контекст.Пакет.Колонки[Индекс].Поля.Установить(1, Выражение);
		
		// Если не нашли сопоставление, значит это новое поле
		Если Не Найден Тогда
			Контекст.Пакет.Колонки[Индекс].Псевдоним = Псевдоним;	
		КонецЕсли;
		
		Возврат ОбъектЗапрос;
	КонецЕсли; // -- 001
	
	ИндексНового = Контекст.Выборка.ВыбираемыеПоля.Количество();
	Выражение = Контекст.Выборка.ВыбираемыеПоля.Добавить(Поле);
	
	// Псевдонимы для колонок - указываются на уровне запроса
	Контекст.Пакет.Колонки[ИндексНового].Псевдоним = Псевдоним;  
	
	Возврат ОбъектЗапрос;
КонецФункции  
Показать
15. DrZombi 290 07.12.23 12:15 Сейчас в теме
(13) В общем я где-то на просторах инета нашел функцию, шаблон.

Функция ДобавитьВременнуюТаблицуПоСхемеЗапроса(ТекстЗапроса, ИмяВременнойТаблицы)
	Перем СхемаЗапроса;
	
	СхемаЗапроса = Новый СхемаЗапроса;
	СхемаЗапроса.УстановитьТекстЗапроса(ТекстЗапроса);
	
	//получим последний запрос пакета, к которому будем присоединять таблицу
	ПоследнийЗапросПакета = СхемаЗапроса.ПакетЗапросов[СхемаЗапроса.ПакетЗапросов.Количество()-1];
	ПоследнийЗапросПакета.ТаблицаДляПомещения = ИмяВременнойТаблицы;
	
	//Каждый оператор представляет собой запрос ВЫБРАТЬ.
	Оператор = ПоследнийЗапросПакета.Операторы[0];
	
	// получаем источники оператора ВЫБРАТЬ
	Источники = Оператор.Источники;
	ОсновнойИсточник = Источники[0];
	
	////добавим к источникам наш регистр, укажем его псевдоним
	//НовыйИсточник = Источники.Добавить("Задача.ЗадачаИсполнителя.Предметы", "СОГ_ПредметыЗадач");
	//НовыйИсточник.Соединения.Очистить();
	//
	//// получим псевдоним основном таблицы для формирования условия соединения
	//ПсевдонимОсновнойТаблицы = ОсновнойИсточник.Источник.Псевдоним;
	//
	////Сформируем строку соединения ЗадачаИсполнителя.Ссылка = ПредметыЗадач.Ссылка ЗадачаИсполнителя
	//УсловиеСоединения = ПсевдонимОсновнойТаблицы + ".Ссылка = СОГ_ПредметыЗадач.Ссылка";
	//УсловиеСоединения2 = "(СОГ_ПредметыЗадач.РольПредмета = ЗНАЧЕНИЕ(Перечисление.РолиПредметов.Основной))";
	//
	//// получим соединения основного источника и добавим к нему своё
	//Соединения = ОсновнойИсточник.Соединения;
	//Соединения.Добавить(НовыйИсточник, УсловиеСоединения);
	//Соединения.Добавить(НовыйИсточник, УсловиеСоединения2);
	//
	//// установим тип соединения
	//Соединения[Соединения.Количество()-1].ТипСоединения = ТипСоединенияСхемыЗапроса.ЛевоеВнешнее;	
	
	//// чтобы в динамическом списке стало доступно для вывода поле из нашего регистра, добавим его в запрос
	//ДобавленнеПоле = Оператор.ВыбираемыеПоля.Добавить("СправочникПодразделенияОрганизаций.Расформировано");	// КАК СОГ_ПредметыЗадач_Содержание
	//
	//ПоследняяКолонка = ПоследнийЗапросПакета.Колонки.Количество()-1;
	//Колонка = ПоследнийЗапросПакета.Колонки[ПоследняяКолонка];
	//Колонка.Псевдоним = "Расформировано";
	
	//Уберем лишние "Поле"... увы... оно тут лишнее..
	Для Каждого Выражение Из Оператор.ВыраженияОтбораКомпоновкиДанных Цикл 
		Псевдоним = Выражение.Псевдоним;
		Если Лев(Псевдоним,4) = "Поле" Тогда
			Выражение.Псевдоним = "";
		КонецЕсли;
	КонецЦикла;
	
	//{ЛЕВОЕ СОЕДИНЕНИЕ Задача.ЗадачаИсполнителя.Предметы КАК ПредметыЗадач
	//ПО ЗадачаИсполнителя.Ссылка = ПредметыЗадач.Ссылка
	//	И (ПредметыЗадач.РольПредмета = ЗНАЧЕНИЕ(Перечисление.РолиПредметов.Основной))}
	
	НовыйТекстЗапроса = СхемаЗапроса.ПолучитьТекстЗапроса();
	Возврат НовыйТекстЗапроса;
КонецФункции
Показать
17. user1923546 46 07.12.23 12:25 Сейчас в теме
(15) Оберните текст функции в блок кода. Спасибо! ٩(◕‿◕。)۶
18. DrZombi 290 07.12.23 12:30 Сейчас в теме
22. DemetrKlim 158 11.12.23 09:38 Сейчас в теме
Какая-то дивная "матрешка" получилась) Я СхемойЗапроса пользуюсь часто и с удовольствием. Но не думал, что хотелось бы что-то доработать - пока нравится штатный механизм.
23. Alxby 1141 28.12.23 18:57 Сейчас в теме
Хранить данные в контексте, созданном в модуле повторного использования? Он живет ограниченное время. И окончание этого срока жизни (а значит создание новой, чистой структуры контекста) вполне может произойти между добавлением поля и группировки.))
24. Жолтокнижниг 258 27.02.24 01:30 Сейчас в теме
(23) К сожалению может, но если вы поставили точку останова и пошли курить на 20 минут и база должна быть клиент-серверной.
25. Alxby 1141 27.02.24 09:24 Сейчас в теме
(24)К сожалению, значение в кэше модуля повторного использования может быть удалено в не только в этом случае. ИТС: "... Кроме этого значение будет удалено при нехватке оперативной памяти в рабочем процессе сервера, при перезапуске рабочего процесса и при переключении клиента на другой рабочий процесс....". Вообще говоря, использовать переопределяемый модуль для хранения значения - это не то, для чего этот механизм предназначен. Для целей статьи гораздо проще и надежнее хранить контекст в переменной и передавать его в качестве параметра во все функции.
26. Жолтокнижниг 258 27.02.24 14:36 Сейчас в теме
(25) Согласен, но есть нюанс, оно работает.
Советую посмотреть код, сам контекст не хранится в модуле повторного использования, там хранится адрес временного хранилища.
Сам не посмотрел код, думал такой же подход как в yaxunit.
Да в такой схеме ошибки возможно, но надо тестировать (причем на разных версиях поведение может быть разным). Вероятность ошибок вырастает, если работа со схемой будет перемежаться другим кодом, например, сформировали схему, выполнили запрос/отчет, доработали схему.
Оставьте свое сообщение