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

· 10 мин

Содержание

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

Частое заблуждение новичка: «язык запросов 1С — это тот же SQL, только по-русски». Похож, но не тот же. В 1С нет UPDATE и DELETE — запрос только читает данные. Зато есть то, чего нет в обычном SQL: обращение к полям через точку (Товары.Ссылка.Дата), виртуальные таблицы регистров с параметрами и ИТОГИ. Если писать запросы «как на SQL», они работают, но медленно и с сюрпризами.

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

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

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

После ВЫБРАТЬ можно указать уточнения в таком порядке: РАЗРЕШЕННЫЕ, РАЗЛИЧНЫЕ, ПЕРВЫЕ N. Например, ВЫБРАТЬ РАЗЛИЧНЫЕ ПЕРВЫЕ 10.

Минимальный рабочий пример — сумма продаж по номенклатуре за период в 1С:Бухгалтерии 3.0:

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

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

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

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

  • 🔹 многострочная строка в модуле продолжается символом | в начале каждой строки;
  • 🔹 параметр в тексте пишут с амперсандом (&НачалоПериода), а в УстановитьПараметр() — без него;
  • 🔹 кавычки внутри текста запроса удваивают: ""ООО"";
  • 🔹 комментарий внутри запроса — //, как в модуле.

Никогда не вклеивайте значения в текст запроса конкатенацией строк. Только параметры. Так запрос не ломается на кавычках в наименованиях, а СУБД может переиспользовать план выполнения.

Вместо Выбрать() результат можно сразу выгрузить: Запрос.Выполнить().Выгрузить() вернёт таблицу значений. Проверить, есть ли вообще строки, — Результат.Пустой(). Это быстрее, чем выгружать таблицу и считать её Количество().

Псевдонимы и имена полей: КАК, точка и составные типы

Ключевое слово КАК даёт имя полю или таблице. Псевдоним таблицы сокращает текст, а псевдоним поля становится именем колонки в выборке. Без него поле СУММА(Товары.Сумма) получит автоматическое имя, и обращаться к нему из кода неудобно.

Правила именования:

  • 🏷 псевдоним — одно слово без пробелов, начинается с буквы;
  • 🏷 зарезервированные слова (Сумма можно, Выбрать — нет) в псевдонимах не используют;
  • 🏷 имена полей в одном запросе не должны повторяться — иначе ошибка о неоднозначности или дубле.

Обращение через точку

Через точку можно «дотянуться» до реквизитов связанного объекта: Товары.Номенклатура.ЕдиницаИзмерения. Платформа сама добавит соединение с таблицей справочника. Удобно, но каждая точка — это скрытый ЛЕВОЕ СОЕДИНЕНИЕ.

Опаснее всего точка через поле составного типа. Поле Регистратор у регистра может ссылаться на десятки видов документов. Запись Движения.Регистратор.Дата заставит платформу соединить таблицу со всеми этими документами. Решение — явно привести тип:

ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Номер КАК НомерРеализации

Если нужны только строки одного вида документа, отберите их оператором ССЫЛКА:

ГДЕ
	Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

Предопределённые значения и перечисления

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

Как отбирать и группировать данные в запросе 1С

Отбор ставят в ГДЕ. Основные операторы условий:

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

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

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

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

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

ГДЕ и ИМЕЮЩИЕ путают постоянно. Разница простая:

  • 🔍 ГДЕ режет исходные строки до свёртки — ГДЕ Товары.Сумма > 1000 оставит строки документов дороже 1000 ₽;
  • 🔍 ИМЕЮЩИЕ режет готовые группы — ИМЕЮЩИЕ СУММА(Товары.Сумма) > 100000 оставит товары, проданные в сумме больше чем на 100 000 ₽.

Пример: из 300 строк реализаций за месяц по 40 товарам ГДЕ с порогом 1000 ₽ отбросит дешёвые строки, а ИМЕЮЩИЕ с порогом 100 000 ₽ оставит, скажем, 6 позиций-лидеров.

Вычисляемые поля

Условную логику пишут через ВЫБОР:

ВЫБОР
	КОГДА Товары.Сумма >= 100000 ТОГДА "Крупная"
	КОГДА Товары.Сумма >= 10000 ТОГДА "Средняя"
	ИНАЧЕ "Мелкая"
КОНЕЦ КАК КатегорияСтроки

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

Соединения таблиц в запросах 1С

Соединение берёт строки двух таблиц и сцепляет их по условию после ПО. Видов четыре:

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

Типичная задача — список всех товаров группы с остатком по счёту 41.01 в Бухгалтерии 3.0, включая товары с нулевым остатком:

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

