Язык запросов 1С:Предприятия 8.3 изнутри: синтаксис, виртуальные таблицы, скорость

· 10 мин

Содержание

Запрос в 1С — это текст на встроенном языке запросов, который читает данные из информационной базы: остатки, обороты, списки документов, цены. Пишется он в модуле через объект Запрос, платформа переводит его в SQL конкретной СУБД (MS SQL Server, PostgreSQL и других) или в команды движка файловой базы, а результат возвращает программе. Изменять данные запросом нельзя — только читать.

Зачем это нужно на практике: один запрос заменяет сотни обращений к объектам в цикле. Отчёт по остаткам на 5 000 позиций через перебор справочника считается минуты, через запрос к виртуальной таблице регистра — доли секунды. Почти все типовые отчёты, обработки заполнения и проведение документов в БП 3.0, ЗУП 3.1 и УТ 11 построены на запросах. Материал ниже общий для платформы 8.3 и не зависит от конфигурации.

Как запрос 1С проходит путь от текста до данных

Разработчик оперирует не таблицами СУБД, а объектами метаданных: Справочник.Номенклатура, Документ.РеализацияТоваровУслуг, РегистрНакопления.ТоварыНаСкладах. Физически в базе это таблицы вида _Reference45 или _Document312, но знать их имена не нужно.

Цепочка выглядит так:

  1. 📝 Код создаёт объект Запрос, задаёт текст и параметры.
  2. 🔍 Платформа разбирает текст, проверяет имена полей и таблиц по метаданным.
  3. 🔐 Если в запросе есть РАЗРЕШЕННЫЕ, платформа учитывает ограничения доступа на уровне записей (RLS) для текущего пользователя.
  4. ⚙️ Текст переводится в SQL нужной СУБД. Для файловой базы — в обращения к собственному движку.
  5. 📦 СУБД выполняет запрос, платформа упаковывает ответ в объект РезультатЗапроса.
  6. 🔁 Код обходит результат выборкой или выгружает его в таблицу значений.

Минимальный рабочий пример — список номенклатуры, которая не помечена на удаление:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Номенклатура.Ссылка КАК Ссылка,
|	Номенклатура.Наименование КАК Наименование
|ИЗ
|	Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|	НЕ Номенклатура.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
|	Наименование";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	Сообщить(Выборка.Наименование);
КонецЦикла;

Три способа забрать результат:

МетодЧто возвращаетКогда брать
Выбрать()Выборку, обход по одной строкеБольшие объёмы, построчная обработка
Выгрузить()ТаблицаЗначенийНужно искать, сортировать, передавать дальше
Пустой()Истина / ЛожьПроверить, есть ли вообще данные

Если нужен только факт наличия записи, пишите ВЫБРАТЬ ПЕРВЫЕ 1 и проверяйте Пустой(). Тянуть всю таблицу ради одной проверки — частая причина тормозов.

Чем язык запросов 1С отличается от SQL

Основа общая: ВЫБРАТЬ … ИЗ … ГДЕ … СГРУППИРОВАТЬ ПО … УПОРЯДОЧИТЬ ПО. Тот, кто писал на SQL, прочитает запрос 1С с первого раза. Различия начинаются в деталях, и именно они путают новичков.

ВозможностьSQLЯзык запросов 1С
Изменение данныхINSERT, UPDATE, DELETEНет, только чтение (временные таблицы — исключение)
Имена таблицФизическиеИмена объектов метаданных
Обращение к реквизиту связанной таблицыЯвный JOINЧерез точку: Контрагент.ИНН
Остатки и оборотыСчитать самомуГотовые виртуальные таблицы регистров
Иерархия справочниковРекурсивные запросыВ ИЕРАРХИИ
Итоги по группировкамROLLUP в части СУБДИТОГИ ПО
Права пользователяПрава СУБДРАЗРЕШЕННЫЕ и RLS
Ограничение числа строкTOP / LIMITПЕРВЫЕ N

Ключевые слова можно писать по-русски или по-английски: ВЫБРАТЬ и SELECT равнозначны. В типовых конфигурациях принят русский вариант.

Обращение через точку — главная «магия» 1С. Запись Реализация.Контрагент.ИНН платформа сама превращает в левое соединение с таблицей контрагентов. Удобно, но за удобство платят скоростью, об этом ниже.

Из чего состоит текст запроса 1С

Порядок секций строгий. Пропускать можно всё, кроме ВЫБРАТЬ и ИЗ.

