query-patterns
$
npx mdskill add SteelMorgan/1c-agent-based-dev-framework/query-patternsOptimizes 1C:Enterprise query patterns to minimize database round-trips and data volume for efficient agent operations.
- Helps avoid performance issues from executing queries in loops, reducing network latency and execution time.
- Integrates with 1C:Enterprise query language and uses structures like Соответствие for data handling.
- Decides based on IT standards prioritizing single queries over iterative ones to enhance efficiency.
- Presents results through structured data mappings for streamlined processing within agent workflows.
SKILL.md
.github/skills/query-patternsView on GitHub ↗
---
name: query-patterns
description: Паттерны запросов 1С. Этот навык учит агента правильно работать с языком запросов 1С:Предприятие.
---
# Паттерны запросов 1С
**Ключевой принцип:** Каждый запрос к базе — сетевой round-trip. Минимизация количества запросов и объёма возвращаемых данных — приоритет.
---
## Правило 1: НИКОГДА не выполняйте запросы в цикле
Для N итераций — N * (латентность сети + время выполнения). 1000 элементов * 5 мс = 5 секунд только на ожидание сети.
Стандарт ИТС: «Запросы к базе данных — ограничение на использование запросов в цикле».
### Правильно — один запрос + Соответствие для обработки
```bsl
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| Номенклатура.СтавкаНДС КАК СтавкаНДС
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&МассивНоменклатуры)";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
СоответствиеДанных = Новый Соответствие;
Пока Выборка.Следующий() Цикл
СоответствиеДанных.Вставить(Выборка.Ссылка,
Новый Структура("Наименование, ЕдиницаИзмерения, СтавкаНДС",
Выборка.Наименование, Выборка.ЕдиницаИзмерения, Выборка.СтавкаНДС));
КонецЦикла;
Для Каждого СтрокаТоваров Из Документ.Товары Цикл
ДанныеНоменклатуры = СоответствиеДанных.Получить(СтрокаТоваров.Номенклатура);
Если ДанныеНоменклатуры <> Неопределено Тогда
СтрокаТоваров.ЕдиницаИзмерения = ДанныеНоменклатуры.ЕдиницаИзмерения;
КонецЕсли;
КонецЦикла;
```
---
## Правило 2: Временные таблицы для сложных запросов
Разбиение на этапы: читаемость, отладка поэтапно, индексация промежуточных результатов.
```bsl
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК ДокументСсылка,
| Реализация.Контрагент КАК Контрагент,
| Реализация.СуммаДокумента КАК Сумма
|ПОМЕСТИТЬ втРеализации
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
| И Реализация.Проведен
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| втРеализации.Контрагент КАК Контрагент,
| СУММА(втРеализации.Сумма) КАК ОбщаяСумма,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ втРеализации.ДокументСсылка) КАК КоличествоДокументов
|ПОМЕСТИТЬ втИтогиПоКонтрагентам
|ИЗ
| втРеализации
|
|СГРУППИРОВАТЬ ПО
| втРеализации.Контрагент
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| втИтоги.Контрагент КАК Контрагент,
| втИтоги.ОбщаяСумма КАК ОбщаяСумма,
| втИтоги.КоличествоДокументов КАК КоличествоДокументов,
| ВзаиморасчетыОстатки.СуммаОстаток КАК Задолженность
|ИЗ
| втИтогиПоКонтрагентам КАК втИтоги
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки КАК ВзаиморасчетыОстатки
| ПО втИтоги.Контрагент = ВзаиморасчетыОстатки.Контрагент
|
|УПОРЯДОЧИТЬ ПО
| ОбщаяСумма УБЫВ";
```
Правила: префикс `вт` (стандарт); `ИНДЕКСИРОВАТЬ ПО` для полей соединения; использовать `МенеджерВременныхТаблиц` для управления жизненным циклом.
---
## Правило 3: Параметризация запросов — не подставляйте значения в текст
Подстановка значений в текст: уязвимость, невозможность кэширования плана СУБД, ошибки форматирования.
```bsl
// Правильно — через параметры
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Товары.Наименование
|ИЗ Справочник.Номенклатура КАК Товары
|ГДЕ Товары.ВидНоменклатуры = &ВидНоменклатуры
| И Товары.Цена >= &МинимальнаяЦена";
Запрос.УстановитьПараметр("ВидНоменклатуры", Перечисления.ВидыНоменклатуры.Товар);
Запрос.УстановитьПараметр("МинимальнаяЦена", 1000);
```
---
## Правило 4: ЕСТЬNULL() при LEFT JOIN
`NULL + 100 = NULL`, `NULL > 0 = ЛОЖЬ`. Необработанный NULL приводит к неверным расчётам и потерянным строкам в условиях.
```bsl
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| ЕСТЬNULL(ОстаткиТоваров.КоличествоОстаток, 0) КАК Остаток,
| ЕСТЬNULL(ОстаткиТоваров.СуммаОстаток, 0) КАК СуммаОстатка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ОстаткиТоваров
| ПО Номенклатура.Ссылка = ОстаткиТоваров.Номенклатура
|ГДЕ
| ЕСТЬNULL(ОстаткиТоваров.КоличествоОстаток, 0) > 0";
```
### Ловушка — фильтр без ЕСТЬNULL
```bsl
// ПЛОХО: ГДЕ ОстаткиТоваров.КоличествоОстаток > 0
// → NULL > 0 = ЛОЖЬ → строки без остатков пропадут (LEFT JOIN превращается в INNER)
```
---
## Правило 5: Виртуальные таблицы регистров — параметры внутрь
Виртуальные таблицы (`Остатки`, `Обороты`, `СрезПоследних`) — параметризованные функции СУБД. Параметры внутри — оптимальный план. Параметры в WHERE — СУБД сначала вычислит **все** данные, потом отфильтрует. Разница — в тысячи раз.
Стандарт ИТС: «Использование виртуальных таблиц».
```bsl
// Правильно — параметры внутри виртуальной таблицы
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.Склад КАК Склад,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &ДатаОстатков,
| Номенклатура В (&СписокНоменклатуры)
| И Склад = &Склад
| ) КАК Остатки";
```
### Неправильно — фильтрация через WHERE
```bsl
// ПЛОХО: СУБД вычислит остатки по ВСЕЙ номенклатуре на ВСЕХ складах, потом отфильтрует
"ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
|ГДЕ Остатки.Номенклатура В (&СписокНоменклатуры)"
```
---
## Правило 6: Пакетные операции — ТаблицаЗначений как параметр запроса
Передача массива данных во временную таблицу через `Запрос.УстановитьПараметр("ВТ", ТаблицаЗначений)` — один запрос вместо цикла.
```bsl
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Штрихкод", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(13)));
ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
Для Каждого СтрокаИмпорта Из ДанныеИмпорта Цикл
НоваяСтрока = ТаблицаДанных.Добавить();
НоваяСтрока.Штрихкод = СтрокаИмпорта.Штрихкод;
НоваяСтрока.Количество = СтрокаИмпорта.Количество;
КонецЦикла;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ДанныеИмпорта", ТаблицаДанных);
Запрос.Текст =
"ВЫБРАТЬ
| Данные.Штрихкод КАК Штрихкод,
| Данные.Количество КАК Количество
|ПОМЕСТИТЬ втДанныеИмпорта
|ИЗ
| &ДанныеИмпорта КАК Данные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Штрихкоды.Номенклатура КАК Номенклатура,
| втДанные.Количество КАК Количество
|ИЗ
| втДанныеИмпорта КАК втДанные
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
| ПО втДанные.Штрихкод = Штрихкоды.Штрихкод";
```
---
## Правило 7: Обработка результатов — Выборка vs Выгрузка
| Способ | Когда использовать |
|--------|-------------------|
| `Выбрать()/Следующий()` | Последовательная обработка, экономия памяти |
| `Выбрать(ПоГруппировкам)` | Иерархические данные |
| `Выгрузить()` → ТаблицаЗначений | Произвольный доступ, поиск, передача в другую процедуру, данных < 10 000 строк |
```bsl
// Выборка — данные загружаются порциями
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// обработка
КонецЦикла;
// Выгрузка — всё в память
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
НайденнаяСтрока = ТаблицаРезультат.Найти(ИскомаяНоменклатура, "Номенклатура");
```
---
## Правило 8: Индексация — помогайте оптимизатору
Индексируйте: поля в условиях ГДЕ, поля в условиях соединения (ОН/ON), поля упорядочивания.
### Временные таблицы — индексируйте поля соединения
```bsl
Запрос.Текст =
"ВЫБРАТЬ
| ДанныеЗаказов.Номенклатура КАК Номенклатура,
| ДанныеЗаказов.Количество КАК Количество
|ПОМЕСТИТЬ втЗаказы
|ИЗ
| &ТаблицаЗаказов КАК ДанныеЗаказов
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| втЗаказы.Номенклатура,
| втЗаказы.Количество КАК Заказано,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК НаСкладе
|ИЗ
| втЗаказы
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(,
| Номенклатура В (ВЫБРАТЬ втЗаказы.Номенклатура ИЗ втЗаказы)) КАК Остатки
| ПО втЗаказы.Номенклатура = Остатки.Номенклатура";
```
В конфигураторе/EDT: Реквизит -> Свойства -> «Индексировать».
---
## Правило 9: Только нужные поля (не ВЫБРАТЬ *)
Избыточные поля: лишний трафик, невозможность покрывающего индекса, хрупкость при добавлении реквизитов.
```bsl
// Правильно — явный список полей
"ВЫБРАТЬ
| Контрагенты.Ссылка,
| Контрагенты.Наименование,
| Контрагенты.ИНН
|ИЗ Справочник.Контрагенты КАК Контрагенты"
```
---
## Правило 10: ПЕРВЫЕ N для ограничения результата
Запрос без ограничения может вернуть миллионы строк и исчерпать память.
```bsl
"ВЫБРАТЬ ПЕРВЫЕ 100
| Товары.Наименование, Товары.Код
|ИЗ Справочник.Номенклатура КАК Товары
|ГДЕ Товары.Наименование ПОДОБНО &СтрокаПоиска
|УПОРЯДОЧИТЬ ПО Товары.Наименование"
```
Для отображения списков используйте динамические списки — они реализуют пагинацию автоматически.
---
## Правило 11: Не маскируйте дубликаты через РАЗЛИЧНЫЕ
`РАЗЛИЧНЫЕ` требует сортировки/хеширования всех строк. Если дубликаты из-за лишнего JOIN — исправьте запрос.
```bsl
// Правильно — подзапрос вместо JOIN + РАЗЛИЧНЫЕ
"ВЫБРАТЬ Контрагенты.Ссылка, Контрагенты.Наименование
|ИЗ Справочник.Контрагенты КАК Контрагенты
|ГДЕ Контрагенты.Ссылка В
| (ВЫБРАТЬ РАЗЛИЧНЫЕ Реализация.Контрагент
| ИЗ Документ.РеализацияТоваровУслуг КАК Реализация
| ГДЕ Реализация.Дата >= &ДатаНачала)"
```
---
## Правило 12: ВЫРАЗИТЬ для составных типов
Если поле имеет составной тип (напр. «Регистратор»), СУБД делает LEFT JOIN ко **всем** таблицам составного типа. `ВЫРАЗИТЬ(Поле КАК Тип)` ограничивает JOIN до одной таблицы.
Стандарт ИТС: «Использование конструкции ВЫРАЗИТЬ в запросах».
```bsl
// Правильно — JOIN только с одной таблицей
"ВЫБРАТЬ
| Движения.Период,
| ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент,
| Движения.Количество
|ИЗ РегистрНакопления.ТоварыНаСкладах КАК Движения
|ГДЕ Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг"
```
### Ловушка — обращение через составной тип без ВЫРАЗИТЬ
```bsl
// ПЛОХО: Движения.Регистратор.Контрагент без ВЫРАЗИТЬ
// Если Регистратор может быть 20 видами документов — 20 LEFT JOIN!
```
---
## Правило 13: ON vs WHERE в LEFT JOIN
- Условие в `ПО` фильтрует правую таблицу **ДО** соединения — строки левой без пары остаются с NULL
- Условие в `ГДЕ` фильтрует **ПОСЛЕ** — строки с NULL отбрасываются, превращая LEFT JOIN в INNER JOIN
```bsl
// Правильно — фильтр правой таблицы в параметрах виртуальной таблицы / в ON
"ВЫБРАТЬ
| Номенклатура.Наименование,
| ЕСТЬNULL(Цены.Цена, 0) КАК Цена
|ИЗ Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаЦен,
| ВидЦены = &ВидЦены) КАК Цены
| ПО Номенклатура.Ссылка = Цены.Номенклатура"
// Все товары в результате, даже без цены
```
### Ловушка — фильтр правой таблицы в WHERE
```bsl
// ПЛОХО: ГДЕ Цены.ВидЦены = &ВидЦены
// → строки без цены (NULL) отфильтруются — LEFT JOIN стал INNER JOIN
```
---
depends_on: []
---
More from SteelMorgan/1c-agent-based-dev-framework
- 1c-ai-agent-cliCLI 1C BSL Agent Framework — tools/install.py (clone, install). Используй при клонировании репозитория, установке компонентов в проект, настройке IDE (Cursor, Claude Code, Windsurf, VS Code+Continue).
- agent-debugПаттерн отладочных сообщений для 1С BSL. Используй, когда стандартная диагностика (event-log, скриншоты) не даёт понять фактическое поведение системы — нужно вставить временные точки логирования в код, запустить тест и проанализировать записи ЖР.
- agent-developmentCreate custom subagents for specialized AI tasks. Use when the user wants to create a new type of subagent, set up task-specific agents, configure code reviewers, debuggers, or domain-specific assistants with custom prompts.
- agent-development-ext>
- agent-git-workflowStandardizes git workflow for the AI agent in the sandbox devcontainer: work in agent/<task>-<yyyymmdd>, integrate via agent, never push to main/master, open PRs via GitHub CLI. Use when the user asks to create branches, push changes, open PRs, or follow this sandbox repo setup.
- auto-skill-bootstrapDeterministic helper to inventory existing project skills, detect missing capability coverage, search skills.sh via Skills CLI, and (optionally) install missing skills under a trust policy. Uses skills-manifest.json + state.json to stay idempotent across changing requirements.
- code-navigationНавигация по коду (Code Navigation). Навык учит агента **эффективно перемещаться по BSL-коду** с помощью LSP (Language Server Protocol).
- config-operationsОперации с конфигурацией 1С (CF) — init, info, edit, validate. Используй при создании конфигурации, анализе структуры, изменении свойств и ChildObjects, валидации Configuration.xml.
- epf-buildСобрать EPF/ERF из XML-исходников. Используй после внесения временных диагностических правок в разобранную обработку.
- epf-dumpРазобрать EPF/ERF в XML-исходники. Используй, когда нужно быстро получить исходный код внешней обработки или отчета для анализа и временной модификации.