Язык запросов 1С на практике: от первой выборки до быстрого кода

· 10 мин

Содержание

Запрос в 1С составляют так: пишут текст на языке запросов (ВЫБРАТЬ … ИЗ … ГДЕ …), передают в него значения через параметры &Имя и выполняют объектом Запрос. Простую выборку быстрее набрать руками, запрос с пятью таблицами и группировками — собрать в конструкторе, а потом довести вручную.

Язык запросов похож на SQL, но работает с объектами конфигурации: справочниками, документами, регистрами. Платформа сама переводит его в запрос к СУБД. Отсюда и главные правила: обращаться к виртуальным таблицам регистров, а не к сырым записям; не выполнять запрос в цикле; не склеивать текст запроса из пользовательского ввода.

Из чего состоит запрос 1С: синтаксис и порядок секций

Секции идут в строгом порядке. Перепутаете — получите синтаксическую ошибку при выполнении.

СекцияЗачем нужнаОбязательна
ВЫБРАТЬсписок полей, плюс РАЗЛИЧНЫЕ, ПЕРВЫЕ N, РАЗРЕШЕННЫЕда
ПОМЕСТИТЬсохранить результат во временную таблицунет
ИЗисточники и соединенияпочти всегда
ГДЕотбор записейнет
СГРУППИРОВАТЬ ПОгруппировка для СУММА, КОЛИЧЕСТВО, МАКСИМУМнет
ИМЕЮЩИЕотбор по результату агрегатных функцийнет
ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕсклеить результаты нескольких выборокнет
УПОРЯДОЧИТЬ ПОсортировканет
ИТОГИ ПОитоговые строки по группировкамнет

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

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

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

Строки текста внутри кода продолжаются символом |. Кавычки внутри текста запроса удваиваются: ""Москва"".

Функции, без которых не обходится почти ни один запрос:

  • 🔹 ЕСТЬNULL(Поле, 0) — подставить значение вместо NULL после левого соединения;
  • 🔹 ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ — условие внутри поля;
  • 🔹 НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) и КОНЕЦПЕРИОДА — границы периода;
  • 🔹 ВЫРАЗИТЬ(Поле КАК Документ.РеализацияТоваровУслуг) — привести составной тип к одному;
  • 🔹 ПОДСТРОКА, ДАТАВРЕМЯ(2025, 1, 1), ТИПЗНАЧЕНИЯ, ССЫЛКА — для строк, дат и проверки типа.

Сравнение с NULL через знак = не работает. Пишите Поле ЕСТЬ NULL или Поле ЕСТЬ НЕ NULL.

Как выполнить запрос из кода: выборка, выгрузка, пустой результат

Результат запроса можно обойти тремя способами. Выбор зависит от того, что дальше делать с данными.

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

Для проверки «есть ли данные» не выгружайте таблицу ради Количество(). Пустой() дешевле, а в самом запросе добавьте ПЕРВЫЕ 1.

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

Конструктор запроса: собрать текст без ручного набора

Конструктор открывают в Конфигураторе или 1С:EDT прямо из модуля. Поставьте курсор в место, где будет текст, и выберите в контекстном меню Конструктор запроса. Если курсор стоит внутри уже написанного текста запроса, конструктор разберёт его и откроет на редактирование.

Второй пункт рядом — Конструктор запроса с обработкой результата. Он сразу генерирует код: создание объекта Запрос, установку параметров и цикл обхода выборки. Для новой процедуры это самый быстрый старт.

Порядок работы в конструкторе:

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

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

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

Проверять запрос удобно в обработке «Консоль запросов»: текст, параметры и результат на одном экране, без запуска своего кода. Консоль работает в пользовательском режиме, поэтому видит данные с учётом прав текущего пользователя.

Соединения таблиц и виртуальные таблицы регистров

Виды соединений

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

Обращение через точку (Документ.Контрагент.ИНН) — тоже соединение, только неявное. Платформа добавит его сама. Одна точка дёшева. Цепочка из трёх-четырёх точек по полям составного типа — это уже десятки соединений в SQL.

Самый тяжёлый случай — поле Регистратор у регистра. Оно ссылается на все документы-регистраторы. Запись Регистратор.Дата заставит платформу соединиться с таблицей каждого из них. Если нужен один вид документа, приведите тип:

ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата

Виртуальные таблицы

У регистров есть готовые таблицы с расчётом на стороне платформы:

  • 📦 РегистрНакопления.<Имя>.Остатки(&Период, Условие) — остатки на дату;
  • 📦 .Обороты(&Начало, &Конец, Периодичность, Условие) — обороты за период;
  • 📦 .ОстаткиИОбороты(...) — начальный остаток, обороты и конечный остаток одной таблицей;
  • 📦 РегистрСведений.<Имя>.СрезПоследних(&Период, Условие) — последнее значение на дату (курсы, цены).

