К статье
Практический материал

Разработка и Конфигурация · 12 мин

1С: как правильно писать запросы

Как правильно писать запросы в 1С: синтаксис языка запросов, соединения, параметры, индексы, виртуальные таблицы и типичные ошибки разработчиков.

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

Правильное написание запросов в 1С сводится к нескольким принципам: выбирать только нужные поля, фильтровать данные как можно раньше, понимать, как работают виртуальные таблицы, и не забывать про индексы. Ниже разобраны конкретные приёмы с примерами кода, типичные ошибки и способы проверить производительность до того, как запрос попадёт в рабочую базу.

Базовая структура запроса и конструктор запросов

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

Начинающим разработчикам стоит использовать конструктор запроса: правый клик в модуле → «Конструктор запроса». Он формирует корректный синтаксис, подсказывает доступные поля и позволяет визуально настроить соединения и итоги. Даже опытные разработчики используют конструктор для сложных запросов с несколькими соединениями, а затем дорабатывают текст вручную.

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Товары.Ссылка КАК Номенклатура,

| Товары.Код КАК Код

|ИЗ

| Справочник.Номенклатура КАК Товары

|ГДЕ

| НЕ Товары.ПометкаУдаления

|

|УПОРЯДОЧИТЬ ПО

| Товары.Наименование";

Обратите внимание на приёмы в этом примере: таблице задан псевдоним (КАК Товары), поля перечислены явно, а не через «звёздочку», добавлен отбор по пометке удаления и явная сортировка. Это базовая гигиена любого запроса.

Выбирайте только нужные поля и ограничивайте выборку

Конструкция «ВЫБРАТЬ *» в 1С встречается редко, но её аналог — выбор всех полей объекта — частая причина медленных запросов. Каждое лишнее поле увеличивает объём передаваемых данных и может превратить быстрый поиск по индексу в полное сканирование таблицы.

Если нужны только первые N записей — используйте «ВЫБРАТЬ ПЕРВЫЕ n». Это особенно важно для проверок существования данных: вместо выборки всех записей и подсчёта их количества в коде запросите «ПЕРВЫЕ 1» и проверьте, не пуст ли результат.

"ВЫБРАТЬ ПЕРВЫЕ 1

| Заказы.Ссылка

|ИЗ

| Документ.ЗаказПокупателя КАК Заказы

|ГДЕ

| Заказы.Контрагент = &Контрагент

| И Заказы.Проведен"

Ключевое слово РАЗЛИЧНЫЕ применяйте только когда дубли действительно возможны и недопустимы. Различие заставляет СУБД дополнительно сортировать и дедуплицировать результат, что на больших выборках заметно. Если дублей нет по логике данных — уберите РАЗЛИЧНЫЕ.

Типичные ошибки в выборке данных
01
ВЫБРАТЬ все поля объекта, когда нужны два-три реквизита — лишняя нагрузка на сеть и память
02
Подсчёт записей через Результат.Выгрузить().Количество() вместо ПЕРВЫЕ 1 или агрегатной функции КОЛИЧЕСТВО
03
РАЗЛИЧНЫЕ «на всякий случай» — скрывает ошибки в соединениях и замедляет запрос
04
Отсутствие УПОРЯДОЧИТЬ ПО там, где порядок важен: без сортировки порядок строк не гарантирован

Параметры запроса: зачем они нужны и как их передавать

Любые переменные значения — контрагент, период, склад — передавайте в запрос через параметры (&ИмяПараметра), а не подстановкой в строку текста запроса. Параметры исключают ошибки форматирования дат и строк, защищают от некорректных значений и позволяют СУБД повторно использовать скомпилированный план запроса.

Запрос.УстановитьПараметр("Контрагент", КонтрагентСсылка);

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

Для списков значений (например, выборка по нескольким складам) параметром передаётся массив или список значений, а в тексте запроса используется оператор В:

"ГДЕ

