Язык запросов 1С: учимся писать выборки от простых до сложных

· 9 мин

Содержание

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

Язык запросов похож на SQL, но не равен ему. Он работает с объектами метаданных: справочниками, документами, регистрами. Поэтому вместо имён таблиц СУБД вы пишете Справочник.Номенклатура или Документ.РеализацияТоваровУслуг.

Ниже — путь от первого ВЫБРАТЬ до пакетных запросов с временными таблицами. Все примеры можно вставить в консоль запросов и запустить.

Из чего состоит запрос: базовый синтаксис

Минимальный запрос выглядит так:

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

Порядок секций жёсткий. Сначала ВЫБРАТЬ, потом ИЗ, ГДЕ, СГРУППИРОВАТЬ ПО, ИМЕЮЩИЕ, УПОРЯДОЧИТЬ ПО, ИТОГИ. Переставлять нельзя.

Что дописывают после ВЫБРАТЬ

  • 🔹 ПЕРВЫЕ 10 — вернуть только десять первых строк. Без УПОРЯДОЧИТЬ ПО «первые» окажутся случайными.
  • 🔹 РАЗЛИЧНЫЕ — убрать повторяющиеся строки.
  • 🔹 РАЗРЕШЕННЫЕ — учесть права пользователя на уровне записей (RLS). Без него запрос вернёт всё, даже то, что человеку видеть нельзя.

Параметры вместо подстановки текста

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

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

В коде значение задаёт метод Запрос.УстановитьПараметр("НачалоПериода", Дата). Так платформа сама правильно оформит даты и ссылки, а вы не получите «инъекцию» в строку и лишние перекомпиляции.

Конкретное значение перечисления в тексте запроса пишут функцией ЗНАЧЕНИЕ: ЗНАЧЕНИЕ(Перечисление.ВидыНоменклатуры.Товар). Строкой «Товар» сравнивать нельзя.

Как запрос запускают из кода

Типовая связка такая:

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ... ИЗ ...";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
    // работа с Выборка.Поле
КонецЦикла;

Если нужна таблица целиком, а не обход по строкам, используйте Результат.Выгрузить() — получите таблицу значений.

Виртуальные таблицы регистров

Регистры накопления и сведений хранят данные в нескольких физических таблицах. Платформа предлагает виртуальные таблицы — готовые срезы, которые считаются на стороне СУБД и работают быстрее ручных агрегаций.

Виртуальная таблицаЧто возвращаетДля какого регистра
Остатки(&Дата)остатки на моментнакопления (вид «Остатки»)
Обороты(&Нач, &Кон, Период)приходы и расходы за периоднакопления
ОстаткиИОбороты(...)начало, обороты, конецнакопления (вид «Остатки»)
СрезПоследних(&Дата)последнее значение на датусведений (периодический)
СрезПервых(&Дата)первое значение на датусведений (периодический)

Пример: остатки товаров на складе на конец дня.

ВЫБРАТЬ
    Ост.Номенклатура КАК Номенклатура,
    Ост.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &НаДату,
        Склад = &Склад) КАК Ост

Условие по складу стоит внутри скобок виртуальной таблицы, а не в секции ГДЕ. Это принципиально: платформа применит отбор до расчёта остатков и не будет считать их по всем складам.

Отбор по измерениям регистра ставьте в параметры виртуальной таблицы. Если вынести его в ГДЕ, запрос сперва посчитает всё, а потом выбросит лишнее. На больших базах разница — секунды против минут.

Для Обороты третьим параметром задают периодичность: День, Месяц, Квартал, Год и другие. Без периодичности вы получите один итог за весь интервал.

Соединения таблиц: ЛЕВОЕ, ВНУТРЕННЕЕ и ОБЪЕДИНИТЬ

Две разные операции часто путают. Соединение добавляет столбцы из другой таблицы, объединение добавляет строки.

Виды соединения

  • 🔸 ВНУТРЕННЕЕ СОЕДИНЕНИЕ — остаются только строки, у которых есть пара в обеих таблицах.
  • 🔸 ЛЕВОЕ СОЕДИНЕНИЕ — все строки левой таблицы; где пары нет, поля правой будут NULL.
  • 🔸 ПРАВОЕ СОЕДИНЕНИЕ — зеркально левому. На практике его просто заменяют левым, поменяв таблицы местами.
  • 🔸 ПОЛНОЕ СОЕДИНЕНИЕ — все строки обеих таблиц.

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

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

Функция ЕСТЬNULL подставляет ноль вместо пустоты. Без неё при арифметике с NULL результат тоже станет NULL.

Условие на правую таблицу в ГДЕ превращает левое соединение во внутреннее: строки с NULL отфильтруются. Такое условие переносите в секцию ПО.

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

Когда нужно склеить результаты двух выборок в один список, пишут:

ВЫБРАТЬ Дата, Сумма ИЗ Документ.ПоступлениеТоваровУслуг
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ Дата, Сумма ИЗ Документ.РеализацияТоваровУслуг

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

Группировка, агрегаты и условия: ГДЕ против ИМЕЮЩИЕ

