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

· 9 мин

Содержание

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

Правила ниже общие для платформы 8.3 и не зависят от конфигурации. Имена объектов в примерах взяты из типовых БП 3.0 и УТ 11 — в вашей базе сверяйте их с деревом метаданных.

Из чего состоит запрос и как его вызвать из кода

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

ВЫБРАТЬ
    Контрагенты.Ссылка КАК Контрагент,
    Контрагенты.ИНН КАК ИНН
ИЗ
    Справочник.Контрагенты КАК Контрагенты

Слово КАК задаёт псевдоним. Им удобно называть и источник, и поля: так текст короче, а в результате колонки получают понятные имена.

Полный порядок секций фиксирован. Пропускать необязательные можно, переставлять нельзя:

  1. ВЫБРАТЬ — с уточнениями РАЗРЕШЕННЫЕ, РАЗЛИЧНЫЕ, ПЕРВЫЕ N именно в этом порядке.
  2. ПОМЕСТИТЬ — если результат надо сохранить во временную таблицу.
  3. ИЗ — источники и соединения.
  4. ГДЕ — условия отбора записей.
  5. СГРУППИРОВАТЬ ПО и ИМЕЮЩИЕ — группировка и отбор по итогам групп.
  6. ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕ — присоединение следующего запроса.
  7. УПОРЯДОЧИТЬ ПО — сортировка.
  8. ИТОГИ … ПО — итоговые строки по группировкам.

Ключевые слова не чувствительны к регистру и имеют английские аналоги: SELECT, FROM, WHERE. В типовых конфигурациях пишут по-русски заглавными, так текст легче читать.

Вызов из встроенного языка

Текст запроса — обычная строка. Многострочные строки в 1С продолжают символом | в начале каждой новой строки:

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

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

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

Руками писать текст не обязательно. В модуле Конфигуратора щёлкните правой кнопкой мыши и выберите «Конструктор запроса» — он соберёт текст по галочкам и сам проставит |. Для отладки без программирования есть обработка «Консоль запросов»: вставили текст, задали параметры, увидели результат.

Отбор записей: условия в секции ГДЕ

ГДЕ оставляет только те строки, для которых условие истинно. Значения в условие не вклеивают строкой — их передают параметрами:

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

В коде параметры задают до выполнения:

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

Основные операторы условий:

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

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

Отбор по виртуальным таблицам регистров

У регистров есть виртуальные таблицы Остатки, Обороты, ОстаткиИОбороты, СрезПоследних. Условие для них пишут в скобках — в параметрах таблицы, а не в ГДЕ:

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

Разница существенная. Условие в параметрах ограничивает данные до расчёта остатков. Условие в ГДЕ по тем же полям сначала заставит посчитать остатки по всем складам, а потом отбросит лишнее. На большой базе это разница между секундой и минутой.

Как соединить две таблицы в одном запросе

Соединение пишут в секции ИЗ. Синтаксис: <таблица> <вид> СОЕДИНЕНИЕ <таблица> ПО <условие>.

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

Пример: номенклатура с ценой из регистра сведений. Товары без цены тоже нужны, поэтому соединение левое:

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

ЕСТЬNULL(Поле, 0) (слитно, это функция) подставляет ноль там, где соединение не нашло пары. Без неё в сумме или сравнении NULL даст неожиданный результат.

Классическая ловушка: левое соединение, а потом в ГДЕ условие на поле правой таблицы, например Цены.Цена > 0. Строки без пары получают NULL, условие для них ложно, и соединение по факту превращается во внутреннее. Условие на правую таблицу переносите в ПО или в параметры виртуальной таблицы.

Обращение к полям через точку

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

Группировка и агрегатные функции: суммы и количество

Чтобы получить продажи по контрагентам, строки группируют и считают по ним агрегаты:

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

Агрегатные функции языка запросов:

  • ➕ СУММА() — сумма числовых значений;
  • 🔢 КОЛИЧЕСТВО() и КОЛИЧЕСТВО(РАЗЛИЧНЫЕ …) — число строк или уникальных значений;
  • 📈 МАКСИМУМ(), МИНИМУМ() — крайние значения, работают и с датами;
  • ➗ СРЕДНЕЕ() — среднее арифметическое.

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

ГДЕ и ИМЕЮЩИЕ путают часто. ГДЕ отбирает исходные строки до группировки, ИМЕЮЩИЕ — готовые группы по значению агрегата. Условие «выручка больше 100 000» можно проверить только после суммирования, поэтому оно в ИМЕЮЩИЕ.

