Язык запросов 1С:Предприятия 8.3 изнутри: синтаксис, виртуальные таблицы, скорость
Содержание
Запрос в 1С — это текст на встроенном языке запросов, который читает данные из информационной базы: остатки, обороты, списки документов, цены. Пишется он в модуле через объект Запрос, платформа переводит его в SQL конкретной СУБД (MS SQL Server, PostgreSQL и других) или в команды движка файловой базы, а результат возвращает программе. Изменять данные запросом нельзя — только читать.
Зачем это нужно на практике: один запрос заменяет сотни обращений к объектам в цикле. Отчёт по остаткам на 5 000 позиций через перебор справочника считается минуты, через запрос к виртуальной таблице регистра — доли секунды. Почти все типовые отчёты, обработки заполнения и проведение документов в БП 3.0, ЗУП 3.1 и УТ 11 построены на запросах. Материал ниже общий для платформы 8.3 и не зависит от конфигурации.
Как запрос 1С проходит путь от текста до данных
Разработчик оперирует не таблицами СУБД, а объектами метаданных: Справочник.Номенклатура, Документ.РеализацияТоваровУслуг, РегистрНакопления.ТоварыНаСкладах. Физически в базе это таблицы вида _Reference45 или _Document312, но знать их имена не нужно.
Цепочка выглядит так:
- 📝 Код создаёт объект
Запрос, задаёт текст и параметры. - 🔍 Платформа разбирает текст, проверяет имена полей и таблиц по метаданным.
- 🔐 Если в запросе есть
РАЗРЕШЕННЫЕ, платформа учитывает ограничения доступа на уровне записей (RLS) для текущего пользователя. - ⚙️ Текст переводится в SQL нужной СУБД. Для файловой базы — в обращения к собственному движку.
- 📦 СУБД выполняет запрос, платформа упаковывает ответ в объект
РезультатЗапроса. - 🔁 Код обходит результат выборкой или выгружает его в таблицу значений.
Минимальный рабочий пример — список номенклатуры, которая не помечена на удаление:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
| Наименование";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Три способа забрать результат:
| Метод | Что возвращает | Когда брать |
|---|---|---|
Выбрать() | Выборку, обход по одной строке | Большие объёмы, построчная обработка |
Выгрузить() | ТаблицаЗначений | Нужно искать, сортировать, передавать дальше |
Пустой() | Истина / Ложь | Проверить, есть ли вообще данные |
Если нужен только факт наличия записи, пишите ВЫБРАТЬ ПЕРВЫЕ 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С: что тормозит и как это найти
Пять приёмов, которые дают основной выигрыш
- 🚫 Убрать запрос из цикла. Сто запросов по одной строке медленнее одного запроса на сто строк. Соберите ключи в массив и передайте параметром с условием
В (&Массив). - 🎯 Не разыменовывать составные поля.
Регистратор.Датапри регистраторе из 15 видов документов превращается в 15 соединений. Если нужен один вид, пишитеВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Дата. - 🗂️ Подзапрос в соединении — во временную таблицу. СУБД плохо оценивает подзапросы внутри
СОЕДИНЕНИЕ. Вынесите его вПОМЕСТИТЬс индексом. - ➗
ИЛИв условии заменить наОБЪЕДИНИТЬ ВСЕ. Условие сИЛИпо разным полям часто мешает СУБД использовать индекс. - 📉 Брать только нужные поля. Не выбирайте
Ссылкацеликом, если нужно одноНаименование, и не тяните табличную часть ради итоговой суммы, которая есть в шапке.
Как измерить, а не гадать
- ⏱️ Замер производительности. В Конфигураторе запустите отладку и включите
Отладка → Замер производительности. После операции увидите, какие строки кода съели время. Если лидирует строкаЗапрос.Выполнить(), проблема в запросе. - 🧪 Консоль запросов. Внешняя обработка, где запрос выполняют отдельно от кода, с параметрами и временем выполнения. В части версий обработки виден и план запроса.
- 📜 Технологический журнал. Настраивается файлом
logcfg.xmlи пишет событияDBMSSQLилиDBPOSTGRSс текстом SQL и длительностью. Подходит для поиска медленных запросов на рабочем сервере.
Включайте технологический журнал с фильтром по длительности и базе. Без фильтров на нагруженном сервере он быстро забивает диск.
Ошибки в запросах 1С и как их разобрать
Синтаксическая ошибка показывает строку и позицию внутри текста запроса в фигурных скобках, например {(7, 3)}: Поле не найдено "Номенклатура.Цена". Первое число — строка текста запроса, а не модуля. Считайте от первой строки внутри кавычек.
| Сообщение | Типичная причина | Что делать |
|---|---|---|
Поле не найдено | Опечатка, поле удалено из метаданных, нет псевдонима таблицы | Откройте запрос в конструкторе, он подсветит проблему |
Таблица не найдена | Неверное имя объекта или временной таблицы | Проверьте, что ПОМЕСТИТЬ выполнился раньше и в том же менеджере |
Неоднозначное поле | Поле с таким именем есть в двух соединённых таблицах | Укажите псевдоним: Остатки.Номенклатура |
Ошибки без сообщения коварнее — запрос работает, но данные неверные:
- 🔢 Задвоение сумм. Соединили шапку документа с табличной частью и сложили
СуммаДокумента— сумма умножилась на число строк. Агрегируйте до соединения. - 🕳️ Пропавшие строки. Условие на поле правой таблицы после
ЛЕВОЕ СОЕДИНЕНИЕвГДЕотсекает строки сNULLи превращает соединение во внутреннее. Переносите условие вПО. - 🗑️ Помеченные на удаление и непроведённые. Запрос к таблице документа без условия
Проведензахватывает черновики. Виртуальные таблицы регистров этой проблемы не имеют: движения есть только у проведённых документов. - 📆 Конец периода.
КонецПериода= 31.03 без времени означает 31.03 00:00:00, и весь последний день выпадает. ИспользуйтеКонецМесяца()илиКонецДня()в коде.
Частые вопросы
Можно ли запросом 1С изменить или удалить данные?
ПолучитьОбъект(), наборы записей регистров. Единственная операция записи — создание и удаление временных таблиц.Где выполнить запрос без программирования?
Файл → Открыть, если у пользователя есть право открывать внешние обработки.