Язык запросов 1С 8.3: разбираемся на реальных примерах

· 9 мин

Содержание

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

Язык похож на SQL, но с русскими ключевыми словами и своими объектами — виртуальными таблицами, срезами регистров, параметрами через &. Работает одинаково в Бухгалтерии 3.0, ЗУП 3.1, УТ 11 и любой другой конфигурации на платформе 8.3 — язык принадлежит платформе, а не прикладному решению.

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

Базовый синтаксис: из чего состоит текст запроса

Любой запрос — это последовательность предложений в строго заданном порядке. Пропускать можно почти все, кроме ВЫБРАТЬ и ИЗ.

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

Разберём элементы:

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

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

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты
    |ГДЕ ИНН = &ИНН";
Запрос.УстановитьПараметр("ИНН", "7701234567");
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Ссылка);
КонецЦикла;

Вертикальная черта | в начале строк — это перенос внутри строкового литерала 1С, а не часть запроса. ВЫБРАТЬ РАЗЛИЧНЫЕ убирает дубли строк, ВЫБРАТЬ ПЕРВЫЕ 10 ограничивает выборку первыми десятью строками.

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

К реквизитам ссылочных полей можно обращаться через точку: Номенклатура.ЕдиницаИзмерения.Наименование. Платформа сама подставит нужное соединение, но каждая такая точка — это скрытое левое соединение, и злоупотреблять ими не стоит (об этом ниже).

Виртуальные таблицы и срезы регистров

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

У регистра накопления есть такие виртуальные таблицы:

ТаблицаЧто возвращает
.Остатки(Период, Условие)остатки на момент времени
.Обороты(Начало, Конец, Период, Условие)приход и расход за интервал
.ОстаткиИОбороты(…)начальный остаток, обороты и конечный остаток

Пример — остатки товаров на складе на дату:

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

Для регистров сведений используют срезы — СрезПоследних и СрезПервых. Классика — курс валюты на дату:

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

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

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

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

Когда данные лежат в двух источниках, их соединяют по общему полю. В 1С четыре вида соединений:

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

На практике в 95% случаев используется левое. Типовая задача — вывести весь список номенклатуры и подставить к нему остаток, даже если остатка нет:

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

Условие соединения задаётся после ПО. Обратите внимание на ЕСТЬNULL(Остатки.КоличествоОстаток, 0): по товарам без движений остаток придёт как NULL, и без этой обёртки в арифметике получится NULL, а не ноль.

Нельзя соединять таблицу с подзапросом или виртуальной таблицей «в лоб» на больших данных — платформа не может эффективно построить план. Сложные источники сначала выгружают во временную таблицу, индексируют и только потом соединяют.

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

Чтобы свернуть строки и посчитать итоги , применяют агрегатные функции и СГРУППИРОВАТЬ ПО. Доступны СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ.

Правило простое: каждое поле в ВЫБРАТЬ либо завёрнуто в агрегатную функцию, либо перечислено в СГРУППИРОВАТЬ ПО. Иначе запрос не выполнится.

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

ИМЕЮЩИЕ — это отбор по уже посчитанному итогу (аналог HAVING). Условие на исходные строки ставят в ГДЕ, условие на сумму или количество — в ИМЕЮЩИЕ.

Отдельно стоит КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — считает число уникальных значений, например сколько разных контрагентов было в продажах. А КОЛИЧЕСТВО(*) просто считает строки, не глядя на их содержимое.

Когда суммируемое поле имеет составной тип (например, «Регистратор»), перед агрегатом его приводят к нужному типу через ВЫРАЗИТЬ(Поле КАК Число(15,2)). Без приведения платформа не поймёт, как складывать значения, и выдаст ошибку типа.

Если нужны не плоские итоги, а иерархия с промежуточными суммами, используют предложение ИТОГИ … ПО:

ИТОГИ
    СУММА(Выручка)
ПО
    Номенклатура

Тогда результат обходят по группировкам: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).

Оптимизация: как не посадить базу тяжёлым запросом