ВЫБРАТЬ [РАЗРЕШЕННЫЕ] [РАЗЛИЧНЫЕ] [ПЕРВЫЕ N]
	<поля>
[ПОМЕСТИТЬ <временная таблица>]
ИЗ
	<источники и соединения>
[ГДЕ <условие>]
[СГРУППИРОВАТЬ ПО <поля>]
[ИМЕЮЩИЕ <условие на агрегаты>]
[ОБЪЕДИНИТЬ [ВСЕ] <другой запрос>]
[УПОРЯДОЧИТЬ ПО <поля>]
[ИТОГИ ... ПО <группировки>]

Соединения таблиц

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

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

Функции, без которых не обойтись

  • 🧮 СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ — агрегаты для группировки.
  • 🔀 ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ — условие внутри поля.
  • 🏷️ ПРЕДСТАВЛЕНИЕ(Поле) — текстовое представление ссылки для вывода.
  • 🎯 ВЫРАЗИТЬ(Поле КАК Тип) — приведение типа, в том числе составного.
  • 📅 НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ), ДОБАВИТЬКДАТЕ, РАЗНОСТЬДАТ — работа с датами.
  • 🔎 ПОДОБНО "%шуруп%" — поиск по подстроке.

Конструктор вместо ручного набора

Писать текст вручную не обязательно. В Конфигураторе встаньте курсором в модуль, откройте контекстное меню и выберите Конструктор запроса…. Он показывает дерево метаданных, вкладки полей, условий, связей, группировки и сам формирует корректный текст. Существующий запрос открывается в конструкторе так же: курсор внутрь строки с текстом — и та же команда.

Пункт Конструктор запроса с обработкой результата… дополнительно генерирует код выполнения и обхода выборки.

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

Параметры вместо склейки строк

Значения в запрос передают параметрами с амперсандом, а не вклеивают в текст:

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

Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Контрагент", Контрагент);

Параметр может быть массивом или списком значений: условие Реализация.Контрагент В (&СписокКонтрагентов) отберёт документы сразу по многим контрагентам.

Если имя параметра в тексте и в УстановитьПараметр различается хоть одной буквой, запрос упадёт при выполнении. Конструктор показывает все параметры на отдельной вкладке — сверяйте там.

Пакетный запрос и ПОМЕСТИТЬ

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

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

Запрос.УстановитьПараметр("Начало", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Конец", КонецМесяца(ТекущаяДатаСеанса()));

Результаты = Запрос.ВыполнитьПакет();
ТаблицаПродаж = Результаты[1].Выгрузить();

Что здесь важно:

  • 📌 Выполнить() возвращает результат последнего запроса пакета, ВыполнитьПакет() — массив всех результатов. Индексы с нуля: [0] — первый запрос, [1] — второй.
  • 📌 МенеджерВременныхТаблиц нужен, если временные таблицы должны пережить выполнение и использоваться следующими запросами в той же процедуре. Внутри одного пакета они доступны и без него.
  • 📌 ИНДЕКСИРОВАТЬ ПО строит индекс по полю временной таблицы. Это окупается, когда по этому полю потом идёт соединение с большой таблицей.
  • 📌 Ненужную временную таблицу удаляют строкой УНИЧТОЖИТЬ ВТ_Продажи — это единственная «запись», которую позволяет язык запросов.

Таблицу значений из кода тоже можно отдать в запрос: передайте её параметром и выберите ИЗ &Таблица КАК Т с ПОМЕСТИТЬ. Колонкам таблицы при этом нужно задать типы, иначе запрос не разберёт поля.

Виртуальные таблицы регистров: почему запросы к ним быстрые

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

РегистрВиртуальная таблицаЧто даёт
Накопления (остатки)ОстаткиОстаток на момент
НакопленияОборотыПриход/расход за период
Накопления (остатки)ОстаткиИОборотыНачальный, обороты, конечный за период
Сведений (периодический)СрезПоследних / СрезПервыхАктуальное значение на дату
БухгалтерииОстатки, Обороты, ОборотыДтКтСальдо и обороты по счетам

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

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

Имя регистра и его измерения в вашей конфигурации могут быть другими — смотрите их в дереве метаданных или в конструкторе.

Отбор в параметрах или в ГДЕ

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

  • ✅ ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) — правильно.
  • ❌ ТоварыНаСкладах.Остатки(&Дата) … ГДЕ Остатки.Склад = &Склад — тот же ответ, но в разы медленнее на большой базе.

В ГДЕ оставляют условия на ресурсы, например КоличествоОстаток > 0: в параметры их не перенести.

Остаток на момент документа

