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

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

Как составлять запросы в 1С: практическое руководство

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

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

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

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

Как устроен запрос в 1С и где его писать

Запрос состоит из двух частей: текста на языке запросов и программного кода, который этот текст выполняет. Текст описывает, какие поля и из каких таблиц нужны, а код создаёт объект Запрос, передаёт параметры и обрабатывает результат. Минимальный каркас выглядит так:

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

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

"ВЫБРАТЬ

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

| Номенклатура.Наименование

|ИЗ

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

|ГДЕ

| НЕ Номенклатура.ПометкаУдаления";

Результат = Запрос.Выполнить();

Выборка = Результат.Выбрать();

Пока Выборка.Следующий() Цикл

Сообщить(Выборка.Наименование);

КонецЦикла;

Писать и отлаживать текст удобнее всего в консоли запросов — внешней обработке, которая выполняет запрос на реальных данных и показывает результат таблицей. В конфигураторе есть встроенный конструктор запроса (правый клик по тексту модуля → «Конструктор запроса»): он позволяет выбрать таблицы и поля мышкой, а затем доработать текст вручную. Конструктор полезен на старте, но сложные запросы с пакетами и временными таблицами в нём редактировать неудобно.

Базовый синтаксис: ВЫБРАТЬ, ИЗ, ГДЕ

Инструкция начинается с ключевого слова ВЫБРАТЬ, за которым перечисляются поля. Источник указывается в секции ИЗ с обязательным псевдонимом через КАК — без псевдонима обращаться к полям в сложных запросах будет невозможно. Условия отбора описываются в секции ГДЕ и поддерживают сравнения, И/ИЛИ/НЕ, операторы В, МЕЖДУ, ПОДОБНО и проверку ЕСТЬ NULL.

Поле Ссылка есть у любого объектного типа (справочника, документа) — через него выполняют сравнения и соединения. Через точку от ссылки можно «разыменовать» поля связанного объекта, например Строки.Номенклатура.СтавкаНДС. Это удобно, но каждое разыменование — скрытое соединение с другой таблицей, и в больших выборках оно заметно замедляет выполнение.

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

Параметры: как передавать условия безопасно

Любое изменяемое значение — дату, контрагента, список номенклатуры — передавайте в запрос параметром, а не подстановкой в текст. Параметр обозначается символом & и заполняется методом УстановитьПараметр() до выполнения:

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

"ВЫБРАТЬ

| Реализация.Ссылка,

| Реализация.СуммаДокумента

|ИЗ

| Документ.РеализацияТоваровУслуг КАК Реализация

|ГДЕ

| Реализация.Контрагент = &Контрагент

| И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода";

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

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

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

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

Отдельный случай — передача списка значений или массива. Параметр принимает массив напрямую, а в тексте используется конструкция Поле В (&Список). Пустой массив в таком условии не вызовет ошибку, но отберёт ноль строк — проверяйте это при отладке, если запрос «внезапно» ничего не возвращает.

Соединения таблиц и типичные ловушки

Соединения описываются в секции ИЗ: ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ и реже используемые правое и полное. Левое соединение возвращает все строки левой таблицы, подставляя NULL там, где пары не нашлось; внутреннее — только совпавшие пары. Выбор типа соединения — это бизнес-логика: для отчёта «все товары и их остатки, включая нулевые» нужно левое, для «только товары с движениями» — внутреннее.

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

"ВЫБРАТЬ

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

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

|ИЗ

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

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

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

Обратите внимание на функцию ЕСТЬNULL(Поле, ЗначениеПоУмолчанию) — она заменяет NULL на конкретное значение. Без неё арифметика с остатками даст NULL вместо нуля, а сравнение Остаток > 0 молча отфильтрует строки без движений. Проверка на NULL — первая вещь, которую стоит сделать при любом левом соединении.

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

Типичные ошибки
01
Соединение без условия ПО по ключевому полю — декартово произведение и зависание запроса
02
Забытый ЕСТЬNULL при левом соединении — NULL в итогах и потерянные строки в условиях
03
Обращение к виртуальной таблице без параметров периода — чтение данных за всё время
04
Отбор по разыменованному полю (Документ.Контрагент.ИНН) вместо параметра — сканирование вместо поиска по индексу
05
Использование РАЗЛИЧНЫЕ для маскировки дублей от неверного соединения вместо исправления самого соединения

Группировки, итоги и агрегатные функции

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

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

"ВЫБРАТЬ

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

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

| СУММА(Продажи.Сумма) КАК Сумма

|ИЗ

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

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

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

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

| Сумма УБЫВ";

Условие на сгруппированные данные задаётся через ИМЕЮЩИЕ — например, отобрать только товары с суммой продаж выше порога. Не путайте с ГДЕ: ГДЕ фильтрует строки до группировки, ИМЕЮЩИЕ — уже посчитанные группы. Перенос условия из ИМЕЮЩИЕ в ГДЕ, где это возможно, обычно ускоряет запрос, потому что сокращает объём обрабатываемых строк.

Для построения иерархических отчётов есть секция ИТОГИ ПО — она добавляет в результат группировочные записи по указанным полям. Это удобно для древовидных выборок, но в управляемых формах и СКД итоги чаще считаются средствами самого отчёта, поэтому ИТОГИ оправданы в основном при программной обработке выборки.

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

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

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

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

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

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

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

Как проверить результат

Количество строк результата
Норма

Совпадает с ожиданием на тестовых данных с известным составом

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

Строк больше, чем записей в источнике — вероятно декартово произведение

