Язык запросов 1С: механизм выполнения и приёмы ускорения

· 9 мин

Содержание

Запрос в 1С — это способ быстро прочитать данные из базы: остатки по складу, продажи за период, список контрагентов с долгом. Пишется он на встроенном языке запросов, похожем на SQL, но с русскими ключевыми словами и своими объектами — виртуальными таблицами регистров.

Главное, что нужно держать в голове: язык запросов 1С умеет только читать данные. Изменить, удалить или записать что-либо через запрос нельзя — для этого есть объектная модель (документы, регистры, их методы Записать()). Запрос возвращает выборку, а что с ней делать — решает уже код обработки или отчёт.

Ниже — как запрос проходит путь от текста до результата, из каких конструкций он собирается, чем левое соединение отличается от внутреннего, зачем нужны временные таблицы и почему один и тот же запрос может выполняться доли секунды или минуты.

Что происходит, когда запрос выполняется

Запрос в коде создаётся объектом Запрос:

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

Что происходит под капотом по шагам:

  1. Разбор текста. Платформа парсит текст запроса на языке 1С и строит внутреннее представление.
  2. Трансляция в SQL. Текст переводится на диалект SQL той СУБД, с которой работает база — PostgreSQL, MS SQL или встроенная файловая СУБД. Имена метаданных («Справочник.Номенклатура») превращаются в имена реальных таблиц вроде _Reference123.
  3. Выполнение на стороне СУБД. Сервер базы данных выполняет SQL, использует индексы, строит план запроса. Вся тяжёлая работа идёт именно здесь, а не в коде 1С.
  4. Возврат результата. СУБД отдаёт выборку платформе, платформа — вашему коду как объект РезультатЗапроса.

Отсюда вывод для производительности: запрос тем быстрее, чем меньше данных просеивает СУБД и чем лучше ложится на индексы. Оптимизация запроса — это почти всегда «помочь СУБД взять данные по индексу и не читать лишнего».

В файловом варианте базы роль СУБД играет сама платформа, поэтому тяжёлые запросы в файловой базе заметно медленнее, чем в клиент-серверной на том же железе.

Из чего собирается запрос: базовый синтаксис

Минимальный запрос состоит из двух секций — что выбрать и откуда:

ВЫБРАТЬ
	Товар.Наименование,
	Товар.ЕдиницаИзмерения
ИЗ
	Справочник.Номенклатура КАК Товар

Основные секции в порядке их записи:

СекцияНазначениеАналог в SQL
ВЫБРАТЬперечень полей результатаSELECT
ИЗисточники данных (таблицы)FROM
ГДЕусловие отбора строкWHERE
СГРУППИРОВАТЬ ПОгруппировка для агрегатовGROUP BY
ИМЕЮЩИЕотбор по агрегатамHAVING
УПОРЯДОЧИТЬ ПОсортировкаORDER BY
ИТОГИ ПОитоги по группировкам— (своё)

Несколько приёмов, которые встречаются в каждом втором запросе:

  • 🔹 ВЫБРАТЬ РАЗЛИЧНЫЕ — убрать дубли строк (аналог DISTINCT).
  • 🔹 ВЫБРАТЬ ПЕРВЫЕ 10 — вернуть только первые N строк (аналог TOP).
  • 🔹 КАК Товар — псевдоним таблице или полю; по псевдониму потом обращаемся к полю через точку.
  • 🔹 Параметры пишутся через &: ГДЕ Товар.Цена > &МинЦена. Значение задаётся в коде: Запрос.УстановитьПараметр("МинЦена", 100);.

Условный расчёт поля делается через ВЫБОР (аналог CASE):

ВЫБРАТЬ
	Заказ.Номер,
	ВЫБОР
		КОГДА Заказ.СуммаДокумента > 100000 ТОГДА "Крупный"
		КОГДА Заказ.СуммаДокумента > 10000 ТОГДА "Средний"
		ИНАЧЕ "Мелкий"
	КОНЕЦ КАК Категория
