Язык запросов 1С 8.3 на практике: от первого ВЫБРАТЬ до пакета с временными таблицами
Содержание
Запрос в 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
ГДЕ
Номенклатура.Ссылка В ИЕРАРХИИ (&Группа)
И НЕ Номенклатура.ЭтоГруппа
Что здесь важно:
- Справочник стоит слева — поэтому в результат попадут все товары, даже без остатка.
- У товаров без остатка поля правой таблицы равны
NULL. ФункцияЕСТЬNULL()превращает их в ноль, иначе в отчёте будут пустые ячейки, а сложение сNULLдастNULL. - Отбор по счёту и организации стоит в параметрах виртуальной таблицы, а не в
ГДЕ. Об этом подробнее в разделе про оптимизацию.
Условие на правую таблицу левого соединения, поставленное в ГДЕ, тихо превращает его во внутреннее. Строки с 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, в выборку не войдут. Передавайте КонецДня() или КонецМесяца().