Если передать в параметр период дату документа, в остаток не попадут движения других документов той же секунды. Точнее передать границу:

Запрос.УстановитьПараметр("Дата", Новый Граница(Документ.МоментВремени(), ВидГраницы.Исключая));

Исключая — остаток до документа, Включая — с его движениями.

Оптимизация запросов 1С: что тормозит и как это найти

Пять приёмов, которые дают основной выигрыш

  1. 🚫 Убрать запрос из цикла. Сто запросов по одной строке медленнее одного запроса на сто строк. Соберите ключи в массив и передайте параметром с условием В (&Массив).
  2. 🎯 Не разыменовывать составные поля. Регистратор.Дата при регистраторе из 15 видов документов превращается в 15 соединений. Если нужен один вид, пишите ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Дата.
  3. 🗂️ Подзапрос в соединении — во временную таблицу. СУБД плохо оценивает подзапросы внутри СОЕДИНЕНИЕ. Вынесите его в ПОМЕСТИТЬ с индексом.
  4. ➗ ИЛИ в условии заменить на ОБЪЕДИНИТЬ ВСЕ. Условие с ИЛИ по разным полям часто мешает СУБД использовать индекс.
  5. 📉 Брать только нужные поля. Не выбирайте Ссылка целиком, если нужно одно Наименование, и не тяните табличную часть ради итоговой суммы, которая есть в шапке.

Как измерить, а не гадать

  • ⏱️ Замер производительности. В Конфигураторе запустите отладку и включите Отладка → Замер производительности. После операции увидите, какие строки кода съели время. Если лидирует строка Запрос.Выполнить(), проблема в запросе.
  • 🧪 Консоль запросов. Внешняя обработка, где запрос выполняют отдельно от кода, с параметрами и временем выполнения. В части версий обработки виден и план запроса.
  • 📜 Технологический журнал. Настраивается файлом logcfg.xml и пишет события DBMSSQL или DBPOSTGRS с текстом SQL и длительностью. Подходит для поиска медленных запросов на рабочем сервере.

Включайте технологический журнал с фильтром по длительности и базе. Без фильтров на нагруженном сервере он быстро забивает диск.

Диагностика: выберите симптом

Ошибки в запросах 1С и как их разобрать

Синтаксическая ошибка показывает строку и позицию внутри текста запроса в фигурных скобках, например {(7, 3)}: Поле не найдено "Номенклатура.Цена". Первое число — строка текста запроса, а не модуля. Считайте от первой строки внутри кавычек.

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

Ошибки без сообщения коварнее — запрос работает, но данные неверные:

  • 🔢 Задвоение сумм. Соединили шапку документа с табличной частью и сложили СуммаДокумента — сумма умножилась на число строк. Агрегируйте до соединения.
  • 🕳️ Пропавшие строки. Условие на поле правой таблицы после ЛЕВОЕ СОЕДИНЕНИЕ в ГДЕ отсекает строки с NULL и превращает соединение во внутреннее. Переносите условие в ПО.
  • 🗑️ Помеченные на удаление и непроведённые. Запрос к таблице документа без условия Проведен захватывает черновики. Виртуальные таблицы регистров этой проблемы не имеют: движения есть только у проведённых документов.
  • 📆 Конец периода. КонецПериода = 31.03 без времени означает 31.03 00:00:00, и весь последний день выпадает. Используйте КонецМесяца() или КонецДня() в коде.

Частые вопросы

Можно ли запросом 1С изменить или удалить данные?
Нет. Язык запросов только читает данные. Изменения делают через объекты встроенного языка: ПолучитьОбъект(), наборы записей регистров. Единственная операция записи — создание и удаление временных таблиц.
Где выполнить запрос без программирования?
Во внешней обработке «Консоль запросов». Её открывают в пользовательском режиме через Файл → Открыть, если у пользователя есть право открывать внешние обработки.
Чем РАЗРЕШЕННЫЕ отличается от обычного запроса?
Без этого слова запрос к данным, закрытым ограничением RLS, завершается ошибкой доступа. С ним платформа молча убирает из результата недоступные пользователю записи.
Совпадает ли язык запросов с языком системы компоновки данных?
Основа одна, но в СКД добавлены расширения в фигурных скобках для отборов и параметров. Обычный запрос из модуля такие конструкции не поймёт.
Работают ли запросы одинаково в файловой и клиент-серверной базе?
Синтаксис один. Различается скорость и поведение блокировок: в клиент-серверном варианте запрос выполняет СУБД, и на тяжёлых запросах важны её индексы и статистика.