ИЗ
	Документ.ЗаказКлиента КАК Заказ

Соединения таблиц и чем они различаются

Когда данные лежат в двух таблицах — например, строки заказа и справочник номенклатуры — их соединяют. Тип соединения определяет, какие строки попадут в результат.

  • ВНУТРЕННЕЕ СОЕДИНЕНИЕ — только строки, для которых нашлось совпадение в обеих таблицах.
  • ЛЕВОЕ СОЕДИНЕНИЕ — все строки левой таблицы; из правой подтягивается совпадение, а если его нет — NULL.
  • ПРАВОЕ СОЕДИНЕНИЕ — зеркально левому, все строки правой таблицы.
  • ПОЛНОЕ СОЕДИНЕНИЕ — все строки обеих таблиц, несовпавшие дополняются NULL.

На практике в 90% случаев используется левое соединение: берём документы и подтягиваем к ним справочные данные.

ВЫБРАТЬ
	СтрокиЗаказа.Номенклатура,
	СтрокиЗаказа.Количество,
	ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
	Документ.ЗаказКлиента.Товары КАК СтрокиЗаказа
	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
	ПО СтрокиЗаказа.Номенклатура = Цены.Номенклатура

После левого соединения в несовпавших строках будет NULL. Если такое поле попадёт в арифметику (Количество * Цена), результат тоже станет NULL. Поэтому поля из присоединённой таблицы почти всегда оборачивают в ЕСТЬNULL(Поле, 0).

Чем соединения опасны для скорости:

  • Каждое соединение — это дополнительная работа для СУБД. Чем больше таблиц, тем сложнее план.
  • Декартово произведение — если забыть условие ПО или задать его неверно, каждая строка левой таблицы умножится на все строки правой. Запрос вернёт миллионы строк и «повиснет».
  • Соединение с подзапросом или виртуальной таблицей дороже, чем с обычной таблицей по индексированному полю.

Агрегатные функции и группировка

Чтобы посчитать суммы, количества и средние, поля делят на две группы: по которым группируем и которые агрегируем.

Агрегатные функции языка запросов:

  • СУММА(Поле) — сумма;
  • КОЛИЧЕСТВО(Поле) — число непустых значений, КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — число уникальных;
  • МАКСИМУМ(Поле), МИНИМУМ(Поле) — экстремумы;
  • СРЕДНЕЕ(Поле) — среднее.

Правило: все поля, которые не внутри агрегатной функции, обязаны быть в СГРУППИРОВАТЬ ПО. Иначе запрос не выполнится.

ВЫБРАТЬ
	Продажи.Контрагент,
	СУММА(Продажи.Сумма) КАК Оборот,
	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Продажи.Ссылка) КАК ЧислоСделок
ИЗ
	Документ.РеализацияТоваровУслуг КАК Продажи
СГРУППИРОВАТЬ ПО
	Продажи.Контрагент
ИМЕЮЩИЕ
	СУММА(Продажи.Сумма) > 50000

Разница между ГДЕ и ИМЕЮЩИЕ: ГДЕ отбирает строки до группировки (по обычным полям), ИМЕЮЩИЕ — после (по агрегатам). Если условие можно поставить в ГДЕ, ставьте туда — так СУБД отсеет лишнее раньше и быстрее.

Отдельная конструкция 1С — ИТОГИ. Она не группирует строки, а добавляет к детальным строкам итоговые по уровням иерархии:

...
ИТОГИ
	СУММА(Сумма)
ПО
	Контрагент

Итоги удобно читать выборкой по группировкам: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).

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

Когда логику в один проход не уложить, есть два инструмента.

Вложенный подзапрос — запрос внутри секции ИЗ или условия. Подходит для небольших наборов данных:

ВЫБРАТЬ
	Товар.Наименование
ИЗ
	Справочник.Номенклатура КАК Товар
ГДЕ
	Товар.Ссылка В
		(ВЫБРАТЬ РАЗЛИЧНЫЕ Строки.Номенклатура
		ИЗ Документ.ЗаказКлиента.Товары КАК Строки)