| Остатки.Склад В (&СписокСкладов)"

Отдельный случай — передача таблицы значений в запрос. Сначала поместите её во временную таблицу через параметр, затем соединяйтесь с ней в основном запросе. Это стандартный приём для пакетной обработки данных, когда нужно сопоставить большой набор значений с данными базы.

Соединения таблиц: как не потерять данные и производительность

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

Самая частая логическая ошибка — выбор внутреннего соединения там, где нужно левое. Например, отчёт по всем товарам с остатками: при внутреннем соединении товары без движений просто исчезнут из отчёта, и пользователь получит искажённую картину. Если нужны все записи основной таблицы — используйте ЛЕВОЕ СОЕДИНЕНИЕ.

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

"ВЫБРАТЬ

| Товары.Ссылка КАК Товар,

| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток

|ИЗ

| Справочник.Номенклатура КАК Товары

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки

| ПО Товары.Ссылка = Остатки.Номенклатура

|ГДЕ

| НЕ Товары.ПометкаУдаления"

Функция ЕСТЬNULL обязательна при левом соединении, если поле участвует в вычислениях или выводится пользователю: NULL в арифметике даёт NULL, и итоги «поплывут».

Выбор типа соединения

ВНУТРЕННЕЕ

Нужны только записи, присутствующие в обеих таблицах. Быстрее левого, но теряет несовпавшие строки.

ЛЕВОЕ

Нужны все записи основной таблицы независимо от наличия связанных данных. Требует ЕСТЬNULL для полей правой таблицы.

ПОЛНОЕ

Редкий случай — объединение всех записей обеих таблиц. Медленно и почти всегда заменяется левым соединением или объединением.

Виртуальные таблицы регистров: параметры обязательны

Виртуальные таблицы (Остатки, Обороты, СрезПервых, СрезПоследних) — главный источник проблем с производительностью. Ключевое правило: все отборы по измерениям передавайте в параметры виртуальной таблицы (в скобках после её имени), а не в секцию ГДЕ основного запроса.

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

// Правильно — отбор в параметрах виртуальной таблицы:

РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад)

// Неправильно — отбор в ГДЕ после формирования всей таблицы:

РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки

ГДЕ Остатки.Склад = &Склад

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

Контрольные точки

Как проверить корректность работы с виртуальными таблицами

Отборы по измерениям регистра
Норма

Указаны в скобках параметров виртуальной таблицы

Тревожный признак

Вынесены в секцию ГДЕ основного запроса

Период для Остатки/СрезПоследних
Норма

Передан явно первым параметром

Тревожный признак

Период не указан, хотя отчёт строится на конкретную дату

Соединение с виртуальной таблицей
Норма

Виртуальная таблица — правая при левом соединении, отборы в её параметрах

Тревожный признак

Виртуальная таблица соединяется без отборов и фильтруется потом

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

Когда запрос становится сложным — несколько соединений, вложенные подзапросы, промежуточные итоги — разбейте его на пакет с временными таблицами. Временная таблица создаётся конструкцией «ПОМЕСТИТЬ ИмяТаблицы» и живёт в рамках менеджера временных таблиц до явного уничтожения (УНИЧТОЖИТЬ) или завершения выполнения.

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

Запрос.Текст =

"ВЫБРАТЬ

| Продажи.Номенклатура КАК Товар,

| СУММА(Продажи.КоличествоОборот) КАК Продано

|ПОМЕСТИТЬ ВТ_Продажи

|ИЗ

| РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи

|СГРУППИРОВАТЬ ПО

| Продажи.Номенклатура

|ИНДЕКСИРОВАТЬ ПО

| Товар

|;

|ВЫБРАТЬ

| ВТ.Товар,

| ВТ.Продано

|ИЗ

| ВТ_Продажи КАК ВТ";

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

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