Что здесь важно:

  1. Справочник стоит слева — поэтому в результат попадут все товары, даже без остатка.
  2. У товаров без остатка поля правой таблицы равны NULL. Функция ЕСТЬNULL() превращает их в ноль, иначе в отчёте будут пустые ячейки, а сложение с NULL даст NULL.
  3. Отбор по счёту и организации стоит в параметрах виртуальной таблицы, а не в ГДЕ. Об этом подробнее в разделе про оптимизацию.

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

Соединение без условия (ПО ИСТИНА) даёт декартово произведение: 1000 товаров × 20 складов = 20 000 строк. Иногда это и нужно — например, чтобы получить матрицу «товар × склад» и потом подтянуть к ней остатки.

Объединение результатов и временные таблицы: как составить пакетный запрос

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

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

  • 📐 одинаковое число полей в каждом запросе;
  • 📐 имена и псевдонимы полей берутся из первого запроса;
  • 📐 УПОРЯДОЧИТЬ ПО и ИТОГИ пишут один раз, в конце всего объединения.

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

Если поля нет в одном из запросов, подставьте заглушку нужного типа: 0 КАК Сумма, ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка) КАК Склад, NULL КАК Комментарий.

Временные таблицы и пакет

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

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Продажи.Номенклатура КАК Номенклатура,
	Продажи.Продано КАК Продано,
	Продажи.Номенклатура.Артикул КАК Артикул
ИЗ
	ВТ_Продажи КАК Продажи
ГДЕ
	Продажи.Продано > 0
;

////////////////////////////////////////////////////////////////////////////////
УНИЧТОЖИТЬ ВТ_Продажи

Запрос.Выполнить() вернёт результат последнего запроса пакета. Нужны результаты всех — вызовите ВыполнитьПакет(): он возвращает массив результатов по порядку. Для запроса УНИЧТОЖИТЬ в массиве тоже будет элемент.

Если временная таблица нужна в другом запросе (например, в соседней процедуре), передайте общий менеджер:

МВТ = Новый МенеджерВременныхТаблиц;
Запрос1.МенеджерВременныхТаблиц = МВТ;
Запрос2.МенеджерВременныхТаблиц = МВТ;

Временные таблицы живут, пока жив менеджер. Если не уничтожить их явно, они освободятся вместе с ним — но в длинных обработках лучше чистить сразу.

Оптимизация: почему запрос тормозит и как его ускорить

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

1. Условия в ГДЕ вместо параметров виртуальной таблицы. Таблицы Остатки, Обороты, ОстаткиИОбороты вычисляются. Условие в параметрах (Остатки(&Дата, Склад = &Склад)) ограничивает выборку до расчёта. Условие в ГДЕ срабатывает после — платформа сначала посчитает остатки по всем складам, потом выбросит лишнее.

2. Запрос в цикле. Классика: перебрать 2000 строк документа и для каждой выполнить запрос остатка. Это 2000 обращений к базе. Правильно — один запрос со списком товаров в параметре В (&Товары) или передать таблицу значений во временную таблицу:

ВЫБРАТЬ
	Т.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
	&ТаблицаТоваров КАК Т

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

3. Точка через составное поле. Разобрали выше: ВЫРАЗИТЬ или ССЫЛКА.

4. Соединение с подзапросом. Вложенный запрос в соединении СУБД оптимизирует плохо: она не знает, сколько там строк. Вынесите подзапрос во временную таблицу с ИНДЕКСИРОВАТЬ ПО по полю соединения.

5. ИЛИ в условии соединения и лишний РАЗЛИЧНЫЕ. ИЛИ мешает использовать индексы — иногда быстрее два запроса через ОБЪЕДИНИТЬ ВСЕ. РАЗЛИЧНЫЕ ставят «на всякий случай», а он заставляет сортировать результат.

Для отладки удобна внешняя обработка «Консоль запросов»: вставляете текст, задаёте параметры, смотрите результат и время выполнения, не перезапуская конфигурацию. В клиент-серверном варианте точную картину даёт технологический журнал и план запроса на стороне СУБД.

Ещё два приёма для быстрого результата: ПЕРВЫЕ 1, когда нужно проверить сам факт наличия записи, и ВЫБРАТЬ только нужных полей вместо ВЫБРАТЬ *. Каждое лишнее поле-ссылка тянет за собой представление.

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

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

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

Отдельная ловушка — граница периода. МЕЖДУ &Начало И &Конец включает обе даты, но если &Конец — это 31.10 без времени, документы, проведённые 31 октября в 14:30, в выборку не войдут. Передавайте КонецДня() или КонецМесяца().

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

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