Временная таблица — результат промежуточного запроса, сохранённый в памяти и доступный следующим запросам пакета. Создаётся через ПОМЕСТИТЬ:

ВЫБРАТЬ
	Строки.Номенклатура,
	СУММА(Строки.Количество) КАК Количество
ПОМЕСТИТЬ ВтТовары
ИЗ
	Документ.ЗаказКлиента.Товары КАК Строки
СГРУППИРОВАТЬ ПО
	Строки.Номенклатура
;
ВЫБРАТЬ
	ВтТовары.Номенклатура,
	ВтТовары.Количество,
	Остатки.КоличествоОстаток
ИЗ
	ВтТовары КАК ВтТовары
	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
	ПО ВтТовары.Номенклатура = Остатки.Номенклатура

Запросы в пакете разделяются ;. Чтобы временные таблицы жили между запросами, запросу назначают менеджер временных таблиц:

Менеджер = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = Менеджер;

Когда что выбирать:

  • 🔹 Подзапрос — набор маленький, используется один раз.
  • 🔹 Временная таблица — набор большой или нужен в нескольких местах запроса; результат объёмный и к нему идёт несколько соединений. СУБД индексирует временную таблицу и работает с ней быстрее, чем с повторяющимся подзапросом.

Отдельно стоит ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ — они складывают результаты двух запросов «в столбик». ОБЪЕДИНИТЬ убирает дубли (и потому медленнее), ОБЪЕДИНИТЬ ВСЕ оставляет всё как есть — используйте его, если дублей заведомо нет.

Виртуальные таблицы регистров — чем они особенные

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

Для регистра накопления типовые виртуальные таблицы:

  • .Остатки(&Период, Отбор) — остатки на дату;
  • .Обороты(&Начало, &Конец, Периодичность, Отбор) — обороты за период;
  • .ОстаткиИОбороты(...) — и то, и другое сразу.

Для регистра сведений — .СрезПоследних(&Период, Отбор) и .СрезПервых(...).

Ключевой приём оптимизации: отбор виртуальной таблицы задавайте в её параметрах, а не в секции ГДЕ. Параметры виртуальной таблицы сужают выборку внутри, до расчёта итогов. Условие в ГДЕ применяется уже к готовому результату — платформа посчитает остатки по всему регистру, а потом отбросит лишнее.

Правильно:

ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(
	&НаДату, Склад = &Склад) КАК Остатки

Медленно (отбор снаружи):

ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, ) КАК Остатки
ГДЕ Остатки.Склад = &Склад

Как ускорить медленный запрос

Если отчёт «думает» минуту, проблема почти всегда в одном из пунктов ниже.

  1. Обращение к полям через точку по составному типу. Запись Регистратор.Дата или Контрагент.ИНН, где поле — ссылка составного типа, платформа разворачивает в неявные левые соединения с каждой таблицей типа. Один «безобидный» . может добавить десяток соединений. Лечится через ВЫРАЗИТЬ(Поле КАК Документ.РеализацияТоваровУслуг).Дата — явно указываем один тип.
  2. Отбор виртуальной таблицы в ГДЕ вместо параметров — см. предыдущий раздел.
  3. Соединение с подзапросом. Если соединяете таблицу с вложенным (ВЫБРАТЬ ...), вынесите подзапрос во временную таблицу — СУБД её проиндексирует.
  4. Отбор по неиндексированному полю. Отбирайте по измерениям регистров и реквизитам, по которым есть индексы. Отбор по реквизиту без индекса заставляет СУБД читать всю таблицу.
  5. ОБЪЕДИНИТЬ там, где достаточно ОБЪЕДИНИТЬ ВСЕ. Снятие дублей — лишняя сортировка.
  6. Получение лишних данных. Не тяните поля, которые не нужны результату, не выбирайте строки, которые потом отбросите в коде.

Самая дорогая ошибка живёт не в самом запросе, а вокруг него — запрос в цикле. Когда для каждой строки документа выполняется отдельный запрос к базе, 500 строк превращаются в 500 обращений к СУБД.

