Язык запросов 1С 8.3 на практике: от ВЫБРАТЬ до временных таблиц
Содержание
Запрос в 1С пишется на встроенном языке запросов: текст из ключевых слов ВЫБРАТЬ … ИЗ … ГДЕ … кладут в свойство Текст объекта Запрос, значения передают через параметры с амперсандом (&Дата), а результат получают методом Выполнить(). Синтаксис похож на SQL, но обращается не к таблицам СУБД, а к объектам конфигурации: Справочник.Контрагенты, Документ.РеализацияТоваровУслуг, виртуальным таблицам регистров.
Правила ниже общие для платформы 8.3 и не зависят от конфигурации. Имена объектов в примерах взяты из типовых БП 3.0 и УТ 11 — в вашей базе сверяйте их с деревом метаданных.
Из чего состоит запрос и как его вызвать из кода
Минимальный запрос состоит из двух частей: что выбрать и откуда.
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
Контрагенты.ИНН КАК ИНН
ИЗ
Справочник.Контрагенты КАК Контрагенты
Слово КАК задаёт псевдоним. Им удобно называть и источник, и поля: так текст короче, а в результате колонки получают понятные имена.
Полный порядок секций фиксирован. Пропускать необязательные можно, переставлять нельзя:
ВЫБРАТЬ— с уточнениямиРАЗРЕШЕННЫЕ,РАЗЛИЧНЫЕ,ПЕРВЫЕ Nименно в этом порядке.ПОМЕСТИТЬ— если результат надо сохранить во временную таблицу.ИЗ— источники и соединения.ГДЕ— условия отбора записей.СГРУППИРОВАТЬ ПОиИМЕЮЩИЕ— группировка и отбор по итогам групп.ОБЪЕДИНИТЬ/ОБЪЕДИНИТЬ ВСЕ— присоединение следующего запроса.УПОРЯДОЧИТЬ ПО— сортировка.ИТОГИ … ПО— итоговые строки по группировкам.
Ключевые слова не чувствительны к регистру и имеют английские аналоги: SELECT, FROM, WHERE. В типовых конфигурациях пишут по-русски заглавными, так текст легче читать.
Вызов из встроенного языка
Текст запроса — обычная строка. Многострочные строки в 1С продолжают символом | в начале каждой новой строки:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Строка(Выборка.Номенклатура) + " — " + Выборка.Артикул);
КонецЦикла;
Выбрать() даёт построчный обход без загрузки всего результата в память. Если нужна таблица значений целиком, вызывают Выгрузить(). Проверить, что запрос ничего не вернул, можно до обхода: Запрос.Выполнить().Пустой().
Руками писать текст не обязательно. В модуле Конфигуратора щёлкните правой кнопкой мыши и выберите «Конструктор запроса» — он соберёт текст по галочкам и сам проставит |. Для отладки без программирования есть обработка «Консоль запросов»: вставили текст, задали параметры, увидели результат.
Отбор записей: условия в секции ГДЕ
ГДЕ оставляет только те строки, для которых условие истинно. Значения в условие не вклеивают строкой — их передают параметрами:
ВЫБРАТЬ
Реализация.Ссылка КАК Документ,
Реализация.СуммаДокумента КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Реализация.Контрагент = &Контрагент
В коде параметры задают до выполнения:
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);
Основные операторы условий:
| Оператор | Пример | Что делает |
|---|---|---|
=, <>, <, > | Сумма > 10000 | сравнение |
МЕЖДУ … И … | Дата МЕЖДУ &Н И &К | диапазон, границы включены |
В (…) | Склад В (&СписокСкладов) | значение из списка или массива |
В ИЕРАРХИИ | Номенклатура В ИЕРАРХИИ (&Группа) | элемент и всё вложенное в группу |
ПОДОБНО | Наименование ПОДОБНО "%болт%" | поиск по шаблону, % — любые символы |
ЕСТЬ NULL | Цены.Цена ЕСТЬ NULL | проверка отсутствующего значения |
ССЫЛКА | Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг | тип ссылки |
Предопределённые значения и перечисления в тексте запроса записывают через функцию ЗНАЧЕНИЕ(): ГДЕ Товары.СтавкаНДС = ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.НДС20). Если такого значения в конфигурации нет, запрос упадёт при выполнении, поэтому имя проверьте в метаданных.
Отбор по виртуальным таблицам регистров
У регистров есть виртуальные таблицы Остатки, Обороты, ОстаткиИОбороты, СрезПоследних. Условие для них пишут в скобках — в параметрах таблицы, а не в ГДЕ:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, Склад = &Склад) КАК Остатки
Разница существенная. Условие в параметрах ограничивает данные до расчёта остатков. Условие в ГДЕ по тем же полям сначала заставит посчитать остатки по всем складам, а потом отбросит лишнее. На большой базе это разница между секундой и минутой.
Как соединить две таблицы в одном запросе
Соединение пишут в секции ИЗ. Синтаксис: <таблица> <вид> СОЕДИНЕНИЕ <таблица> ПО <условие>.
| Вид | Что попадёт в результат |
|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | только строки, у которых пара нашлась в обеих таблицах |
ЛЕВОЕ СОЕДИНЕНИЕ | все строки левой таблицы; где пары нет — NULL в полях правой |
ПРАВОЕ СОЕДИНЕНИЕ | зеркально левому |
ПОЛНОЕ СОЕДИНЕНИЕ | все строки обеих таблиц |
Пример: номенклатура с ценой из регистра сведений. Товары без цены тоже нужны, поэтому соединение левое:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Номенклатура,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&НаДату, ВидЦены = &ВидЦены) КАК Цены
ПО Номенклатура.Ссылка = Цены.Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
ЕСТЬNULL(Поле, 0) (слитно, это функция) подставляет ноль там, где соединение не нашло пары. Без неё в сумме или сравнении NULL даст неожиданный результат.
Классическая ловушка: левое соединение, а потом в ГДЕ условие на поле правой таблицы, например Цены.Цена > 0. Строки без пары получают NULL, условие для них ложно, и соединение по факту превращается во внутреннее. Условие на правую таблицу переносите в ПО или в параметры виртуальной таблицы.
Обращение к полям через точку
Если нужен реквизит ссылки, соединение писать не обязательно: Реализация.Контрагент.ИНН. Платформа сама построит соединение. Удобно, но у каждой точки есть цена: на поле составного типа (например, Регистратор или Субконто1) платформа соединяет со всеми возможными таблицами типа. Для таких полей сначала уточните тип через ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент.
Группировка и агрегатные функции: суммы и количество
Чтобы получить продажи по контрагентам, строки группируют и считают по ним агрегаты:
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
КОЛИЧЕСТВО(Реализация.Ссылка) КАК Документов,
СУММА(Реализация.СуммаДокумента) КАК Выручка
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
ИМЕЮЩИЕ
СУММА(Реализация.СуммаДокумента) > 100000
УПОРЯДОЧИТЬ ПО
Выручка УБЫВ
Агрегатные функции языка запросов:
- ➕
СУММА()— сумма числовых значений; - 🔢
КОЛИЧЕСТВО()иКОЛИЧЕСТВО(РАЗЛИЧНЫЕ …)— число строк или уникальных значений; - 📈
МАКСИМУМ(),МИНИМУМ()— крайние значения, работают и с датами; - ➗
СРЕДНЕЕ()— среднее арифметическое.
Правило без исключений: каждое поле в ВЫБРАТЬ либо стоит под агрегатной функцией, либо перечислено в СГРУППИРОВАТЬ ПО. Иначе платформа выдаст ошибку ещё при разборе текста.
ГДЕ и ИМЕЮЩИЕ путают часто. ГДЕ отбирает исходные строки до группировки, ИМЕЮЩИЕ — готовые группы по значению агрегата. Условие «выручка больше 100 000» можно проверить только после суммирования, поэтому оно в ИМЕЮЩИЕ.
Для группировки по месяцам используют НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ): выражение ставят и в ВЫБРАТЬ, и в СГРУППИРОВАТЬ ПО.
Итоги
Секция ИТОГИ добавляет к плоскому результату строки промежуточных сумм — как в отчёте с группировками:
ИТОГИ
СУММА(Выручка)
ПО
ОБЩИЕ,
Контрагент
Обходить такой результат удобнее через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
Объединение результатов: ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ
Когда данные лежат в разных объектах, но нужны одним списком, запросы склеивают по вертикали. Пример: поступления и реализации одним реестром.
ВЫБРАТЬ
Поступление.Ссылка КАК Документ,
Поступление.Дата КАК Дата,
Поступление.СуммаДокумента КАК Приход,
0 КАК Расход
ИЗ
Документ.ПоступлениеТоваровУслуг КАК Поступление
ГДЕ
Поступление.Проведен
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Реализация.Ссылка,
Реализация.Дата,
0,
Реализация.СуммаДокумента
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
УПОРЯДОЧИТЬ ПО
Дата
Правила объединения:
- 📐 число полей во всех частях одинаковое, поля сопоставляются по порядку, а не по имени;
- 🏷 псевдонимы берутся из первого запроса, в следующих их можно не писать;
- 🧮 недостающее поле заполняют константой —
0,""илиNULL; - ↕️
УПОРЯДОЧИТЬ ПОпишут один раз, в самом конце.
ОБЪЕДИНИТЬ без ВСЕ убирает одинаковые строки, и на это уходит время. Если дублей быть не может или они нужны, пишите ОБЪЕДИНИТЬ ВСЕ.
Временные таблицы и пакетный запрос
Сложную выборку удобно разбить на шаги. Промежуточный результат кладут во временную таблицу словом ПОМЕСТИТЬ, а запросы пакета разделяют точкой с запятой:
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
СУММА(Товары.Количество) КАК Количество
ПОМЕСТИТЬ ВТ_Продано
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка.Проведен
И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Товары.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Продано.Номенклатура,
ВТ_Продано.Количество,
ЕСТЬNULL(Цены.Цена, 0) * ВТ_Продано.Количество КАК СуммаПоПрайсу
ИЗ
ВТ_Продано КАК ВТ_Продано
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&КонецПериода, ВидЦены = &ВидЦены) КАК Цены
ПО ВТ_Продано.Номенклатура = Цены.Номенклатура
Табличную часть документа читают как Документ.<Имя>.<ИмяТЧ>, а реквизиты шапки — через поле Ссылка. ИНДЕКСИРОВАТЬ ПО имеет смысл для полей, по которым временную таблицу потом соединяют.
Выполнить() вернёт результат последнего запроса пакета. Если нужны все, вызывают ВыполнитьПакет() — он вернёт массив результатов по порядку.
Когда нужен менеджер временных таблиц
Внутри одного пакета временные таблицы видны всем последующим запросам. Чтобы таблица пережила выполнение и пригодилась второму объекту Запрос, подключают менеджер:
МВТ = Новый МенеджерВременныхТаблиц;
Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МВТ;
// … Запрос1 создаёт ВТ_Продано
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МВТ;
// … Запрос2 читает ВТ_Продано
Через параметр во временную таблицу можно загрузить и таблицу значений из кода: ВЫБРАТЬ … ПОМЕСТИТЬ ВТ ИЗ &ТаблицаИзКода КАК Т. Колонкам таблицы значений при этом нужны явно заданные типы, иначе запрос не поймёт, что в них лежит.
Ненужную таблицу удаляют командой УНИЧТОЖИТЬ ВТ_Продано — это экономит память при длинных пакетах.
Ошибки в тексте запроса и приёмы ускорения
Сообщение об ошибке содержит позицию в фигурных скобках: строку и колонку текста запроса. Начинайте поиск с этого места, а не с начала.
Что замедляет запросы
- 🔁 Запрос в цикле. Сотня вызовов по одному товару медленнее одного запроса с условием
В (&СписокТоваров). Соберите значения в массив и передайте параметром. - 🔗 Длинные цепочки через точку по составным полям. Уточняйте тип через
ВЫРАЗИТЬили выбирайте поле во временную таблицу. - 🧱 Условие на виртуальную таблицу в ГДЕ вместо параметров — разобрано выше.
- 🗂 Соединение с вложенным запросом. Оптимизатор СУБД плохо оценивает его размер. Вынесите вложенный запрос во временную таблицу с индексом.
- 🔍
ПОДОБНО "%…"с процентом в начале не использует индекс и читает таблицу целиком.
Замерить, где теряется время, помогает режим замера производительности в отладчике Конфигуратора: он показывает время по каждой строке модуля, включая вызов Выполнить().
Проверьте себя на трёх вопросах по синтаксису:
Частые вопросы
Можно ли писать комментарии внутри текста запроса?
//, считается комментарием и при выполнении игнорируется. Конструктор запроса при пересборке текста комментарии не сохраняет.Как вывести вместо ссылки её текстовое представление?
ПРЕДСТАВЛЕНИЕ(Поле). Она возвращает строку, поэтому соединять и сравнивать по ней не стоит — только выводить.Что делает слово РАЗРЕШЕННЫЕ?
Как выбрать только первые 10 строк?
ПЕРВЫЕ 10 сразу после ВЫБРАТЬ и задайте УПОРЯДОЧИТЬ ПО. Без сортировки «первые» строки окажутся случайными.Как записать дату прямо в тексте запроса?
ДАТАВРЕМЯ(2026, 1, 31). Для дат, которые меняются, надёжнее параметр: текст не придётся править.