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

· 10 мин

Содержание

Запрос в 1С пишут на встроенном языке запросов и выполняют через объект Запрос: задают текст, передают параметры методом УстановитьПараметр(), вызывают Выполнить() и обходят результат. Текст удобнее всего создавать не руками, а через конструктор: в модуле Конфигуратора щёлкните правой кнопкой мыши и выберите Конструктор запроса....

Типичная ситуация: бухгалтер просит выгрузить продажи по контрагентам за квартал, а готового отчёта нет. Программист открывает Конфигуратор, за пять минут собирает запрос и проверяет его в консоли. Ниже — как писать запросы в 1С так, чтобы они работали быстро и не падали на чужих данных.

Где писать и проверять запрос

Запрос живёт в трёх местах:

  • 🧩 Модуль формы, объекта или общего модуля — запрос выполняется кодом.
  • 📊 Схема компоновки данных — набор данных типа «Запрос» в отчёте.
  • 🔍 Консоль запросов — внешняя обработка для отладки. В неё вставляют текст, задают параметры и сразу видят результат таблицей.

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

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

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

Конструктор вызывают из контекстного меню текстового редактора модуля. Есть два пункта:

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

Если курсор стоит внутри уже готового текста запроса, конструктор откроет его для правки. Если внутри текста есть синтаксическая ошибка, конструктор сначала сообщит о ней.

Основные вкладки окна:

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

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

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

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

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

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

Полная схема выглядит так. Порядок секций строгий, менять его нельзя:

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

Несколько правил, которые экономят время на отладке:

  • 🔤 Ключевые слова можно писать по-английски (SELECT, FROM) — платформа понимает оба варианта. Смешивать в одном запросе не стоит.
  • 🏷️ Каждой таблице давайте псевдоним через КАК. Без него поля из двух таблиц становятся неоднозначными.
  • 🔗 Через точку можно обращаться к реквизитам ссылки: Реализация.Контрагент.ИНН. Это удобно, но каждая точка — неявное соединение с ещё одной таблицей.
  • 📄 Табличную часть документа выбирают как отдельную таблицу: Документ.РеализацияТоваровУслуг.Товары. Поле Ссылка в ней ведёт на сам документ.

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

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

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

Результат можно не только обходить выборкой. Метод Выгрузить() превращает его в таблицу значений, а Пустой() быстро проверяет, вернулось ли хоть что-то.

Параметры и условия отбора

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

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

Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(Дата1));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(Дата2));
Запрос.УстановитьПараметр("Организация", Организация);

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

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

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

Конец периода передавайте через КонецДня(). Если передать просто дату, она придёт с временем 00:00:00, и документы последнего дня выпадут из выборки. Это самая частая причина «у меня в отчёте сумма меньше, чем в журнале».

Для отбора остатков и оборотов регистров условие лучше ставить в параметры виртуальной таблицы:

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

Если то же условие перенести в ГДЕ, платформа сначала посчитает остатки по всем складам, а потом отбросит лишнее. На базе с десятком складов разница во времени заметна.

Соединения таблиц и объединение выборок

Когда данные лежат в разных таблицах, их соединяют. В конструкторе это вкладка «Связи», в тексте — секция ИЗ.

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

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

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

Функция ЕСТЬNULL() здесь обязательна. Без неё у позиций без остатка придёт NULL, и любое сложение с ним в коде даст ошибку или пустое значение.

Отбор по правой таблице левого соединения ставьте в условие ПО, а не в ГДЕ. Условие в ГДЕ вида Остатки.Склад = &Склад выкинет строки, где Остатки пустые, и левое соединение фактически станет внутренним.

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

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

Чтобы получить сумму продаж по контрагенту, строки группируют и сворачивают агрегатными функциями.

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

Агрегатных функций пять, плюс вариант для уникальных значений:

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

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

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

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

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

Сложную выборку удобнее разбить на шаги. Первый запрос кладёт данные во временную таблицу, следующие работают с ней.

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

Что здесь происходит:

  1. ПОМЕСТИТЬ ВТ_Продажи превращает первый запрос в создание временной таблицы.
  2. Точка с запятой отделяет запросы пакета. Строка из косых черт — просто комментарий для читаемости, её вставляет конструктор.
  3. ИНДЕКСИРОВАТЬ ПО строит индекс по полю, по которому дальше идёт соединение.
  4. Виртуальная таблица остатков сразу ограничена номенклатурой из продаж — платформа не считает остатки по всему справочнику.

Выполнить() вернёт результат последнего запроса пакета. Если нужны результаты всех шагов, вызывайте ВыполнитьПакет() — он возвращает массив результатов. Таблица живёт, пока жив менеджер временных таблиц; освободить её раньше можно запросом УНИЧТОЖИТЬ ВТ_Продажи.

Приёмы, которые дают основной выигрыш в скорости:

  • 🚫 Не выполняйте запрос в цикле. Сто запросов по одному документу медленнее одного запроса по ста документам. Соберите ссылки в массив и передайте параметром в условие В (&Ссылки).
  • 🎯 Отбор — в параметры виртуальных таблиц, а не в ГДЕ.
  • 🔗 Меньше точек через составные ссылки. Обращение Регистратор.Контрагент.ИНН, где регистратор может быть документом десяти видов, порождает соединение с каждой таблицей. Уточните тип через ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).
  • 📦 Подзапросы в соединениях заменяйте временными таблицами. Оптимизатор СУБД хуже работает с вложенными запросами в ПО.
  • 🔝 ПЕРВЫЕ N для проверок. Когда нужно лишь узнать, есть ли хоть одна запись, ВЫБРАТЬ ПЕРВЫЕ 1 быстрее полной выборки.
Проверка запроса перед переносом в модуль
Выполнено: 0 / 6

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

Платформа указывает в сообщении строку и колонку ошибки, например {(12, 5)}. Счёт строк идёт от начала текста запроса, а не модуля. Скопируйте текст в консоль или конструктор — так проще найти место.

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

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

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

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