Разработка и Конфигурация · 15 мин
Как составлять запросы в 1С: практическое руководство
Практическое руководство по составлению запросов в 1С: синтаксис языка запросов, конструктор, соединения, параметры, группировки и типичные ошибки новичков.
Язык запросов 1С — основной инструмент выборки данных из информационной базы. Он похож на SQL, но работает не с физическими таблицами СУБД, а с объектами метаданных: справочниками, документами, регистрами. Запрос возвращает таблицу значений или выборку, которую программа обходит в цикле, передаёт в отчёт или использует для проверки условий.
Большинство запросов в типовых конфигурациях пишутся один раз и используются годами, поэтому ошибка в запросе дорого обходится: лишние соединения замедляют работу базы, неверные условия приводят к неправильным цифрам в отчётах, а обращение к устаревшим виртуальным таблицам ломает код после обновления конфигурации. Ниже разобраны синтаксис, практические приёмы и контрольные точки, которые помогут писать запросы осознанно.
Материал ориентирован на тех, кто уже знаком с конфигуратором и встроенным языком 1С, но хочет систематизировать работу с запросами: от простого «ВЫБРАТЬ» до соединений, группировок и виртуальных таблиц.
Как устроен запрос в 1С и где его писать
Запрос состоит из двух частей: текста на языке запросов и программного кода, который этот текст выполняет. Текст описывает, какие поля и из каких таблиц нужны, а код создаёт объект Запрос, передаёт параметры и обрабатывает результат. Минимальный каркас выглядит так:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления";
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Писать и отлаживать текст удобнее всего в консоли запросов — внешней обработке, которая выполняет запрос на реальных данных и показывает результат таблицей. В конфигураторе есть встроенный конструктор запроса (правый клик по тексту модуля → «Конструктор запроса»): он позволяет выбрать таблицы и поля мышкой, а затем доработать текст вручную. Конструктор полезен на старте, но сложные запросы с пакетами и временными таблицами в нём редактировать неудобно.
Базовый синтаксис: ВЫБРАТЬ, ИЗ, ГДЕ
Инструкция начинается с ключевого слова ВЫБРАТЬ, за которым перечисляются поля. Источник указывается в секции ИЗ с обязательным псевдонимом через КАК — без псевдонима обращаться к полям в сложных запросах будет невозможно. Условия отбора описываются в секции ГДЕ и поддерживают сравнения, И/ИЛИ/НЕ, операторы В, МЕЖДУ, ПОДОБНО и проверку ЕСТЬ NULL.
Поле Ссылка есть у любого объектного типа (справочника, документа) — через него выполняют сравнения и соединения. Через точку от ссылки можно «разыменовать» поля связанного объекта, например Строки.Номенклатура.СтавкаНДС. Это удобно, но каждое разыменование — скрытое соединение с другой таблицей, и в больших выборках оно заметно замедляет выполнение.
Для сортировки используется УПОРЯДОЧИТЬ ПО, для ограничения количества строк — ВЫБРАТЬ ПЕРВЫЕ N. Если нужны только уникальные комбинации значений, добавьте РАЗЛИЧНЫЕ, но помните: дедупликация выполняется на уровне СУБД и на больших таблицах стоит ресурсов.
Параметры: как передавать условия безопасно
Любое изменяемое значение — дату, контрагента, список номенклатуры — передавайте в запрос параметром, а не подстановкой в текст. Параметр обозначается символом & и заполняется методом УстановитьПараметр() до выполнения:
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка,
| Реализация.СуммаДокумента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Контрагент = &Контрагент
| И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", ТекущаяДата());
Параметры решают сразу две задачи. Во-первых, исключают ошибки форматирования: дата, подставленная строкой, ведёт себя непредсказуемо в зависимости от региональных настроек. Во-вторых, СУБД может повторно использовать скомпилированный план запроса, что ускоряет массовые операции.
Отдельный случай — передача списка значений или массива. Параметр принимает массив напрямую, а в тексте используется конструкция Поле В (&Список). Пустой массив в таком условии не вызовет ошибку, но отберёт ноль строк — проверяйте это при отладке, если запрос «внезапно» ничего не возвращает.
Соединения таблиц и типичные ловушки
Соединения описываются в секции ИЗ: ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ и реже используемые правое и полное. Левое соединение возвращает все строки левой таблицы, подставляя NULL там, где пары не нашлось; внутреннее — только совпавшие пары. Выбор типа соединения — это бизнес-логика: для отчёта «все товары и их остатки, включая нулевые» нужно левое, для «только товары с движениями» — внутреннее.
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка КАК Номенклатура,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
|ИЗ
| Справочник.Номенклатура КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки
| ПО Товары.Ссылка = Остатки.Номенклатура";
Обратите внимание на функцию ЕСТЬNULL(Поле, ЗначениеПоУмолчанию) — она заменяет NULL на конкретное значение. Без неё арифметика с остатками даст NULL вместо нуля, а сравнение Остаток > 0 молча отфильтрует строки без движений. Проверка на NULL — первая вещь, которую стоит сделать при любом левом соединении.
Условия соединения пишутся после ПО. Распространённая ошибка — соединять табличные части документов без условия по ссылке на документ: результатом будет декартово произведение, где каждая строка одной таблицы перемножается с каждой строкой другой. На больших базах такой запрос способен «повесить» сеанс на минуты.
Группировки, итоги и агрегатные функции
Для свёртки данных используется секция СГРУППИРОВАТЬ ПО с агрегатными функциями: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Все неагрегатные поля в списке выборки обязаны попасть в группировку — иначе конструктор выдаст ошибку ещё до выполнения.
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура,
| СУММА(Продажи.Количество) КАК Количество,
| СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
| РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи
|СГРУППИРОВАТЬ ПО
| Продажи.Номенклатура
|УПОРЯДОЧИТЬ ПО
| Сумма УБЫВ";
Условие на сгруппированные данные задаётся через ИМЕЮЩИЕ — например, отобрать только товары с суммой продаж выше порога. Не путайте с ГДЕ: ГДЕ фильтрует строки до группировки, ИМЕЮЩИЕ — уже посчитанные группы. Перенос условия из ИМЕЮЩИЕ в ГДЕ, где это возможно, обычно ускоряет запрос, потому что сокращает объём обрабатываемых строк.
Для построения иерархических отчётов есть секция ИТОГИ ПО — она добавляет в результат группировочные записи по указанным полям. Это удобно для древовидных выборок, но в управляемых формах и СКД итоги чаще считаются средствами самого отчёта, поэтому ИТОГИ оправданы в основном при программной обработке выборки.
Виртуальные таблицы регистров и производительность
Регистры накопления и сведений предоставляют виртуальные таблицы: Остатки, Обороты, ОстаткиИОбороты для регистров накопления, СрезПоследних и СрезПервых для регистров сведений. Параметры этих таблиц указываются в скобках после имени — период, периодичность, условие отбора:
РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаОстатков, Склад = &Склад)
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ТипЦен = &ТипЦен)
Главное правило производительности: отбор задавайте в параметрах виртуальной таблицы, а не в секции ГДЕ после соединения. Условие в параметрах применяется до построения виртуальной таблицы, и платформа читает только нужные записи. То же условие в ГДЕ заставит сначала вычислить срез по всем измерениям, а потом выбросить лишнее.
Второе правило — запрашивать только нужные поля. Конструкция ВЫБРАТЬ Таблица.* тянет все колонки, включая ресурсы и реквизиты, которые вам не нужны, и мешает СУБД использовать покрывающие индексы. На больших регистрах разница между выборкой трёх полей и выборкой всех измеряется кратно.
Как проверить результат
Совпадает с ожиданием на тестовых данных с известным составом
Строк больше, чем записей в источнике — вероятно декартово произведение
Сходятся с типовым отчётом по тем же данным за тот же период
Расхождения — проверьте параметры виртуальной таблицы и границы периода
Числовые поля содержат числа или обработаны через ЕСТЬNULL
NULL в итогах и пропущенные строки в условиях сравнения
Запрос на рабочем объёме данных выполняется за приемлемое для операции время
Резкий рост времени при увеличении периода — вероятно, отбор не в параметрах виртуальной таблицы
Пакетные запросы и временные таблицы
Когда логика сложная — например, сначала отобрать документы, потом собрать их движения, потом свернуть — запрос разбивают на пакет. Отдельные запросы в пакете разделяются точкой с запятой, а промежуточные результаты сохраняются во временные таблицы инструкцией ПОМЕСТИТЬ ИмяТаблицы:
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка
|ПОМЕСТИТЬ ВТ_Документы
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &Начало И &Конец
|;
|ВЫБРАТЬ
| Товары.Номенклатура,
| СУММА(Товары.Количество) КАК Количество
|ИЗ
| ВТ_Документы КАК Документы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК Товары
| ПО Документы.Ссылка = Товары.Ссылка
|СГРУППИРОВАТЬ ПО
| Товары.Номенклатура";
Метод Выполнить() выполняет весь пакет и возвращает результат последнего запроса. Если нужны результаты нескольких запросов пакета, используйте ВыполнитьПакет() — он вернёт массив результатов в порядке следования инструкций. Временные таблицы живут в рамках объекта запроса; чтобы использовать их в нескольких разных объектах, применяется МенеджерВременныхТаблиц.
Не забывайте уничтожать временные таблицы, которые больше не нужны, инструкцией УНИЧТОЖИТЬ ИмяТаблицы в конце пакета. В длительных серверных вызовах накопленные временные таблицы занимают память рабочего процесса и могут замедлять соседние сеансы.
- 1Сформулируйте, какие поля и из каких объектов метаданных нужны, и какой отбор требуется
- 2Соберите текст в консоли запросов на копии базы или тестовых данных
- 3Добавьте параметры вместо всех изменяемых значений и проверьте граничные случаи (пустой список, NULL)
- 4Сверьте результат с типовым отчётом или известными данными
- 5Перенесите текст в модуль, оформив переносы строк через вертикальную черту
- 6Проверьте время выполнения на объёме, близком к рабочему, и при необходимости перенесите условия в параметры виртуальных таблиц
Отладка и проверка результата
Главный инструмент отладки — консоль запросов с возможностью заполнения параметров и просмотра временных таблиц. Если запрос в модуле возвращает не то, что ожидалось, скопируйте его текст в консоль вместе со значениями параметров и выполняйте по частям: сначала первую инструкцию пакета, потом соединения по одному.
Для диагностики производительности на клиент-серверной базе используйте технологический журнал с событиями DBMSSQL/DBPOSTGRS или план запроса на стороне СУБД. Типовой признак проблемы — полное сканирование таблицы там, где ожидался поиск по индексу: обычно это следствие отбора по неиндексированному полю или функции над полем в условии.
Полезная практика — тестировать запрос на трёх наборах данных: пустом (результат должен быть пустым, а не ошибкой), минимальном с известным содержимым и объёмном, близком к рабочему. Первый ловит ошибки типов, второй — логические, третий — проблемы производительности.
Если запрос используется в отчёте, рассмотрите систему компоновки данных вместо программной обработки выборки: тот же текст запроса в СКД получает настройки отбора, группировки и оформления без дополнительного кода, а пользователь сможет менять отчёт самостоятельно.
Частые вопросы
Чем запрос 1С отличается от SQL-запроса к базе напрямую?
Язык запросов 1С работает с объектами метаданных, а не с физическими таблицами, и платформа сама транслирует его в диалект конкретной СУБД. Прямые SQL-запросы к базе обходят механизмы платформы: права, блокировки, итоги регистров — и при записи способны нарушить целостность данных. Чтение напрямую иногда применяют для аналитики, но запись через SQL недопустима.
Почему запрос с условием «Поле = &Параметр» не возвращает строки, хотя данные есть?
Чаще всего причина в несовпадении типов: параметру присвоена строка вместо ссылки, пустая ссылка другого типа или значение NULL. Проверьте тип и значение параметра в отладчике непосредственно перед Выполнить(). Вторая частая причина — NULL в сравниваемом поле: сравнение с NULL всегда ложно, используйте ЕСТЬ NULL или ЕСТЬNULL.
Когда использовать обход выборки, а когда — Выгрузить() в таблицу значений?
Метод Выбрать() дешевле по памяти и подходит для последовательной однократной обработки. Выгрузить() оправдан, когда к данным нужен многократный произвольный доступ: поиск строк, сортировка, свёртка, передача в табличное поле формы. На очень больших результатах выгрузка целиком может израсходовать заметный объём памяти.
Как выбрать данные из табличной части документа?
Табличная часть доступна как отдельный источник через точку: Документ.РеализацияТоваровУслуг.Товары. У неё есть поле Ссылка — ссылка на документ-владелец, по которому табличную часть соединяют с шапкой или фильтруют по документам. Отбор по конкретному документу всегда задавайте через это поле, иначе прочитаются табличные части всех документов вида.
Можно ли в запросе обратиться к данным другой информационной базы?
Штатными средствами языка запросов — нет, запрос работает только с текущей информационной базой. Для межбазовых выборок используют COM-соединение, веб-сервисы, HTTP-сервисы или планы обмена: данные из внешней базы получают отдельно, помещают в таблицу значений и передают в запрос параметром через конструкцию В (&Таблица) с помещением во временную таблицу.
Что делать, если запрос в консоли работает быстро, а в коде медленно?
Проверьте, совпадают ли значения и типы параметров — другой тип параметра может отключить использование индекса. Убедитесь, что в коде не выполняется запрос в цикле: тысяча выполнений быстрого запроса суммарно даст заметную задержку, и правильное решение — один запрос с условием В (&Список) вместо цикла.
Что важно запомнить
Запрос в 1С — это текст на языке запросов плюс объект «Запрос» с параметрами. Передавайте значения только параметрами, отборы по виртуальным таблицам задавайте в их параметрах, при левых соединениях обрабатывайте NULL, а результат проверяйте на тестовых данных с известным составом. Сложную логику разбивайте на пакет с временными таблицами и отлаживайте по частям в консоли запросов.