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

· 10 мин

Содержание

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

Всё ниже относится к платформе 1С:Предприятие 8.3 и одинаково работает в любой конфигурации: Бухгалтерии, ЗУП, УТ или самописной. Меняются только имена справочников и регистров. Язык запросов только читает данные: записать или удалить им ничего нельзя.

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

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

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

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

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

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

Порядок предложений

Предложения пишутся строго в таком порядке. Обязательны только первые два.

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

Что делать с результатом

У РезультатЗапроса три полезных метода:

  • 🔁 Выбрать() — построчный обход, самый экономный по памяти;
  • 📋 Выгрузить() — получить ТаблицуЗначений, если нужно искать, сортировать или передавать данные дальше;
  • ❓ Пустой() — проверить, вернул ли запрос хоть одну строку, не обходя её.

Нужна только одна строка — пишите ВЫБРАТЬ ПЕРВЫЕ 1, а результат проверяйте так: Если Выборка.Следующий() Тогда. Обходить цикл ради одной записи незачем.

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

Конструктор открывается в Конфигураторе из контекстного меню модуля. Если курсор стоит внутри готового текста запроса, конструктор откроет его для правки. Пункт Конструктор запроса с обработкой результата... сразу сгенерирует код с Новый Запрос, выполнением и циклом обхода.

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

  1. Таблицы и поля. Слева дерево метаданных, посередине выбранные таблицы, справа поля. Перетащите таблицу, затем нужные поля. Кнопкой с карандашом над списком полей задаются вычисляемые выражения.
  2. Связи. Появляется, когда таблиц больше одной. Здесь задают условие соединения и галочки «Все» слева и справа: от них зависит тип соединения.
  3. Группировка. Поля группировки и суммируемые поля с агрегатной функцией.
  4. Условия. Отборы ГДЕ. Галочка «Произвольное» включает ручной ввод выражения.
  5. Дополнительно. ПЕРВЫЕ, РАЗЛИЧНЫЕ, РАЗРЕШЕННЫЕ, а также выбор «Создание временной таблицы» с её именем.
  6. Объединения/Псевдонимы. Добавляет второй запрос через ОБЪЕДИНИТЬ и сопоставляет поля.
  7. Порядок и Итоги. Сортировка и итоги по группировкам.
  8. Пакет запросов. Список запросов пакета, если их несколько.

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

Виртуальная таблица в конструкторе

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

В пользовательском режиме конструктора нет. Чтобы отлаживать запросы прямо в рабочей базе, используют внешнюю обработку «Консоль запросов». В ней есть свой конструктор, ввод параметров и просмотр результата таблицей.

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

Значения в условие не вклеивают строкой. Их передают параметрами: имя с амперсандом в тексте и значение из кода.

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

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

Почему параметры, а не склейка строк:

  • 🛡️ нет проблем с форматом дат и чисел, кавычками и разделителями;
  • 🔗 ссылку на элемент справочника строкой вообще не передать;
  • 📦 в параметр В (&Список) можно положить массив, список значений или колонку таблицы.

Полезные операторы условий

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

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

Если в тексте есть &Параметр, а УстановитьПараметр для него не вызван, запрос упадёт при выполнении. Имена сверяются без учёта регистра, но опечатка в одной букве означает новый, незаданный параметр.

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

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

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

Пример: остатки товаров на дату с ценой, если она задана.

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

Имена регистров здесь условные: в типовых конфигурациях они называются по-своему, подставьте свои из дерева метаданных.

Две ловушки левого соединения

  1. NULL вместо нуля. Если цены нет, поле Цены.Цена равно NULL. Любая арифметика с NULL даёт NULL. Поэтому оборачивайте такие поля в ЕСТЬNULL(Поле, 0).
  2. Условие по правой таблице в ГДЕ. Написали ГДЕ Цены.ВидЦены = &ВидЦены — и строки без цены пропали, соединение фактически стало внутренним. Отбор по правой таблице переносите в ПО или в параметры её виртуальной таблицы.

Соединение «многие ко многим» размножает строки. Если сумма в отчёте вдруг выросла вдвое, первым делом проверьте, не находит ли каждая строка слева по две пары справа.

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

Агрегатные функции сворачивают строки: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Для подсчёта уникальных значений есть КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле).

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

Продажи по контрагентам за период, только те, кто купил больше чем на 100 000:

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

ГДЕ отсекает документы до свёртки, ИМЕЮЩИЕ — готовые группы после неё. Условие на сумму по контрагенту в ГДЕ не написать: там суммы ещё нет.

Итоги для иерархических отчётов

ИТОГИ СУММА(Сумма) ПО ОБЩИЕ, Контрагент добавляет в результат строки итогов. Обходить такой результат удобно по группировкам:

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

Если итоги нужны только для подвала таблицы, проще посчитать их в коде или в схеме компоновки данных. ИТОГИ оправданы там, где результат выводится деревом.

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

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

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

Запрос.УстановитьПараметр("Документ", Ссылка);
Запрос.УстановитьПараметр("Дата", Новый Граница(Ссылка.МоментВремени(), ВидГраницы.Исключая));

Выборка = Запрос.Выполнить().Выбрать(); // результат последнего запроса пакета

Что здесь сделано:

  • 📥 ПОМЕСТИТЬ создаёт временную таблицу, ИНДЕКСИРОВАТЬ ПО строит индекс по полю соединения;
  • 🎯 остатки читаются только по товарам документа — отбор стоит внутри параметров виртуальной таблицы;
  • ⏱️ Новый Граница(..., ВидГраницы.Исключая) даёт остаток на момент документа без его собственных движений.

Выполнить или ВыполнитьПакет

Выполнить() возвращает результат последнего запроса пакета. ВыполнитьПакет() возвращает массив результатов всех запросов по порядку. Для запроса с ПОМЕСТИТЬ элемент массива содержит одну строку с полем Количество — число записанных строк.

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

Как ускорить запрос в 1С

Медленный запрос почти всегда медленный по одной из пяти причин.

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

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

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

РАЗРЕШЕННЫЕ и права

В базах с ограничением доступа на уровне записей запрос без РАЗРЕШЕННЫЕ у пользователя без прав на часть строк завершится ошибкой. С РАЗРЕШЕННЫЕ недоступные строки просто не попадут в результат. В привилегированном режиме и у полноправного пользователя разницы нет.

Ошибки при создании запроса и отладка

Платформа сообщает ошибку запроса с координатами: {(12, 5)} — это строка и колонка внутри текста запроса, а не модуля. Отсчитывайте от строки ВЫБРАТЬ.

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

Как отлаживать

  • 🐞 Поставьте точку останова после УстановитьПараметр и в окне вычисления выражений посмотрите Запрос.Текст и Запрос.Параметры.
  • 🧪 Вычислите там же Запрос.Выполнить().Выгрузить() — отладчик покажет таблицу значений целиком.
  • 📤 Для пакета с временными таблицами вызовите ВыполнитьПакет() и смотрите каждый элемент массива: так видно, на каком шаге пропали строки.
  • 🧰 Скопируйте текст с параметрами в «Консоль запросов» и меняйте его по кусочку, пока ошибка не уйдёт.

Медленный запрос в рабочей базе не ускоряйте экспериментами с индексами и настройками СУБД в рабочее время. Сначала воспроизведите проблему на копии базы.

Частые вопросы о запросах 1С

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