Запросы на встроенном языке 1С 8.3: от первого ВЫБРАТЬ до пакета с временными таблицами
Содержание
Чтобы создать запрос в 1С, в модуле создают объект Новый Запрос, записывают в свойство Текст строку на языке запросов (ВЫБРАТЬ … ИЗ … ГДЕ …), задают параметры методом УстановитьПараметр() и вызывают Выполнить(). Текст удобнее всего собирать в конструкторе запроса: в Конфигураторе щёлкните правой кнопкой в модуле и выберите Конструктор запроса....
Всё ниже относится к платформе 1С:Предприятие 8.3 и одинаково работает в любой конфигурации: Бухгалтерии, ЗУП, УТ или самописной. Меняются только имена справочников и регистров. Язык запросов только читает данные: записать или удалить им ничего нельзя.
Из чего состоит запрос 1С и как выполнить его из кода
Минимальный рабочий пример: выбрать непомеченные на удаление элементы справочника и обойти результат.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
| Наименование";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Каждая строка текста после первой начинается с вертикальной черты |: так встроенный язык записывает многострочные строковые литералы.
Порядок предложений
Предложения пишутся строго в таком порядке. Обязательны только первые два.
| Предложение | Зачем нужно |
|---|---|
ВЫБРАТЬ | список полей; сюда же ПЕРВЫЕ N, РАЗЛИЧНЫЕ, РАЗРЕШЕННЫЕ |
ИЗ | источники: таблицы, виртуальные таблицы, вложенные запросы |
ГДЕ | отбор строк до группировки |
СГРУППИРОВАТЬ ПО | свёртка строк по полям |
ИМЕЮЩИЕ | отбор уже сгруппированных строк |
ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕ | склейка с ещё одним запросом |
УПОРЯДОЧИТЬ ПО | сортировка |
ИТОГИ ПО | промежуточные и общие итоги |
Что делать с результатом
У РезультатЗапроса три полезных метода:
- 🔁
Выбрать()— построчный обход, самый экономный по памяти; - 📋
Выгрузить()— получитьТаблицуЗначений, если нужно искать, сортировать или передавать данные дальше; - ❓
Пустой()— проверить, вернул ли запрос хоть одну строку, не обходя её.
Нужна только одна строка — пишите ВЫБРАТЬ ПЕРВЫЕ 1, а результат проверяйте так: Если Выборка.Следующий() Тогда. Обходить цикл ради одной записи незачем.
Конструктор запроса: собираем текст без ручного набора
Конструктор открывается в Конфигураторе из контекстного меню модуля. Если курсор стоит внутри готового текста запроса, конструктор откроет его для правки. Пункт Конструктор запроса с обработкой результата... сразу сгенерирует код с Новый Запрос, выполнением и циклом обхода.
Основные вкладки и их назначение:
- Таблицы и поля. Слева дерево метаданных, посередине выбранные таблицы, справа поля. Перетащите таблицу, затем нужные поля. Кнопкой с карандашом над списком полей задаются вычисляемые выражения.
- Связи. Появляется, когда таблиц больше одной. Здесь задают условие соединения и галочки «Все» слева и справа: от них зависит тип соединения.
- Группировка. Поля группировки и суммируемые поля с агрегатной функцией.
- Условия. Отборы
ГДЕ. Галочка «Произвольное» включает ручной ввод выражения. - Дополнительно.
ПЕРВЫЕ,РАЗЛИЧНЫЕ,РАЗРЕШЕННЫЕ, а также выбор «Создание временной таблицы» с её именем. - Объединения/Псевдонимы. Добавляет второй запрос через
ОБЪЕДИНИТЬи сопоставляет поля. - Порядок и Итоги. Сортировка и итоги по группировкам.
- Пакет запросов. Список запросов пакета, если их несколько.
Кнопка Запрос в нижней части окна показывает текст, который получится. Удобно сверять, когда учитесь.
Виртуальная таблица в конструкторе
Таблицы Остатки, Обороты, СрезПоследних принимают параметры. В конструкторе выделите такую таблицу в среднем списке и нажмите кнопку Параметры виртуальной таблицы. Откроется окно, где задаются период и условие. Про то, почему условие нужно писать именно там, — в разделе про скорость.
В пользовательском режиме конструктора нет. Чтобы отлаживать запросы прямо в рабочей базе, используют внешнюю обработку «Консоль запросов». В ней есть свой конструктор, ввод параметров и просмотр результата таблицей.
Параметры запроса и условия отбора
Значения в условие не вклеивают строкой. Их передают параметрами: имя с амперсандом в тексте и значение из кода.
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Документ,
| Реализация.СуммаДокумента КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Проведен
| И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Реализация.Контрагент В (&СписокКонтрагентов)";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("СписокКонтрагентов", МассивКонтрагентов);
Почему параметры, а не склейка строк:
- 🛡️ нет проблем с форматом дат и чисел, кавычками и разделителями;
- 🔗 ссылку на элемент справочника строкой вообще не передать;
- 📦 в параметр
В (&Список)можно положить массив, список значений или колонку таблицы.
Полезные операторы условий
| Задача | Как записать |
|---|---|
| Диапазон дат включительно | Поле МЕЖДУ &Начало И &Конец |
| Значение из списка | Поле В (&Массив) |
| Элемент и все вложенные в группу | Поле В ИЕРАРХИИ (&Группа) |
| Поиск по части строки | Поле ПОДОБНО &Шаблон, шаблон "%молоко%" |
| Проверка на NULL | Поле ЕСТЬ NULL |
| Проверка типа составного поля | Поле ССЫЛКА Документ.ПоступлениеТоваровУслуг |
| Пустая ссылка | Поле = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) |
Конструкция ЗНАЧЕНИЕ() берёт предопределённые элементы и значения перечислений прямо в тексте, без параметра. Например: ЗНАЧЕНИЕ(Перечисление.ВидыДоговоровКонтрагентов.СПоставщиком).
Если в тексте есть &Параметр, а УстановитьПараметр для него не вызван, запрос упадёт при выполнении. Имена сверяются без учёта регистра, но опечатка в одной букве означает новый, незаданный параметр.
Соединения таблиц: какое выбрать
Соединение нужно, когда данные лежат в двух источниках: например, цены в регистре сведений, а остатки в регистре накопления.
| Тип | Что попадёт в результат | Типичный случай |
|---|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | только строки, нашедшие пару в обеих таблицах | товары, у которых есть и остаток, и цена |
ЛЕВОЕ СОЕДИНЕНИЕ | все строки левой таблицы, справа NULL при отсутствии пары | все товары, цена если есть |
ПРАВОЕ СОЕДИНЕНИЕ | зеркально левому | почти не используют, переставляют таблицы |
ПОЛНОЕ СОЕДИНЕНИЕ | все строки обеих таблиц | сверка двух источников: что есть только в одном |
Пример: остатки товаров на дату с ценой, если она задана.
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Количество,
| ЕСТЬNULL(Цены.Цена, 0) КАК Цена
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ) КАК Цены
| ПО Остатки.Номенклатура = Цены.Номенклатура";
Имена регистров здесь условные: в типовых конфигурациях они называются по-своему, подставьте свои из дерева метаданных.
Две ловушки левого соединения
- NULL вместо нуля. Если цены нет, поле
Цены.Ценаравно NULL. Любая арифметика с NULL даёт NULL. Поэтому оборачивайте такие поля вЕСТЬNULL(Поле, 0). - Условие по правой таблице в
ГДЕ. НаписалиГДЕ Цены.ВидЦены = &ВидЦены— и строки без цены пропали, соединение фактически стало внутренним. Отбор по правой таблице переносите вПОили в параметры её виртуальной таблицы.
Соединение «многие ко многим» размножает строки. Если сумма в отчёте вдруг выросла вдвое, первым делом проверьте, не находит ли каждая строка слева по две пары справа.
Группировка, агрегатные функции и итоги
Агрегатные функции сворачивают строки: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Для подсчёта уникальных значений есть КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле).
Правило одно: каждое поле в ВЫБРАТЬ либо стоит под агрегатной функцией, либо перечислено в СГРУППИРОВАТЬ ПО. Иначе конструктор и платформа выдадут ошибку.
Продажи по контрагентам за период, только те, кто купил больше чем на 100 000:
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Контрагент КАК Контрагент,
| КОЛИЧЕСТВО(Реализация.Ссылка) КАК ЧислоДокументов,
| СУММА(Реализация.СуммаДокумента) КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Проведен
| И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|СГРУППИРОВАТЬ ПО
| Реализация.Контрагент
|ИМЕЮЩИЕ
| СУММА(Реализация.СуммаДокумента) > 100000
|УПОРЯДОЧИТЬ ПО
| Сумма УБЫВ";
ГДЕ отсекает документы до свёртки, ИМЕЮЩИЕ — готовые группы после неё. Условие на сумму по контрагенту в ГДЕ не написать: там суммы ещё нет.
Итоги для иерархических отчётов
ИТОГИ СУММА(Сумма) ПО ОБЩИЕ, Контрагент добавляет в результат строки итогов. Обходить такой результат удобно по группировкам:
Выборка = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока Выборка.Следующий() Цикл
// строка общего итога
ВыборкаКонтрагент = Выборка.Выбрать();
Пока ВыборкаКонтрагент.Следующий() Цикл
// строки по контрагентам
КонецЦикла;
КонецЦикла;
Если итоги нужны только для подвала таблицы, проще посчитать их в коде или в схеме компоновки данных. ИТОГИ оправданы там, где результат выводится деревом.
Временные таблицы и пакетный запрос
Сложную выборку легче собрать по шагам. Каждый шаг кладёт результат во временную таблицу, следующий читает её как обычную. Запросы пакета разделяются точкой с запятой.
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Строки.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Строки
|ГДЕ
| Строки.Ссылка = &Документ
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
| &Дата,
| Номенклатура В
| (ВЫБРАТЬ
| ВТ.Номенклатура
| ИЗ
| ВТ_Товары КАК ВТ)) КАК Остатки
| ПО ВТ_Товары.Номенклатура = Остатки.Номенклатура";
Запрос.УстановитьПараметр("Документ", Ссылка);
Запрос.УстановитьПараметр("Дата", Новый Граница(Ссылка.МоментВремени(), ВидГраницы.Исключая));
Выборка = Запрос.Выполнить().Выбрать(); // результат последнего запроса пакета
Что здесь сделано:
- 📥
ПОМЕСТИТЬсоздаёт временную таблицу,ИНДЕКСИРОВАТЬ ПОстроит индекс по полю соединения; - 🎯 остатки читаются только по товарам документа — отбор стоит внутри параметров виртуальной таблицы;
- ⏱️
Новый Граница(..., ВидГраницы.Исключая)даёт остаток на момент документа без его собственных движений.
Выполнить или ВыполнитьПакет
Выполнить() возвращает результат последнего запроса пакета. ВыполнитьПакет() возвращает массив результатов всех запросов по порядку. Для запроса с ПОМЕСТИТЬ элемент массива содержит одну строку с полем Количество — число записанных строк.
Временные таблицы живут, пока жив МенеджерВременныхТаблиц. Один менеджер можно передать в несколько объектов Запрос и переиспользовать таблицы. Таблица больше не нужна — освободите её запросом УНИЧТОЖИТЬ ВТ_Товары или закройте менеджер методом Закрыть().
Как ускорить запрос в 1С
Медленный запрос почти всегда медленный по одной из пяти причин.
- Запрос в цикле. Сто строк документа — сто обращений к базе. Соберите ключи в массив или временную таблицу и получите всё одним запросом с соединением.
- Отбор виртуальной таблицы в
ГДЕ. ЗаписьОстатки(&Дата) ГДЕ Склад = &Складсначала считает остатки по всем складам, потом отбрасывает лишнее. ЗаписьОстатки(&Дата, Склад = &Склад)считает только нужный склад. - Разыменование составных полей через точку.
Регистратор.ДатаилиСубконто1.Наименованиезаставляют платформу соединять таблицы всех возможных типов поля. Сократите набор типов:ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Дата. - Соединение с вложенным запросом. Оптимизатор СУБД плохо оценивает размер подзапроса. Положите его во временную таблицу с индексом и соединяйте уже с ней.
- Лишние строки и поля. Не выбирайте все поля «на всякий случай»; для проверки существования хватит
ПЕРВЫЕ 1.
ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ
ОБЪЕДИНИТЬ убирает одинаковые строки и тратит на это время. Если дублей быть не может или они нужны (две разные продажи на одну сумму), пишите ОБЪЕДИНИТЬ ВСЕ.
РАЗРЕШЕННЫЕ и права
В базах с ограничением доступа на уровне записей запрос без РАЗРЕШЕННЫЕ у пользователя без прав на часть строк завершится ошибкой. С РАЗРЕШЕННЫЕ недоступные строки просто не попадут в результат. В привилегированном режиме и у полноправного пользователя разницы нет.
Ошибки при создании запроса и отладка
Платформа сообщает ошибку запроса с координатами: {(12, 5)} — это строка и колонка внутри текста запроса, а не модуля. Отсчитывайте от строки ВЫБРАТЬ.
Как отлаживать
- 🐞 Поставьте точку останова после
УстановитьПараметри в окне вычисления выражений посмотритеЗапрос.ТекстиЗапрос.Параметры. - 🧪 Вычислите там же
Запрос.Выполнить().Выгрузить()— отладчик покажет таблицу значений целиком. - 📤 Для пакета с временными таблицами вызовите
ВыполнитьПакет()и смотрите каждый элемент массива: так видно, на каком шаге пропали строки. - 🧰 Скопируйте текст с параметрами в «Консоль запросов» и меняйте его по кусочку, пока ошибка не уйдёт.
Медленный запрос в рабочей базе не ускоряйте экспериментами с индексами и настройками СУБД в рабочее время. Сначала воспроизведите проблему на копии базы.
Частые вопросы о запросах 1С
Можно ли запросом изменить или удалить данные?
ПолучитьОбъект(), наборы записей регистров и их метод Записать().Чем запрос отличается от схемы компоновки данных?
Запрос.Как передать в запрос таблицу значений?
ВЫБРАТЬ Т.Поле ПОМЕСТИТЬ ВТ ИЗ &Таблица КАК Т. Колонкам таблицы нужно задать типы через ОписаниеТипов, иначе платформа не примет её.Почему конструктор переписал мой текст?
Где писать запрос: на клиенте или на сервере?
Запрос на тонком и веб-клиенте недоступен, поэтому код выносят в процедуру с директивой &НаСервере или в серверный общий модуль.