Язык запросов 1С: от первого ВЫБРАТЬ до оптимизации

· 10 мин

Содержание

Запрос в 1С начинается с ключевого слова ВЫБРАТЬ, перечисления полей, источника после ИЗ и отбора после ГДЕ. Это минимальный рабочий скелет: выбрать такие-то колонки из такой-то таблицы по такому-то условию. Всё остальное — соединения, группировки, временные таблицы — наращивается поверх этих трёх блоков.

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

Все примеры — на языке запросов платформы 8.3 (интерфейс «Такси»). Запросы в 1С нужны почти везде: отчёты на системе компоновки данных, обработки, автозаполнение документов, проверки при проведении, регламентные задания. Принципы одни и те же, меняется только то, куда уходит результат — в таблицу значений, в макет отчёта или в код.

Писать запрос руками с нуля необязательно: в конфигураторе и в расширении есть конструктор запросов (правый клик в модуле → Конструктор запроса), который собирает текст по визуальной схеме. Но понимать синтаксис всё равно нужно — конструктор не подскажет, где вы теряете производительность.

Из чего состоит запрос: ВЫБРАТЬ, ИЗ, ГДЕ

Базовая форма читается почти как фраза на русском. Берём справочник номенклатуры, два поля, отсекаем помеченные на удаление и сортируем по алфавиту:

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

Разберём по строкам:

  • 📋 ВЫБРАТЬ — список полей результата. Каждому полю через КАК даём псевдоним — по нему потом обращаются и в коде, и в других секциях запроса.
  • 🗂 ИЗ ... КАК — источник данных и его короткое имя. Справочник, документ, регистр, табличная часть — всё адресуется через точку: Документ.РеализацияТоваровУслуг, Справочник.Контрагенты.
  • 🔎 ГДЕ — условие отбора строк. Условия соединяются через И, ИЛИ, НЕ.
  • ↕️ УПОРЯДОЧИТЬ ПО — сортировка. Указывают псевдоним поля и при желании УБЫВ или ВОЗР.

Полезные варианты самого ВЫБРАТЬ:

  • ВЫБРАТЬ РАЗЛИЧНЫЕ — убирает полные дубли строк. Удобнее группировки, когда агрегаты не нужны, а дубли мешают.
  • ВЫБРАТЬ ПЕРВЫЕ 10 — ограничивает выборку первыми N строками. Вместе с сортировкой даёт «топ-10».
  • Обращение через точку — Товары.Номенклатура.ЕдиницаИзмерения — разыменовывает ссылку и достаёт реквизит связанного объекта. Удобно, но на условиях отбора это неявное соединение, о чём ниже.

Псевдоним через КАК задавайте всегда и осмысленно. Это не формальность: по псевдониму вы ссылаетесь на поле в ГДЕ, СГРУППИРОВАТЬ ПО, УПОРЯДОЧИТЬ ПО и при чтении выборки в коде.

Условия отбора и параметры запроса

Жёстко вписывать значения в текст запроса нельзя — ни конкретную дату, ни ссылку, ни строку. Для этого есть параметры: имя с амперсандом в тексте, значение — в коде.

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

Значения передаём перед выполнением:

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

Операторы, которые чаще всего нужны в ГДЕ:

ОператорДля чегоПример
=, <>, >, <сравнениеСумма > 0
МЕЖДУ ... И ...диапазон (включая границы)Дата МЕЖДУ &Н И &К
В (...)вхождение в список значенийСтатус В (&Список)
В ИЕРАРХИИ (...)элемент и вся его ветка группыГруппа В ИЕРАРХИИ (&Папка)
ПОДОБНОпоиск по шаблону, % — любой текстНаименование ПОДОБНО "%болт%"
ЕСТЬ NULLпроверка на отсутствие значенияЦена ЕСТЬ NULL

Границы дат в документах — частая ловушка. Если передать &КонецПериода как просто дату без времени, записи за последний день с ненулевым временем не попадут в выборку. Оборачивайте границу в КонецДня() в коде.

Для даты прямо в тексте запроса (например, точка отсчёта «с начала года») есть функция ДАТАВРЕМЯ(2026, 1, 1) — год, месяц, день, при необходимости часы-минуты-секунды.

Соединение таблиц: ЛЕВОЕ, ВНУТРЕННЕЕ, ПОЛНОЕ

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

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

Типичный случай — строки документа и регистр цен к ним:

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

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