Суммы и остатки
Норма

Сходятся с типовым отчётом по тем же данным за тот же период

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

Расхождения — проверьте параметры виртуальной таблицы и границы периода

Значения NULL в числовых полях
Норма

Числовые поля содержат числа или обработаны через ЕСТЬNULL

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

NULL в итогах и пропущенные строки в условиях сравнения

Время выполнения
Норма

Запрос на рабочем объёме данных выполняется за приемлемое для операции время

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

Резкий рост времени при увеличении периода — вероятно, отбор не в параметрах виртуальной таблицы

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

Когда логика сложная — например, сначала отобрать документы, потом собрать их движения, потом свернуть — запрос разбивают на пакет. Отдельные запросы в пакете разделяются точкой с запятой, а промежуточные результаты сохраняются во временные таблицы инструкцией ПОМЕСТИТЬ ИмяТаблицы:

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

"ВЫБРАТЬ

| Реализация.Ссылка

|ПОМЕСТИТЬ ВТ_Документы

|ИЗ

| Документ.РеализацияТоваровУслуг КАК Реализация

|ГДЕ

| Реализация.Дата МЕЖДУ &Начало И &Конец

|;

|ВЫБРАТЬ

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

| СУММА(Товары.Количество) КАК Количество

|ИЗ

| ВТ_Документы КАК Документы

| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК Товары

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

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

| Товары.Номенклатура";

Метод Выполнить() выполняет весь пакет и возвращает результат последнего запроса. Если нужны результаты нескольких запросов пакета, используйте ВыполнитьПакет() — он вернёт массив результатов в порядке следования инструкций. Временные таблицы живут в рамках объекта запроса; чтобы использовать их в нескольких разных объектах, применяется МенеджерВременныхТаблиц.

Не забывайте уничтожать временные таблицы, которые больше не нужны, инструкцией УНИЧТОЖИТЬ ИмяТаблицы в конце пакета. В длительных серверных вызовах накопленные временные таблицы занимают память рабочего процесса и могут замедлять соседние сеансы.

Порядок разработки запроса
  1. 1
    Сформулируйте, какие поля и из каких объектов метаданных нужны, и какой отбор требуется
  2. 2
    Соберите текст в консоли запросов на копии базы или тестовых данных
  3. 3
    Добавьте параметры вместо всех изменяемых значений и проверьте граничные случаи (пустой список, NULL)
  4. 4
    Сверьте результат с типовым отчётом или известными данными
  5. 5
    Перенесите текст в модуль, оформив переносы строк через вертикальную черту
  6. 6
    Проверьте время выполнения на объёме, близком к рабочему, и при необходимости перенесите условия в параметры виртуальных таблиц

Отладка и проверка результата

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

Для диагностики производительности на клиент-серверной базе используйте технологический журнал с событиями DBMSSQL/DBPOSTGRS или план запроса на стороне СУБД. Типовой признак проблемы — полное сканирование таблицы там, где ожидался поиск по индексу: обычно это следствие отбора по неиндексированному полю или функции над полем в условии.

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

Если запрос используется в отчёте, рассмотрите систему компоновки данных вместо программной обработки выборки: тот же текст запроса в СКД получает настройки отбора, группировки и оформления без дополнительного кода, а пользователь сможет менять отчёт самостоятельно.
ПЗ
Практическое замечание

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

Чем запрос 1С отличается от SQL-запроса к базе напрямую?

Язык запросов 1С работает с объектами метаданных, а не с физическими таблицами, и платформа сама транслирует его в диалект конкретной СУБД. Прямые SQL-запросы к базе обходят механизмы платформы: права, блокировки, итоги регистров — и при записи способны нарушить целостность данных. Чтение напрямую иногда применяют для аналитики, но запись через SQL недопустима.

Почему запрос с условием «Поле = &Параметр» не возвращает строки, хотя данные есть?

Чаще всего причина в несовпадении типов: параметру присвоена строка вместо ссылки, пустая ссылка другого типа или значение NULL. Проверьте тип и значение параметра в отладчике непосредственно перед Выполнить(). Вторая частая причина — NULL в сравниваемом поле: сравнение с NULL всегда ложно, используйте ЕСТЬ NULL или ЕСТЬNULL.

Когда использовать обход выборки, а когда — Выгрузить() в таблицу значений?

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

Как выбрать данные из табличной части документа?

Табличная часть доступна как отдельный источник через точку: Документ.РеализацияТоваровУслуг.Товары. У неё есть поле Ссылка — ссылка на документ-владелец, по которому табличную часть соединяют с шапкой или фильтруют по документам. Отбор по конкретному документу всегда задавайте через это поле, иначе прочитаются табличные части всех документов вида.

Можно ли в запросе обратиться к данным другой информационной базы?

Штатными средствами языка запросов — нет, запрос работает только с текущей информационной базой. Для межбазовых выборок используют COM-соединение, веб-сервисы, HTTP-сервисы или планы обмена: данные из внешней базы получают отдельно, помещают в таблицу значений и передают в запрос параметром через конструкцию В (&Таблица) с помещением во временную таблицу.

Что делать, если запрос в консоли работает быстро, а в коде медленно?

Проверьте, совпадают ли значения и типы параметров — другой тип параметра может отключить использование индекса. Убедитесь, что в коде не выполняется запрос в цикле: тысяча выполнений быстрого запроса суммарно даст заметную задержку, и правильное решение — один запрос с условием В (&Список) вместо цикла.

Итог

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

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