Для группировки по месяцам используют НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ): выражение ставят и в ВЫБРАТЬ, и в СГРУППИРОВАТЬ ПО.

Итоги

Секция ИТОГИ добавляет к плоскому результату строки промежуточных сумм — как в отчёте с группировками:

ИТОГИ
    СУММА(Выручка)
ПО
    ОБЩИЕ,
    Контрагент

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

Объединение результатов: ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ

Когда данные лежат в разных объектах, но нужны одним списком, запросы склеивают по вертикали. Пример: поступления и реализации одним реестром.

ВЫБРАТЬ
    Поступление.Ссылка КАК Документ,
    Поступление.Дата КАК Дата,
    Поступление.СуммаДокумента КАК Приход,
    0 КАК Расход
ИЗ
    Документ.ПоступлениеТоваровУслуг КАК Поступление
ГДЕ
    Поступление.Проведен

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

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

УПОРЯДОЧИТЬ ПО
    Дата

Правила объединения:

  • 📐 число полей во всех частях одинаковое, поля сопоставляются по порядку, а не по имени;
  • 🏷 псевдонимы берутся из первого запроса, в следующих их можно не писать;
  • 🧮 недостающее поле заполняют константой — 0, "" или NULL;
  • ↕️ УПОРЯДОЧИТЬ ПО пишут один раз, в самом конце.

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

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

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

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

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

Табличную часть документа читают как Документ.<Имя>.<ИмяТЧ>, а реквизиты шапки — через поле Ссылка. ИНДЕКСИРОВАТЬ ПО имеет смысл для полей, по которым временную таблицу потом соединяют.

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

Когда нужен менеджер временных таблиц

Внутри одного пакета временные таблицы видны всем последующим запросам. Чтобы таблица пережила выполнение и пригодилась второму объекту Запрос, подключают менеджер:

МВТ = Новый МенеджерВременныхТаблиц;
Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МВТ;
// … Запрос1 создаёт ВТ_Продано
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МВТ;
// … Запрос2 читает ВТ_Продано

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

Ненужную таблицу удаляют командой УНИЧТОЖИТЬ ВТ_Продано — это экономит память при длинных пакетах.

Ошибки в тексте запроса и приёмы ускорения

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

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

Что замедляет запросы

  • 🔁 Запрос в цикле. Сотня вызовов по одному товару медленнее одного запроса с условием В (&СписокТоваров). Соберите значения в массив и передайте параметром.
  • 🔗 Длинные цепочки через точку по составным полям. Уточняйте тип через ВЫРАЗИТЬ или выбирайте поле во временную таблицу.
  • 🧱 Условие на виртуальную таблицу в ГДЕ вместо параметров — разобрано выше.
  • 🗂 Соединение с вложенным запросом. Оптимизатор СУБД плохо оценивает его размер. Вынесите вложенный запрос во временную таблицу с индексом.
  • 🔍 ПОДОБНО "%…" с процентом в начале не использует индекс и читает таблицу целиком.

Замерить, где теряется время, помогает режим замера производительности в отладчике Конфигуратора: он показывает время по каждой строке модуля, включая вызов Выполнить().

Проверьте себя на трёх вопросах по синтаксису:

Проверьте себя
Где писать условие «по складу» для виртуальной таблицы Остатки?
Чем ОБЪЕДИНИТЬ отличается от ОБЪЕДИНИТЬ ВСЕ?
Какая функция заменит NULL нулём после левого соединения?

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

Можно ли писать комментарии внутри текста запроса?
Да. Строка, начинающаяся с //, считается комментарием и при выполнении игнорируется. Конструктор запроса при пересборке текста комментарии не сохраняет.
Как вывести вместо ссылки её текстовое представление?
Функцией ПРЕДСТАВЛЕНИЕ(Поле). Она возвращает строку, поэтому соединять и сравнивать по ней не стоит — только выводить.
Что делает слово РАЗРЕШЕННЫЕ?
Оно отбрасывает записи, к которым у пользователя нет доступа по ограничениям на уровне записей. Без него запрос пользователя с ограниченными правами завершится ошибкой доступа.
Как выбрать только первые 10 строк?
Добавьте ПЕРВЫЕ 10 сразу после ВЫБРАТЬ и задайте УПОРЯДОЧИТЬ ПО. Без сортировки «первые» строки окажутся случайными.
Как записать дату прямо в тексте запроса?
Функцией ДАТАВРЕМЯ(2026, 1, 31). Для дат, которые меняются, надёжнее параметр: текст не придётся править.