Пример — курс валюты на дату через регистр КурсыВалют, который есть в типовых конфигурациях:

ВЫБРАТЬ
	Курсы.Курс КАК Курс,
	Курсы.Кратность КАК Кратность
ИЗ
	РегистрСведений.КурсыВалют.СрезПоследних(&Дата, Валюта = &Валюта) КАК Курсы

Отбор по измерениям ставьте в параметр «Условие» виртуальной таблицы, а не в ГДЕ. Условие в параметре платформа применяет до расчёта остатков и среза. Условие в ГДЕ срабатывает после: сначала посчитаются остатки по всем складам, потом лишнее отбросится. На большой базе разница — секунды против минут.

Временные таблицы и пакетные запросы

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

Пример пакета: сначала остатки складов, потом к ним цены. Имена ОстаткиТоваров и Цены здесь условные — подставьте регистры своей конфигурации из дерева метаданных.

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

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

Обратите внимание на приём во втором запросе: срез цен ограничен только теми товарами, что есть на складе. Без этого платформа построит срез по всей номенклатуре базы.

Код выполнения:

Запрос = Новый Запрос(ТекстЗапроса);
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("Склад", Склад);

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

ВыполнитьПакет() возвращает массив результатов по каждому запросу пакета, последний — итоговый. Если вызвать просто Выполнить(), вернётся только результат последнего запроса.

Правила для временных таблиц:

  • ✅ индексируйте поле, по которому дальше соединяете, через ИНДЕКСИРОВАТЬ ПО — но только для таблиц от нескольких тысяч строк, на маленьких индекс лишь тратит время;
  • ✅ удаляйте ненужную таблицу командой УНИЧТОЖИТЬ ВТ_Остатки в пакете, если менеджер живёт долго;
  • ✅ один менеджер можно передать нескольким запросам — таблицы из первого видны во втором.

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

Любое значение снаружи — дату, ссылку, строку от пользователя — передавайте параметром:

Запрос.УстановитьПараметр("Контрагент", Контрагент);
Запрос.УстановитьПараметр("Склады", МассивСкладов); // для условия Склад В (&Склады)

В параметр можно передать массив или список значений и использовать его в условии В (&Склады).

Почему не склеивать текст. Запрос не уходит в СУБД напрямую, и классическая SQL-инъекция в лоб не сработает. Но текст запроса 1С изменить можно. Если строка пользователя подставлена в текст в кавычках, ввод вида x" ИЛИ ИСТИНА ИЛИ "1" = "1 превратит отбор в «все записи». Для отчёта с ограничением по контрагенту это утечка данных.

Отдельный случай — поиск по ПОДОБНО. Символы %, _, [, ], ^ в нём служебные. Экранируйте их и указывайте спецсимвол:

Функция ЭкранироватьДляПодобно(Знач Строка)
	Строка = СтрЗаменить(Строка, "~", "~~");
	Для Каждого Символ Из СтрРазделить("%,_,[,],^", ",") Цикл
		Строка = СтрЗаменить(Строка, Символ, "~" + Символ);
	КонецЦикла;
	Возврат Строка;
КонецФункции

Запрос.Текст = "... ГДЕ Контрагенты.Наименование ПОДОБНО &Шаблон СПЕЦСИМВОЛ ""~""";
Запрос.УстановитьПараметр("Шаблон", "%" + ЭкранироватьДляПодобно(ТекстПоиска) + "%");

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

И ещё о правах. Если в конфигурации настроено ограничение доступа на уровне записей, пишите ВЫБРАТЬ РАЗРЕШЕННЫЕ. Без этого слова пользователь с ограничениями получит ошибку доступа вместо отфильтрованного результата.

Проверьте себя
Где лучше задать отбор по складу для таблицы остатков?
Как передать в запрос список из 20 складов?
Что вернёт Выполнить() у пакета из трёх запросов?

Как найти медленный запрос: замер производительности и технологический журнал

Начинайте с замера. В Конфигураторе запустите отладку, включите Отладка → Замер производительности и выполните медленную операцию. Замер покажет строки кода с временем и числом вызовов. Если строка Запрос.Выполнить() вызвана 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?
Нет, это собственный язык платформы с похожим синтаксисом. Он работает с объектами метаданных, поддерживает русские и английские ключевые слова и переводится платформой в SQL конкретной СУБД.
Как узнать число строк результата без выгрузки?
Используйте Выборка.Количество() у выборки или отдельный запрос с КОЛИЧЕСТВО(*). Для проверки «есть ли хоть одна строка» достаточно Результат.Пустой().
Сколько живёт временная таблица?
Пока существует менеджер временных таблиц. Если менеджер не задан явно, таблица живёт только в рамках одного пакета и исчезает после его выполнения.
Как изменить данные запросом?
Никак: язык запросов 1С только читает данные. Изменения делают через объекты (ПолучитьОбъект(), наборы записей регистров) в коде.