Порядок оптимизации медленного запроса
  1. 1
    Воспроизведите проблему на копии базы с объёмом данных, близким к рабочему
  2. 2
    Проверьте, что все отборы по виртуальным таблицам переданы в их параметры, а не в ГДЕ
  3. 3
    Уберите лишние поля, РАЗЛИЧНЫЕ без необходимости и соединения ради одного реквизита
  4. 4
    Убедитесь, что соединения идут по индексированным полям (ссылки, измерения)
  5. 5
    Разбейте сложный запрос на пакет с временными таблицами и ИНДЕКСИРОВАТЬ ПО
  6. 6
    Замерьте время выполнения до и после; при необходимости снимите технологический журнал с событием DBMSSQL/DBPOSTGRS для анализа плана запроса

Проверка результата и типичные ошибки логики

Запрос, который выполняется без ошибок, не обязательно возвращает правильные данные. Перед использованием в прикладном коде проверяйте три вещи: не пуст ли результат (РезультатЗапроса.Пустой()), не появились ли дубли строк из-за соединения «один ко многим» и совпадают ли итоги с контрольными значениями из стандартных отчётов.

Дубли при соединении — классическая ловушка. Если соединить документ с табличной частью по ссылке, каждая строка документа умножится на количество строк табличной части. При последующей группировке с СУММА по полям шапки итоги завысятся кратно. Всегда группируйте детальные данные до соединения с шапкой или соединяйтесь с уже сгруппированным подзапросом.

Для отладки используйте консоль запросов (внешняя обработка, широко распространена в сообществе) — она позволяет выполнить запрос интерактивно, подставить параметры и посмотреть результат без написания кода. В конфигураторе промежуточные результаты удобно смотреть через точку останова и вычисление выражения Результат.Выгрузить() в табло.

Часто задаваемые вопросы

Чем запрос отличается от прямого обхода данных через методы объекта?

Запрос читает данные одним обращением к СУБД и возвращает только нужные поля, тогда как обход ссылок в цикле (Ссылка.ПолучитьОбъект() или обращение через точку) порождает отдельное обращение к базе на каждую запись и загружает объект целиком. Для чтения данных почти всегда правильнее запрос; объектные методы нужны для изменения данных с бизнес-логикой.

Когда использовать ОБЪЕДИНИТЬ, а когда ОБЪЕДИНИТЬ ВСЕ?

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

Как выбрать данные за период, чтобы не ошибиться с границами?

Передавайте границы параметрами и используйте условие «Период МЕЖДУ &Начало И &Конец» или сравнения с КонецДня()/КонецПериода. Частая ошибка — передача даты без времени в качестве конца периода: записи за последний день после 00:00:00 теряются. Для виртуальных таблиц оборотов используйте параметры периода самой таблицы.

Почему запрос в консоли работает быстро, а в отчёте медленно?

Обычно дело в объёме параметров или окружении: в отчёте запрос может выполняться с другими значениями параметров (например, без отбора по организации), с большим периодом или внутри транзакции с блокировками. Проверьте фактические значения параметров в момент выполнения и сравните план запроса в обоих случаях.

Нужно ли писать АВТОУПОРЯДОЧИВАНИЕ в запросах?

АВТОУПОРЯДОЧИВАНИЕ добавляет сортировку по представлениям ссылочных полей, что почти всегда избыточно и замедляет выполнение. Явно указывайте УПОРЯДОЧИТЬ ПО по нужным полям; АВТОУПОРЯДОЧИВАНИЕ оправдано лишь в редких случаях, когда требуется «привычный» порядок ссылок без явного указания полей.

Итог

Что важно запомнить

Хороший запрос в 1С выбирает только нужные поля, использует параметры, передаёт отборы в параметры виртуальных таблиц, соединяет таблицы по индексированным полям и разбивается на временные таблицы при росте сложности. Проверяйте результат на дубли, NULL и реалистичных объёмах данных — до того, как запрос попадёт в рабочую базу.