Условие соединения пишется после ПО. Не путайте его с ГДЕ: условие в ПО отбирает пары при соединении, а ГДЕ фильтрует уже готовый результат. Для левого соединения это разные вещи — отбор правой таблицы в ГДЕ может «схлопнуть» левое соединение до внутреннего.

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

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

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

Основные агрегаты: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Правила, которые легко нарушить:

  1. Все поля, которые не завёрнуты в агрегатную функцию, обязаны попасть в СГРУППИРОВАТЬ ПО. Иначе платформа вернёт ошибку.
  2. КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) считает число уникальных значений, а КОЛИЧЕСТВО(*) — число строк.
  3. Фильтр по результату агрегата — это ИМЕЮЩИЕ, а не ГДЕ. ГДЕ отбирает строки до свёртки, ИМЕЮЩИЕ — группы после.

Отдельно стоит ИТОГИ ... ПО — многоуровневые итоги прямо в выборке (аналог «подвала» в отчётах). Их обходят методом ВыбратьПоГруппировкам(). Для табличного результата в коде это удобно, но учтите: такой результат уже не плоская таблица, а дерево.

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

Два разных набора строк с одинаковой структурой колонок склеиваются через ОБЪЕДИНИТЬ:

  • ОБЪЕДИНИТЬ — склеивает и попутно убирает полные дубли.
  • ОБЪЕДИНИТЬ ВСЕ — склеивает как есть, дубли остаются. Работает быстрее, потому что не чистит дубли; если их заведомо нет — берите этот вариант.
ВЫБРАТЬ
    "Приход" КАК Операция,
    Поступление.Номенклатура КАК Номенклатура,
    Поступление.Количество КАК Количество
ИЗ
    Документ.ПоступлениеТоваровУслуг.Товары КАК Поступление

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    "Расход",
    Реализация.Номенклатура,
    Реализация.Количество
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Реализация

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

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

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

Чтобы пакет с временными таблицами отработал, запросу нужно отдать менеджер временных таблиц:

Запрос = Новый Запрос(ТекстПакета);
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("Группа", ГруппаТоваров);
Результат = Запрос.ВыполнитьПакет()[1];

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

Как ускорить запрос: приёмы и типичные ошибки

Медленный запрос почти всегда медленный по одной из нескольких предсказуемых причин. Пройдитесь по чек-листу — он закрывает большинство случаев.

Проверка запроса на производительность
Выполнено: 0 / 6

Разберём главные приёмы.

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

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

Отбор по складу внутри скобок виртуальной таблицы заставит платформу отфильтровать данные до расчёта остатков. Тот же отбор в ГДЕ сработает уже после — медленнее и на большем объёме.

Индексация временных таблиц. Если временную таблицу вы потом соединяете по какому-то полю — добавьте ИНДЕКСИРОВАТЬ ПО этому полю при её создании (как в примере выше). Без индекса соединение пойдёт перебором.

Никаких подзапросов в соединениях. Конструкция ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ... ИЗ ...) КАК Т ПО ... — классический тормоз: по подзапросу нельзя построить индекс. Вынесите подзапрос в отдельную временную таблицу пакета и проиндексируйте её.

Осторожно с точкой у составных типов. Обращение ДокументСсылка.Реквизит, где поле имеет составной тип (может ссылаться на разные документы), разворачивается в соединение со всеми возможными таблицами типа. В условии отбора это особенно дорого. Лучше заранее привести тип через ВЫРАЗИТЬ(Поле КАК Документ.РеализацияТоваровУслуг).Реквизит.

Главная ошибка — запрос внутри цикла. Если по коллекции из тысячи элементов вы на каждом шаге выполняете новый запрос, базе уходит тысяча обращений вместо одного. Разворачивайте логику наоборот: загрузите всю нужную номенклатуру во временную таблицу, выполните один запрос с соединением и обойдите готовую выборку циклом. Правило простое — данные получаем запросом, цикл только читает результат. Это самый частый источник тормозов в типовых доработках.

Не тащите лишние поля. Каждое поле в ВЫБРАТЬ — это чтение колонки, а реквизит через точку — ещё и соединение. Если поле в отчёте не показывается и в коде не используется, уберите его. Особенно это касается реквизитов справочников, которые разыменовывают «на всякий случай».

Условную логику в полях удобно описывать через ВЫБОР:

ВЫБОР
    КОГДА Остатки.КоличествоОстаток > 0 ТОГДА "В наличии"
    КОГДА Остатки.КоличествоОстаток = 0 ТОГДА "Закончился"
    ИНАЧЕ "Нет данных"
КОНЕЦ КАК СтатусНаличия

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

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

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

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