Агрегатные функции сворачивают строки в одну: СУММА, КОЛИЧЕСТВО, СРЕДНЕЕ, МИНИМУМ, МАКСИМУМ. Все поля без агрегата нужно перечислить в СГРУППИРОВАТЬ ПО.

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

Разница между условиями простая:

ГДЕИМЕЮЩИЕ
Когда работаетдо группировкипосле группировки
Что фильтруетисходные строкиготовые итоги
Агрегаты внутринельзяможно

Правило: всё, что можно отсечь по исходным полям, отсекайте в ГДЕ. Это дешевле — группировать придётся меньше строк. В ИМЕЮЩИЕ оставляйте только условия по агрегатам, как СУММА(...) > 100000.

Условные выражения

Для ветвления внутри запроса есть конструкция ВЫБОР:

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

Условия проверяются сверху вниз, срабатывает первое подходящее. Поэтому границы идут от большей к меньшей.

Подзапросы и временные таблицы

Подзапрос — запрос в скобках внутри другого. Его ставят в ИЗ, в ГДЕ или в соединение. Читаются такие конструкции тяжело, и оптимизатор не всегда справляется с глубокой вложенностью.

Временная таблица решает ту же задачу чище. Вы один раз считаете выборку и дальше обращаетесь к ней как к обычной таблице:

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

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

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

Это пакетный запрос: несколько запросов в одном тексте, разделённых точкой с запятой. Результатом считается последний запрос с ВЫБРАТЬ, если вы не обращаетесь к промежуточным через Запрос.ВыполнитьПакет().

Когда временная таблица нужна

  • ✅ одна выборка используется в запросе два и более раза;
  • ✅ нужно сузить большую таблицу перед тяжёлым соединением;
  • ✅ запрос стал нечитаемым из-за вложенных скобок.

Индекс на временной таблице

Если вы соединяете временную таблицу по какому-то полю, добавьте индекс:

ВЫБРАТЬ ... ПОМЕСТИТЬ ВТПродажи ...
ИНДЕКСИРОВАТЬ ПО Контрагент

Для маленьких таблиц он не нужен. Для десятков тысяч строк ускорение заметно.

Чтобы временная таблица жила за пределами одного запроса, используют МенеджерВременныхТаблиц. Присвойте его свойству Запрос.МенеджерВременныхТаблиц и передавайте в следующие запросы.

Как ускорить запрос: проверки перед запуском на большой базе

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

Проверка запроса перед боевым запуском
Выполнено: 0 / 6

Расшифрую то, что не очевидно из списка.

Запрос в цикле

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

Условия, которые ломают индекс

Любая функция над полем в условии мешает СУБД использовать индекс по нему. Условие ПОДСТРОКА(Номенклатура.Код, 1, 3) = "000" читает всю таблицу. Там, где можно, сравнивайте поле напрямую или используйте диапазон.

Оператор ПОДОБНО "%слово%" с процентом в начале — тоже полное сканирование. Применяйте его к уже отфильтрованной выборке.

Составные типы и обращение через точку

Поле составного типа, например Регистратор, может ссылаться на десятки документов. Обращение через точку (Регистратор.Дата) заставит платформу присоединить все возможные таблицы. Если известен конкретный тип, ограничьте его через ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Дата.

План запроса

Если запрос всё равно медленный, смотрите не на текст, а на то, как его исполняет СУБД. Для этого используют технологический журнал платформы с событиями по SQL-запросам либо средства мониторинга СУБД. Они показывают, какой именно кусок запроса съедает время. Настройка журнала — отдельная административная задача, и делать её на рабочей базе стоит осторожно.

Практические примеры: готовые заготовки

Три выборки, которые встречаются чаще всего.

Остатки на дату с названиями

ВЫБРАТЬ
    Ост.Номенклатура.Наименование КАК Товар,
    Ост.Склад КАК Склад,
    Ост.КоличествоОстаток КАК Остаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК Ост
ГДЕ
    Ост.КоличествоОстаток > 0

Последняя цена номенклатуры

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

Документы без движений в регистре

Нужен запрос, который находит проведённые документы, оставившие регистр пустым. Левое соединение с отбором по NULL:

ВЫБРАТЬ
    Док.Ссылка КАК Документ
ИЗ
    Документ.РеализацияТоваровУслуг КАК Док
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи КАК Продажи
    ПО Док.Ссылка = Продажи.Регистратор
ГДЕ
    Док.Проведен
    И Продажи.Регистратор ЕСТЬ NULL

Такой запрос полезен при поиске «битых» проведений. Исправлять данные по его результату стоит только после копии базы.

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

Писать запрос вслепую в модуле — долго. Работайте в консоли запросов: любая обработка с таким названием подойдёт, их несколько в открытом доступе, а в ряде конфигураций есть встроенная. Вы запускаете текст, видите таблицу результата и меняете параметры без перезапуска сеанса.

Типовые сообщения об ошибках и что за ними стоит:

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

Если запрос вернул не то, что ожидалось, сузьте его. Уберите соединения, оставьте одну таблицу и добавляйте части по одной, сверяя число строк на каждом шаге. Так видно, на каком соединении строки размножились или пропали.

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

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

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