Skill v1.0.2
LLM-judged scan90/100~1 modified
version: "1.0.2" name: query-patterns description: "При написании новых запросов 1С и параметров" alwaysApply: false
Паттерны запросов 1С
Ключевой принцип: Каждый запрос к базе — сетевой round-trip. Минимизация количества запросов и объёма возвращаемых данных — приоритет.
Правило 1: НИКОГДА не выполняйте запросы в цикле
Для N итераций — N (латентность сети + время выполнения). 1000 элементов 5 мс = 5 секунд только на ожидание сети.
Стандарт ИТС: «Запросы к базе данных — ограничение на использование запросов в цикле».
Правильно — один запрос + Соответствие для обработки
Запрос = Новый Запрос;Запрос.Текст ="ВЫБРАТЬ| Номенклатура.Ссылка КАК Ссылка,| Номенклатура.Наименование КАК Наименование,| Номенклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,| Номенклатура.СтавкаНДС КАК СтавкаНДС|ИЗ| Справочник.Номенклатура КАК Номенклатура|ГДЕ| Номенклатура.Ссылка В (&МассивНоменклатуры)";Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);Результат = Запрос.Выполнить();Выборка = Результат.Выбрать();СоответствиеДанных = Новый Соответствие;Пока Выборка.Следующий() ЦиклСоответствиеДанных.Вставить(Выборка.Ссылка,Новый Структура("Наименование, ЕдиницаИзмерения, СтавкаНДС",Выборка.Наименование, Выборка.ЕдиницаИзмерения, Выборка.СтавкаНДС));КонецЦикла;Для Каждого СтрокаТоваров Из Документ.Товары ЦиклДанныеНоменклатуры = СоответствиеДанных.Получить(СтрокаТоваров.Номенклатура);Если ДанныеНоменклатуры <> Неопределено ТогдаСтрокаТоваров.ЕдиницаИзмерения = ДанныеНоменклатуры.ЕдиницаИзмерения;КонецЕсли;КонецЦикла;
Правило 2: Временные таблицы для сложных запросов
Разбиение на этапы: читаемость, отладка поэтапно, индексация промежуточных результатов.
Запрос = Новый Запрос;Запрос.Текст ="ВЫБРАТЬ| Реализация.Ссылка КАК ДокументСсылка,| Реализация.Контрагент КАК Контрагент,| Реализация.СуммаДокумента КАК Сумма|ПОМЕСТИТЬ втРеализации|ИЗ| Документ.РеализацияТоваровУслуг КАК Реализация|ГДЕ| Реализация.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания| И Реализация.Проведен|;||////////////////////////////////////////////////////////////////////////////////|ВЫБРАТЬ| втРеализации.Контрагент КАК Контрагент,| СУММА(втРеализации.Сумма) КАК ОбщаяСумма,| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ втРеализации.ДокументСсылка) КАК КоличествоДокументов|ПОМЕСТИТЬ втИтогиПоКонтрагентам|ИЗ| втРеализации||СГРУППИРОВАТЬ ПО| втРеализации.Контрагент|;||////////////////////////////////////////////////////////////////////////////////|ВЫБРАТЬ| втИтоги.Контрагент КАК Контрагент,| втИтоги.ОбщаяСумма КАК ОбщаяСумма,| втИтоги.КоличествоДокументов КАК КоличествоДокументов,| ВзаиморасчетыОстатки.СуммаОстаток КАК Задолженность|ИЗ| втИтогиПоКонтрагентам КАК втИтоги| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки КАК ВзаиморасчетыОстатки| ПО втИтоги.Контрагент = ВзаиморасчетыОстатки.Контрагент||УПОРЯДОЧИТЬ ПО| ОбщаяСумма УБЫВ";
Правила: префикс вт (стандарт); ИНДЕКСИРОВАТЬ ПО для полей соединения; использовать МенеджерВременныхТаблиц для управления жизненным циклом.
Правило 3: Параметризация запросов — не подставляйте значения в текст
Подстановка значений в текст: уязвимость, невозможность кэширования плана СУБД, ошибки форматирования.
// Правильно — через параметрыЗапрос = Новый Запрос;Запрос.Текст ="ВЫБРАТЬ Товары.Наименование|ИЗ Справочник.Номенклатура КАК Товары|ГДЕ Товары.ВидНоменклатуры = &ВидНоменклатуры| И Товары.Цена >= &МинимальнаяЦена";Запрос.УстановитьПараметр("ВидНоменклатуры", Перечисления.ВидыНоменклатуры.Товар);Запрос.УстановитьПараметр("МинимальнаяЦена", 1000);
Правило 4: ЕСТЬNULL() при LEFT JOIN
NULL + 100 = NULL, NULL > 0 = ЛОЖЬ. Необработанный NULL приводит к неверным расчётам и потерянным строкам в условиях.
Запрос.Текст ="ВЫБРАТЬ| Номенклатура.Ссылка КАК Номенклатура,| ЕСТЬNULL(ОстаткиТоваров.КоличествоОстаток, 0) КАК Остаток,| ЕСТЬNULL(ОстаткиТоваров.СуммаОстаток, 0) КАК СуммаОстатка|ИЗ| Справочник.Номенклатура КАК Номенклатура| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ОстаткиТоваров| ПО Номенклатура.Ссылка = ОстаткиТоваров.Номенклатура|ГДЕ| ЕСТЬNULL(ОстаткиТоваров.КоличествоОстаток, 0) > 0";
Ловушка — фильтр без ЕСТЬNULL
// ПЛОХО: ГДЕ ОстаткиТоваров.КоличествоОстаток > 0// → NULL > 0 = ЛОЖЬ → строки без остатков пропадут (LEFT JOIN превращается в INNER)
Правило 5: Виртуальные таблицы регистров — параметры внутрь
Виртуальные таблицы (Остатки, Обороты, СрезПоследних) — параметризованные функции СУБД. Параметры внутри — оптимальный план. Параметры в WHERE — СУБД сначала вычислит все данные, потом отфильтрует. Разница — в тысячи раз.
Стандарт ИТС: «Использование виртуальных таблиц».
// Правильно — параметры внутри виртуальной таблицыЗапрос.Текст ="ВЫБРАТЬ| Остатки.Номенклатура КАК Номенклатура,| Остатки.Склад КАК Склад,| Остатки.КоличествоОстаток КАК Остаток|ИЗ| РегистрНакопления.ТоварыНаСкладах.Остатки(| &ДатаОстатков,| Номенклатура В (&СписокНоменклатуры)| И Склад = &Склад| ) КАК Остатки";
Неправильно — фильтрация через WHERE
// ПЛОХО: СУБД вычислит остатки по ВСЕЙ номенклатуре на ВСЕХ складах, потом отфильтрует"ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки|ГДЕ Остатки.Номенклатура В (&СписокНоменклатуры)"
Правило 6: Пакетные операции — ТаблицаЗначений как параметр запроса
Передача массива данных во временную таблицу через Запрос.УстановитьПараметр("ВТ", ТаблицаЗначений) — один запрос вместо цикла.
ТаблицаДанных = Новый ТаблицаЗначений;ТаблицаДанных.Колонки.Добавить("Штрихкод", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(13)));ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));Для Каждого СтрокаИмпорта Из ДанныеИмпорта ЦиклНоваяСтрока = ТаблицаДанных.Добавить();НоваяСтрока.Штрихкод = СтрокаИмпорта.Штрихкод;НоваяСтрока.Количество = СтрокаИмпорта.Количество;КонецЦикла;Запрос = Новый Запрос;Запрос.УстановитьПараметр("ДанныеИмпорта", ТаблицаДанных);Запрос.Текст ="ВЫБРАТЬ| Данные.Штрихкод КАК Штрихкод,| Данные.Количество КАК Количество|ПОМЕСТИТЬ втДанныеИмпорта|ИЗ| &ДанныеИмпорта КАК Данные|;||////////////////////////////////////////////////////////////////////////////////|ВЫБРАТЬ| Штрихкоды.Номенклатура КАК Номенклатура,| втДанные.Количество КАК Количество|ИЗ| втДанныеИмпорта КАК втДанные| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды| ПО втДанные.Штрихкод = Штрихкоды.Штрихкод";
Правило 7: Обработка результатов — Выборка vs Выгрузка
| Способ | Когда использовать | |
|---|---|---|
Выбрать()/Следующий() | Последовательная обработка, экономия памяти | |
Выбрать(ПоГруппировкам) | Иерархические данные | |
Выгрузить() → ТаблицаЗначений | Произвольный доступ, поиск, передача в другую процедуру, данных < 10 000 строк |
// Выборка — данные загружаются порциямиВыборка = Запрос.Выполнить().Выбрать();Пока Выборка.Следующий() Цикл// обработкаКонецЦикла;// Выгрузка — всё в памятьТаблицаРезультат = Запрос.Выполнить().Выгрузить();НайденнаяСтрока = ТаблицаРезультат.Найти(ИскомаяНоменклатура, "Номенклатура");
Правило 8: Индексация — помогайте оптимизатору
Индексируйте: поля в условиях ГДЕ, поля в условиях соединения (ОН/ON), поля упорядочивания.
Индекс временной таблицы не добавляется "на всякий случай". Он оправдан, когда временная таблица большая и дальше многократно участвует в соединениях/отборах, либо есть измерение/план запроса, показывающий выигрыш. Для маленьких одноразовых ВТ индекс может стоить дороже, чем последующее чтение.
Временные таблицы — индексируйте поля соединения
Запрос.Текст ="ВЫБРАТЬ| ДанныеЗаказов.Номенклатура КАК Номенклатура,| ДанныеЗаказов.Количество КАК Количество|ПОМЕСТИТЬ втЗаказы|ИЗ| &ТаблицаЗаказов КАК ДанныеЗаказов||ИНДЕКСИРОВАТЬ ПО| Номенклатура|;||////////////////////////////////////////////////////////////////////////////////|ВЫБРАТЬ| втЗаказы.Номенклатура,| втЗаказы.Количество КАК Заказано,| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК НаСкладе|ИЗ| втЗаказы| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(,| Номенклатура В (ВЫБРАТЬ втЗаказы.Номенклатура ИЗ втЗаказы)) КАК Остатки| ПО втЗаказы.Номенклатура = Остатки.Номенклатура";
В конфигураторе/EDT: Реквизит -> Свойства -> «Индексировать».
Правило 9: Только нужные поля (не ВЫБРАТЬ *)
Избыточные поля: лишний трафик, невозможность покрывающего индекса, хрупкость при добавлении реквизитов.
// Правильно — явный список полей"ВЫБРАТЬ| Контрагенты.Ссылка,| Контрагенты.Наименование,| Контрагенты.ИНН|ИЗ Справочник.Контрагенты КАК Контрагенты"
Правило 10: ПЕРВЫЕ N для ограничения результата
Запрос без ограничения может вернуть миллионы строк и исчерпать память.
"ВЫБРАТЬ ПЕРВЫЕ 100| Товары.Наименование, Товары.Код|ИЗ Справочник.Номенклатура КАК Товары|ГДЕ Товары.Наименование ПОДОБНО &СтрокаПоиска|УПОРЯДОЧИТЬ ПО Товары.Наименование"
Для отображения списков используйте динамические списки — они реализуют пагинацию автоматически.
Правило 11: Не маскируйте дубликаты через РАЗЛИЧНЫЕ
РАЗЛИЧНЫЕ требует сортировки/хеширования всех строк. Если дубликаты из-за лишнего JOIN — исправьте запрос.
// Правильно — подзапрос вместо JOIN + РАЗЛИЧНЫЕ"ВЫБРАТЬ Контрагенты.Ссылка, Контрагенты.Наименование|ИЗ Справочник.Контрагенты КАК Контрагенты|ГДЕ Контрагенты.Ссылка В| (ВЫБРАТЬ РАЗЛИЧНЫЕ Реализация.Контрагент| ИЗ Документ.РеализацияТоваровУслуг КАК Реализация| ГДЕ Реализация.Дата >= &ДатаНачала)"
Правило 12: ВЫРАЗИТЬ для составных типов
Если поле имеет составной тип (напр. «Регистратор»), СУБД делает LEFT JOIN ко всем таблицам составного типа. ВЫРАЗИТЬ(Поле КАК Тип) ограничивает JOIN до одной таблицы.
Стандарт ИТС: «Использование конструкции ВЫРАЗИТЬ в запросах».
// Правильно — JOIN только с одной таблицей"ВЫБРАТЬ| Движения.Период,| ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент,| Движения.Количество|ИЗ РегистрНакопления.ТоварыНаСкладах КАК Движения|ГДЕ Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг"
Ловушка — обращение через составной тип без ВЫРАЗИТЬ
// ПЛОХО: Движения.Регистратор.Контрагент без ВЫРАЗИТЬ// Если Регистратор может быть 20 видами документов — 20 LEFT JOIN!
Правило 13: ON vs WHERE в LEFT JOIN
- Условие в
ПОфильтрует правую таблицу ДО соединения — строки левой без пары остаются с NULL - Условие в
ГДЕфильтрует ПОСЛЕ — строки с NULL отбрасываются, превращая LEFT JOIN в INNER JOIN
// Правильно — фильтр правой таблицы в параметрах виртуальной таблицы / в ON"ВЫБРАТЬ| Номенклатура.Наименование,| ЕСТЬNULL(Цены.Цена, 0) КАК Цена|ИЗ Справочник.Номенклатура КАК Номенклатура| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаЦен,| ВидЦены = &ВидЦены) КАК Цены| ПО Номенклатура.Ссылка = Цены.Номенклатура"// Все товары в результате, даже без цены
Ловушка — фильтр правой таблицы в WHERE
// ПЛОХО: ГДЕ Цены.ВидЦены = &ВидЦены// → строки без цены (NULL) отфильтруются — LEFT JOIN стал INNER JOIN
Правило 14: Review checklist для текста запроса
Эти проверки не заменяют оптимизацию, а ловят style/safety дефекты до измерений производительности.
| Проверка | Правило | |
|---|---|---|
| Имена | Вложенные запросы, временные таблицы и наборы СКД имеют осмысленные имена. Не оставляй ВложенныйЗапрос, ВТ1, НаборДанных1 в рабочем коде. | |
| Составные типы | Для разыменования реквизита составного типа используй ВЫРАЗИТЬ(... КАК ...) и/или ССЫЛКА, чтобы не получить неявные JOIN ко всем возможным типам. | |
| Предопределенные значения | Для пустых ссылок и предопределенных значений используй ЗНАЧЕНИЕ(...), когда значение является частью модели, а не внешним параметром. | |
| Лишние поля | Не выбирай поля, которые дальше не используются. | |
| Строковые константы | Значения, которые меняются от вызова к вызову, передавай параметрами, а не строковыми литералами в тексте запроса. | |
| RLS | Не убирай РАЗРЕШЕННЫЕ ради производительности без явного решения по безопасности. | |
| Вычисления | Сложные вычисляемые поля сначала считай в ВЫБРАТЬ/ВТ, затем фильтруй по готовому полю, если это снижает повторные вычисления. | |
ПО vs ГДЕ | Условия соединения держи в ПО; условия отбора результата - в ГДЕ. Смешивай только с осознанной причиной. | |
| Dot dereference | Избегай длинных цепочек через точку в ГДЕ; вынеси нужные значения в ВТ или используй явное соединение. | |
| Сборка текста | Не собирай произвольный текст запроса конкатенацией в бизнес-логике. Динамические фрагменты должны быть заранее определены, промаркированы и ограничены белым списком таблиц/полей. | |
| Длинный запрос | Длинный запрос выноси в отдельную функцию, которая возвращает только текст. Выполнение, параметры и обработка результата остаются отдельно. | |
| Порядок | Сначала установи Запрос.Текст, затем вызывай Запрос.УстановитьПараметр(...). | |
| Пустой результат | Не делай отдельный РезультатЗапроса.Пустой() перед обычным обходом, если достаточно первого Выборка.Следующий(). | |
| Булевы поля | Для булевого результата используй булеву алгебру (Количество > 0 КАК ЕстьОстаток), а не ВЫБОР ... ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ. | |
АВТОНОМЕРЗАПИСИ | Не строь бизнес-логику на ожидании, что автонумерация записей гарантированно идет с единицы и без пропусков. |
Правило 15: В (...) не является универсальным решением
Для маленьких списков параметров В (&Список) нормально. Для больших наборов, подзапросов и критичных участков проверь вариант через временную таблицу и явное соединение: так легче управлять планом, индексами и объемом данных.
Не переписывай В механически. Меняй форму запроса только когда есть ожидаемый выигрыш, читаемость или evidence от плана/замера.
Правило 16: ИТОГИ только для иерархического обхода
ИТОГИ генерирует дополнительные строки-суммы. Они нужны только при иерархическом обходе Выбрать(ПоГруппировкам). Для плоской выборки — СГРУППИРОВАТЬ ПО, без ИТОГИ.
// Плоская выборка сумм по контрагентам — СГРУППИРОВАТЬ ПО, без ИТОГИ"ВЫБРАТЬ Контрагент, СУММА(Сумма) КАК Итог|ИЗ ...|СГРУППИРОВАТЬ ПО Контрагент"
depends_on: []