Запрос внутри цикла по строкам — главная причина тормозов в доработках. Правильно: одним запросом получить все нужные данные сразу (соединив таблицы или через временную таблицу), а в цикле уже работать с готовой выборкой в памяти.

Посмотреть, что реально происходит, помогает технологический журнал и замер времени: включите в конфигураторе режим Отладка → Замер производительности или анализируйте план запроса средствами СУБД.

Проверьте себя
Где быстрее отобрать остатки по одному складу?
Что вернёт поле присоединённой таблицы, если совпадения в ЛЕВОМ СОЕДИНЕНИИ не нашлось?
Какой оператор объединения не убирает дубли и потому быстрее?

Практические запросы под типовые задачи

Несколько шаблонов, которые закрывают большинство реальных задач.

Остатки товаров на складе на дату:

ВЫБРАТЬ
	Остатки.Номенклатура,
	Остатки.КоличествоОстаток
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки(
		&НаДату, Склад = &Склад) КАК Остатки
ГДЕ
	Остатки.КоличествоОстаток > 0

Долг контрагентов (взаиморасчёты):

ВЫБРАТЬ
	Расчёты.Контрагент,
	Расчёты.СуммаОстаток КАК Долг
ИЗ
	РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(&НаДату, ) КАК Расчёты
ГДЕ
	Расчёты.СуммаОстаток <> 0
УПОРЯДОЧИТЬ ПО
	Долг УБЫВ

Продажи по менеджерам за период с итогами:

ВЫБРАТЬ
	Продажи.Менеджер,
	Продажи.Номенклатура,
	СУММА(Продажи.ВыручкаОборот) КАК Выручка
ИЗ
	РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи
СГРУППИРОВАТЬ ПО
	Продажи.Менеджер,
	Продажи.Номенклатура
ИТОГИ
	СУММА(Выручка)
ПО
	Менеджер

Результат такого запроса выгружают в табличный документ отчёта, в дерево значений или обходят выборкой. Для разовых проверок удобно отлаживать текст в консоли запросов (внешняя обработка «Консоль запросов» или аналог из состава разработческих инструментов).

Частые вопросы о запросах в 1С

Можно ли запросом изменить или удалить данные?
Нет. Язык запросов 1С только читает. Для изменения данных используют объектную модель: получают объект, меняют реквизиты, вызывают Записать() или Удалить(). Это сделано намеренно — чтобы изменения проходили через бизнес-логику проведения и проверки.
Чем виртуальная таблица отличается от обычной?
Обычная таблица (справочник, документ) хранится в базе как есть. Виртуальная таблица регистра рассчитывается платформой на лету из записей регистра — остатки, обороты, срезы. Напрямую такой таблицы в СУБД нет, её «собирает» транслятор запроса под заданные параметры.
Почему запрос быстрый в консоли, но медленный в отчёте?
Чаще всего в отчёте другие параметры (шире период, нет отбора по складу) или данных в боевой базе кратно больше, чем в тестовой. Ещё причина — запрос в цикле: сам текст выполняется быстро, но вызывается сотни раз.
Когда брать временную таблицу вместо подзапроса?
Когда промежуточный результат большой, используется в нескольких соединениях или к нему идёт отбор. Временную таблицу СУБД индексирует, повторяющийся подзапрос — пересчитывает каждый раз.
Что делать с NULL после соединений?
Оборачивать поля присоединённых таблиц в ЕСТЬNULL(Поле, 0) для чисел и ЕСТЬNULL(Поле, "") для строк. Иначе NULL в арифметике и сравнениях даст неожиданный результат.
Нужно ли знать SQL, чтобы писать запросы в 1С?
Необязательно, но полезно. Конструкции совпадают по смыслу (ВЫБРАТЬ ≈ SELECT, соединения ≈ JOIN), а понимание того, как СУБД исполняет SQL и использует индексы, прямо помогает писать быстрые запросы.