Язык запросов 1С на практике: от первой выборки до быстрого кода
Содержание
Запрос в 1С составляют так: пишут текст на языке запросов (ВЫБРАТЬ … ИЗ … ГДЕ …), передают в него значения через параметры &Имя и выполняют объектом Запрос. Простую выборку быстрее набрать руками, запрос с пятью таблицами и группировками — собрать в конструкторе, а потом довести вручную.
Язык запросов похож на SQL, но работает с объектами конфигурации: справочниками, документами, регистрами. Платформа сама переводит его в запрос к СУБД. Отсюда и главные правила: обращаться к виртуальным таблицам регистров, а не к сырым записям; не выполнять запрос в цикле; не склеивать текст запроса из пользовательского ввода.
Из чего состоит запрос 1С: синтаксис и порядок секций
Секции идут в строгом порядке. Перепутаете — получите синтаксическую ошибку при выполнении.
| Секция | Зачем нужна | Обязательна |
|---|---|---|
ВЫБРАТЬ | список полей, плюс РАЗЛИЧНЫЕ, ПЕРВЫЕ N, РАЗРЕШЕННЫЕ | да |
ПОМЕСТИТЬ | сохранить результат во временную таблицу | нет |
ИЗ | источники и соединения | почти всегда |
ГДЕ | отбор записей | нет |
СГРУППИРОВАТЬ ПО | группировка для СУММА, КОЛИЧЕСТВО, МАКСИМУМ | нет |
ИМЕЮЩИЕ | отбор по результату агрегатных функций | нет |
ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕ | склеить результаты нескольких выборок | нет |
УПОРЯДОЧИТЬ ПО | сортировка | нет |
ИТОГИ ПО | итоговые строки по группировкам | нет |
Минимальный рабочий пример — список элементов справочника без помеченных на удаление:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
| И НЕ Номенклатура.ЭтоГруппа
|УПОРЯДОЧИТЬ ПО
| Наименование";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Строки текста внутри кода продолжаются символом |. Кавычки внутри текста запроса удваиваются: ""Москва"".
Функции, без которых не обходится почти ни один запрос:
- 🔹
ЕСТЬNULL(Поле, 0)— подставить значение вместоNULLпосле левого соединения; - 🔹
ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ— условие внутри поля; - 🔹
НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ)иКОНЕЦПЕРИОДА— границы периода; - 🔹
ВЫРАЗИТЬ(Поле КАК Документ.РеализацияТоваровУслуг)— привести составной тип к одному; - 🔹
ПОДСТРОКА,ДАТАВРЕМЯ(2025, 1, 1),ТИПЗНАЧЕНИЯ,ССЫЛКА— для строк, дат и проверки типа.
Сравнение с NULL через знак = не работает. Пишите Поле ЕСТЬ NULL или Поле ЕСТЬ НЕ NULL.
Как выполнить запрос из кода: выборка, выгрузка, пустой результат
Результат запроса можно обойти тремя способами. Выбор зависит от того, что дальше делать с данными.
| Метод | Что даёт | Когда выбирать |
|---|---|---|
Результат.Выбрать() | последовательная выборка | обработка строк по одной, большие объёмы |
Результат.Выгрузить() | ТаблицаЗначений | нужно искать, сортировать, передать на форму |
Результат.Пустой() | Истина / Ложь | проверить, есть ли хоть одна строка |
Для проверки «есть ли данные» не выгружайте таблицу ради Количество(). Пустой() дешевле, а в самом запросе добавьте ПЕРВЫЕ 1.
Если в запросе есть ИТОГИ ПО, выборку обходят по группировкам: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), а внутри каждой группы снова вызывают Выбрать(). Так строят иерархические отчёты без ручного подсчёта сумм.
Конструктор запроса: собрать текст без ручного набора
Конструктор открывают в Конфигураторе или 1С:EDT прямо из модуля. Поставьте курсор в место, где будет текст, и выберите в контекстном меню Конструктор запроса. Если курсор стоит внутри уже написанного текста запроса, конструктор разберёт его и откроет на редактирование.
Второй пункт рядом — Конструктор запроса с обработкой результата. Он сразу генерирует код: создание объекта Запрос, установку параметров и цикл обхода выборки. Для новой процедуры это самый быстрый старт.
Порядок работы в конструкторе:
- Вкладка
Таблицы и поля: слева дерево метаданных, перетащите нужные таблицы в середину, поля — в правую колонку. - Вкладка
Связи: задайте условия соединения и отметьте флажки «Все» — от них зависит тип соединения (внутреннее, левое, полное). - Вкладка
Группировка: перенесите поля для группировки и выберите агрегатные функции для ресурсов. - Вкладка
Условия: добавьте отборы. Для параметра пишите&ИмяПараметра— конструктор сам его объявит. - Вкладка
Дополнительно:ПЕРВЫЕ,РАЗЛИЧНЫЕи тип запроса — выборка данных или создание временной таблицы. - Вкладка
Пакет запросов: здесь добавляют следующие запросы пакета и видят временные таблицы.
У виртуальных таблиц регистров в списке выбранных таблиц есть кнопка параметров. Через неё задают период и условия прямо в параметрах таблицы — это важно для скорости, о чём ниже.
Конструктор не умеет красиво оформлять сложные выражения и иногда переставляет текст по-своему. Рабочая схема: каркас и связи — в конструкторе, вычисляемые поля и тонкие условия — руками в тексте.
Проверять запрос удобно в обработке «Консоль запросов»: текст, параметры и результат на одном экране, без запуска своего кода. Консоль работает в пользовательском режиме, поэтому видит данные с учётом прав текущего пользователя.
Соединения таблиц и виртуальные таблицы регистров
Виды соединений
| Соединение | Что попадёт в результат | Пример задачи |
|---|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | только строки, у которых есть пара в обеих таблицах | товары, по которым есть цена |
ЛЕВОЕ СОЕДИНЕНИЕ | все строки левой таблицы, справа NULL, если пары нет | все товары, цена — если есть |
ПРАВОЕ СОЕДИНЕНИЕ | зеркально левому | используют редко, проще поменять таблицы местами |
ПОЛНОЕ СОЕДИНЕНИЕ | все строки обеих таблиц | сверка двух источников |
Обращение через точку (Документ.Контрагент.ИНН) — тоже соединение, только неявное. Платформа добавит его сама. Одна точка дёшева. Цепочка из трёх-четырёх точек по полям составного типа — это уже десятки соединений в SQL.
Самый тяжёлый случай — поле Регистратор у регистра. Оно ссылается на все документы-регистраторы. Запись Регистратор.Дата заставит платформу соединиться с таблицей каждого из них. Если нужен один вид документа, приведите тип:
ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата
Виртуальные таблицы
У регистров есть готовые таблицы с расчётом на стороне платформы:
- 📦
РегистрНакопления.<Имя>.Остатки(&Период, Условие)— остатки на дату; - 📦
.Обороты(&Начало, &Конец, Периодичность, Условие)— обороты за период; - 📦
.ОстаткиИОбороты(...)— начальный остаток, обороты и конечный остаток одной таблицей; - 📦
РегистрСведений.<Имя>.СрезПоследних(&Период, Условие)— последнее значение на дату (курсы, цены).
Пример — курс валюты на дату через регистр КурсыВалют, который есть в типовых конфигурациях:
ВЫБРАТЬ
Курсы.Курс КАК Курс,
Курсы.Кратность КАК Кратность
ИЗ
РегистрСведений.КурсыВалют.СрезПоследних(&Дата, Валюта = &Валюта) КАК Курсы
Отбор по измерениям ставьте в параметр «Условие» виртуальной таблицы, а не в ГДЕ. Условие в параметре платформа применяет до расчёта остатков и среза. Условие в ГДЕ срабатывает после: сначала посчитаются остатки по всем складам, потом лишнее отбросится. На большой базе разница — секунды против минут.
Временные таблицы и пакетные запросы
Временная таблица — промежуточный результат, который живёт внутри менеджера временных таблиц. Её используют, когда одни и те же данные нужны несколько раз или когда соединение с подзапросом тормозит.
Пример пакета: сначала остатки складов, потом к ним цены. Имена ОстаткиТоваров и Цены здесь условные — подставьте регистры своей конфигурации из дерева метаданных.
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.КоличествоОстаток КАК Остаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
РегистрНакопления.ОстаткиТоваров.Остатки(&Дата, Склад = &Склад) КАК Остатки
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Остатки.Номенклатура КАК Номенклатура,
ВТ_Остатки.Остаток КАК Остаток,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
ВТ_Остатки КАК ВТ_Остатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Цены.СрезПоследних(
&Дата,
Номенклатура В
(ВЫБРАТЬ
Т.Номенклатура
ИЗ
ВТ_Остатки КАК Т)) КАК Цены
ПО ВТ_Остатки.Номенклатура = Цены.Номенклатура
Обратите внимание на приём во втором запросе: срез цен ограничен только теми товарами, что есть на складе. Без этого платформа построит срез по всей номенклатуре базы.
Код выполнения:
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("Склад", Склад);
Результаты = Запрос.ВыполнитьПакет();
Таблица = Результаты[Результаты.ВГраница()].Выгрузить();
ВыполнитьПакет() возвращает массив результатов по каждому запросу пакета, последний — итоговый. Если вызвать просто Выполнить(), вернётся только результат последнего запроса.
Правила для временных таблиц:
- ✅ индексируйте поле, по которому дальше соединяете, через
ИНДЕКСИРОВАТЬ ПО— но только для таблиц от нескольких тысяч строк, на маленьких индекс лишь тратит время; - ✅ удаляйте ненужную таблицу командой
УНИЧТОЖИТЬ ВТ_Остаткив пакете, если менеджер живёт долго; - ✅ один менеджер можно передать нескольким запросам — таблицы из первого видны во втором.
Параметры в запросе вместо склейки строк
Любое значение снаружи — дату, ссылку, строку от пользователя — передавайте параметром:
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Запрос.УстановитьПараметр("Склады", МассивСкладов); // для условия Склад В (&Склады)
В параметр можно передать массив или список значений и использовать его в условии В (&Склады).
Почему не склеивать текст. Запрос не уходит в СУБД напрямую, и классическая SQL-инъекция в лоб не сработает. Но текст запроса 1С изменить можно. Если строка пользователя подставлена в текст в кавычках, ввод вида x" ИЛИ ИСТИНА ИЛИ "1" = "1 превратит отбор в «все записи». Для отчёта с ограничением по контрагенту это утечка данных.
Отдельный случай — поиск по ПОДОБНО. Символы %, _, [, ], ^ в нём служебные. Экранируйте их и указывайте спецсимвол:
Функция ЭкранироватьДляПодобно(Знач Строка)
Строка = СтрЗаменить(Строка, "~", "~~");
Для Каждого Символ Из СтрРазделить("%,_,[,],^", ",") Цикл
Строка = СтрЗаменить(Строка, Символ, "~" + Символ);
КонецЦикла;
Возврат Строка;
КонецФункции
Запрос.Текст = "... ГДЕ Контрагенты.Наименование ПОДОБНО &Шаблон СПЕЦСИМВОЛ ""~""";
Запрос.УстановитьПараметр("Шаблон", "%" + ЭкранироватьДляПодобно(ТекстПоиска) + "%");
Тильду заменяем первой — иначе удвоятся тильды, которые мы сами только что добавили.
И ещё о правах. Если в конфигурации настроено ограничение доступа на уровне записей, пишите ВЫБРАТЬ РАЗРЕШЕННЫЕ. Без этого слова пользователь с ограничениями получит ошибку доступа вместо отфильтрованного результата.
Как найти медленный запрос: замер производительности и технологический журнал
Начинайте с замера. В Конфигураторе запустите отладку, включите Отладка → Замер производительности и выполните медленную операцию. Замер покажет строки кода с временем и числом вызовов. Если строка Запрос.Выполнить() вызвана 3 000 раз — проблема не в тексте запроса, а в цикле вокруг него.
Если тормозит один запрос, нужно увидеть, во что он превратился в СУБД. Для этого включают технологический журнал: файл logcfg.xml кладут в каталог conf установки платформы, например C:\Program Files\1cv8\conf\. Пример, который пишет запросы дольше одной секунды вместе с планами:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\techlog" history="24">
<event>
<eq property="name" value="DBMSSQL"/>
<ge property="duration" value="1000000"/>
</event>
<property name="all"/>
</log>
<plansql/>
</config>
Длительность в журнале 8.3 указывается в микросекундах, поэтому 1 000 000 — это одна секунда. Для PostgreSQL событие называется DBPOSTGRS, а событие SDBL показывает запрос ещё в терминах 1С.
Технологический журнал быстро растёт и нагружает диск сервера. Включайте его на время диагностики, ставьте порог длительности и удаляйте logcfg.xml сразу после сбора данных.
Что искать в плане запроса:
- 🔍 полное сканирование большой таблицы (
Table Scan,Seq Scan) там, где ждали поиска по индексу; - 🔍 вложенные циклы по таблицам на сотни тысяч строк — признак неверной оценки числа строк, часто из-за подзапроса в соединении;
- 🔍 огромное число чтений при маленьком результате — отбор срабатывает поздно, обычно условие стоит в
ГДЕвместо параметров виртуальной таблицы.
В отладчике содержимое временной таблицы можно посмотреть выражением Запрос.МенеджерВременныхТаблиц.Таблицы["ВТ_Остатки"].ПолучитьДанные().Выгрузить() в окне вычисления выражения (Shift+F9). Это помогает понять, на каком шаге пакета строки «размножились».
Ошибки при составлении запросов и как их не допустить
| Ошибка | Чем вредит | Как правильно |
|---|---|---|
| Запрос внутри цикла | тысячи обращений к СУБД | собрать ключи в массив, один запрос с В (&Массив) |
Отбор виртуальной таблицы в ГДЕ | расчёт по всем данным регистра | условие в параметрах таблицы |
| Соединение с подзапросом | СУБД ошибается в оценке строк, план «ломается» | подзапрос во временную таблицу с индексом |
ИЛИ в условии соединения | индекс не используется | разбить на два запроса и ОБЪЕДИНИТЬ ВСЕ |
ОБЪЕДИНИТЬ без нужды | лишняя сортировка для удаления дублей | ОБЪЕДИНИТЬ ВСЕ, если дублей быть не может |
| Цепочки точек по составному типу | десятки неявных соединений | ВЫРАЗИТЬ к нужному типу |
Отбор по полю ПРЕДСТАВЛЕНИЕ(...) | условие по вычисляемой строке, индекс бесполезен | отбор по ссылке, представление — только в выводе |
ВЫБРАТЬ * | лишние поля, включая табличные части | перечислять только нужные поля |
Ещё два приёма, которые экономят время на сопровождении:
- 📝 давайте каждому полю псевдоним
КАК. Без него при изменении текста меняются имена колонок, и падает код, который к ним обращается; - 📝 называйте временные таблицы с префиксом
ВТ_и по смыслу:ВТ_ОстаткиСклада, а неВТ1. Через полгода пакет из восьми запросов иначе не прочитать.
Частые вопросы
Можно ли написать запрос без Конфигуратора?
Язык запросов 1С — это SQL?
Как узнать число строк результата без выгрузки?
Выборка.Количество() у выборки или отдельный запрос с КОЛИЧЕСТВО(*). Для проверки «есть ли хоть одна строка» достаточно Результат.Пустой().Сколько живёт временная таблица?
Как изменить данные запросом?
ПолучитьОбъект(), наборы записей регистров) в коде.