Медленный запрос — главная причина тормозов в 1С. Несколько приёмов, которые реально ускоряют выборки:

  • ✅ Отбор — в параметры виртуальной таблицы, а не в ГДЕ. Это самое частое и самое дорогое упущение.
  • ✅ Временные таблицы с индексом. Сложную выборку разбивают на пакет: промежуточный результат кладут в ПОМЕСТИТЬ ВТ_Имя и добавляют ИНДЕКСИРОВАТЬ ПО полям, по которым дальше будет соединение.
  • ✅ Перечисляйте поля явно. ВЫБРАТЬ * тянет лишние колонки и мешает оптимизатору.
  • ✅ Не обращайтесь через точку на несколько уровней (Документ.Контрагент.ЮрЛицо.Наименование) — каждая точка добавляет левое соединение. Лучше явное соединение с нужным справочником.
  • ✅ Никогда не выполняйте запрос в цикле. Если данные нужны по списку объектов, передайте список целиком параметром и получите всё одним запросом.

Пример пакетного запроса с временной таблицей:

ВЫБРАТЬ
    Заказы.Контрагент КАК Контрагент,
    СУММА(Заказы.Сумма) КАК Сумма
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
    Документ.ЗаказКлиента КАК Заказы
ГДЕ
    Заказы.Дата МЕЖДУ &Начало И &Конец
СГРУППИРОВАТЬ ПО
    Заказы.Контрагент
ИНДЕКСИРОВАТЬ ПО
    Контрагент
;
ВЫБРАТЬ
    ВТ_Заказы.Контрагент,
    ВТ_Заказы.Сумма
ИЗ
    ВТ_Заказы КАК ВТ_Заказы

Точка с запятой ; разделяет пакет. Для работы временных таблиц между частями запросу назначают менеджер: Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц.

Чтобы понять, где именно тормозит, есть штатные инструменты. В конфигураторе — Отладка → Замер производительности: покажет, сколько времени съел каждый вызов Выполнить(). Для серверной базы смотрят технологический журнал и план запроса на стороне СУБД. Оптимизируют по фактам из замера, а не на глаз — интуиция здесь ошибается чаще, чем кажется.

Частые ошибки и как их распознать

Большинство проблем с запросами укладываются в несколько повторяющихся сценариев. Вот как они выглядят и что с ними делать:

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

Ещё одна тонкость: функция ЗНАЧЕНИЕ() нужна, когда в условии участвует предопределённый элемент, перечисление или пустая ссылка — например ГДЕ Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Выполнен). Подставлять такие значения строкой нельзя.

Практические примеры сложных выборок

Соберём пару запросов, которые встречаются в реальных отчётах.

Условная логика через ВЫБОР. Нужно пометить товары словами «в наличии» / «нет на складе»:

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

Объединение разнородных данных через ОБЪЕДИНИТЬ ВСЕ. Собрать в одну таблицу приходы и расходы денег:

ВЫБРАТЬ "Поступление" КАК Операция, СУММА(Т.Сумма) КАК Сумма
ИЗ Документ.ПоступлениеНаРасчетныйСчет КАК Т
ГДЕ Т.Дата МЕЖДУ &Начало И &Конец

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

ВЫБРАТЬ "Списание", СУММА(Т.Сумма)
ИЗ Документ.СписаниеСРасчетногоСчета КАК Т
ГДЕ Т.Дата МЕЖДУ &Начало И &Конец

ОБЪЕДИНИТЬ убирает дубли строк, ОБЪЕДИНИТЬ ВСЕ — оставляет всё как есть и работает быстрее. Число и типы полей в объединяемых запросах должны совпадать.

Проверьте, насколько уверенно вы читаете синтаксис:

Проверьте себя
Как получить остатки товаров на дату?
Где правильнее задать отбор по складу для виртуальной таблицы остатков?
Что вернёт поле из правой таблицы при левом соединении, если совпадения нет?

Отлаживать сложные запросы удобно в консоли запросов (обработка из стандартных инструментов разработчика) — там видно результат без запуска кода, и текст можно править на лету.

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

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