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

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

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

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

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

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

Материал ориентирован на тех, кто пишет запросы в конфигураторе, консоли запросов или в схеме компоновки данных. Примеры приведены для управляемых приложений на платформе 8.3, но большинство принципов справедливо и для более ранних версий.

Структура запроса: из каких частей состоит текст

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

Источником данных может быть физическая таблица (Справочник.Номенклатура, Документ.РеализацияТоваровУслуг), виртуальная таблица регистра (РегистрНакопления.Продажи.Обороты) или вложенный запрос. Каждому источнику принято давать короткий псевдоним через КАК — это делает текст читаемым и избавляет от неоднозначности при соединениях.

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

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

"ВЫБРАТЬ

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

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

|ИЗ

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

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

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

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

| Количество УБЫВ";

Обратите внимание на символ «|» в начале строк литерала — он позволяет писать многострочный текст запроса в модуле. Конструктор запроса ставит его автоматически; при ручном наборе пропущенный символ — частая причина синтаксической ошибки.

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

Любое значение, которое приходит извне — даты, ссылки, списки, — передавайте через параметры с префиксом «&». Конкатенация значений прямо в текст запроса опасна: портится типизация, ломается кэш планов запросов, а при передаче строк пользователем возникает риск некорректного текста.

Параметр объявляется в тексте как &ИмяПараметра, а значение задаётся методом УстановитьПараметр до вызова Выполнить(). Для отбора по списку значений или массиву используйте оператор В (&Список) — платформа сама развернёт коллекцию в условие.

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

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

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

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

Если параметр не установлен, при выполнении возникнет ошибка. Проверяйте обязательные параметры до выполнения, а для необязательных используйте значения по умолчанию или конструкцию с проверкой на НЕОПРЕДЕЛЕНО в коде.

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

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

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

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

После ЛЕВОГО соединения поля правой таблицы могут содержать NULL. Всегда обрабатывайте это функцией ЕСТЬNULL(Поле, ЗначениеПоУмолчанию), иначе арифметика с NULL даст NULL и исказит итоги, а сравнение с NULL в условии просто не сработает.

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

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

...

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

Типичные ошибки
01
Соединение по неиндексированным полям или выражениям — запрос сканирует таблицы целиком и работает в разы медленнее
02
Забытый ЕСТЬNULL после левого соединения — суммы и сравнения дают NULL и «теряют» строки
03
Условие на правую таблицу в секции ГДЕ после левого соединения фактически превращает его во внутреннее — строки без совпадений пропадают
04
Соединение без условия по измерениям виртуальной таблицы порождает декартово произведение и зависание

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

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

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

Остатки или Обороты

Остатки(Момент, Условие)

Нужен срез на конкретный момент: сколько товара на складе сейчас. Не даёт данных о движении внутри периода.

Обороты(Начало, Конец, Периодичность, Условие)

Нужна активность за период: продажи, поступления. Не показывает начальное и конечное сальдо напрямую.

ОстаткиИОбороты

Нужны и сальдо, и движения одним запросом, например оборотно-сальдовая ведомость. Тяжелее двух предыдущих — не используйте без необходимости.

Для документов с временем внутри дня используйте параметр типа Граница, если отбор идёт по моменту времени: МоментВремени() документа корректнее сравнивать через границу с видом «Включая» или «Исключая», иначе документы на границе периода могут выпасть или продублироваться.

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

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

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

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

"ВЫБРАТЬ

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

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

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

|ИЗ

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

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

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

|;

|ВЫБРАТЬ

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

| ЕСТЬNULL(ВТ.Количество, 0) КАК Продано

|ИЗ

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

| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Продажи КАК ВТ

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

|;

|УНИЧТОЖИТЬ ВТ_Продажи";

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

Обход результата и проверка корректности

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

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

Как проверить запрос перед внедрением
  1. 1
    Соберите текст запроса в консоли запросов на тестовой копии базы и выполните с реальными параметрами
  2. 2
    Сравните итоговые суммы и количество строк со стандартным отчётом по тому же регистру за тот же период
  3. 3
    Проверьте крайние случаи: пустой период, отсутствие данных, документы ровно на границе периода
  4. 4
    Замерьте время выполнения на объёме, близком к рабочему; если запрос медленный — переносите условия в параметры виртуальных таблиц
  5. 5
    Проверьте результат под пользователем с ограниченными правами: RLS может изменить состав данных

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

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

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

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

Совпадает с ожиданием или с контрольным отчётом

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

Строк больше — вероятно дублирование из-за соединения «один ко многим» без группировки

Суммы по итогам
Норма

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

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

Суммы завышены — дубли строк; занижены или NULL — потеряны строки из-за условия после левого соединения

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

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

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

Растёт с ростом периода непропорционально — условие стоит в ГДЕ вместо параметров виртуальной таблицы

Поведение на пустых данных
Норма

Возвращается пустой результат без ошибки

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

Ошибка или строки с NULL там, где ожидались нули — не хватает ЕСТЬNULL

Типичные ошибки и как их избежать

Первая группа ошибок — логические: дублирование строк при соединении с табличной частью без группировки, потеря строк из-за условия на правую таблицу после левого соединения, неверная работа с NULL. Эти ошибки коварны тем, что запрос выполняется без сообщений, просто возвращает неправильные данные.

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

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

Итог

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

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

Частые вопросы о запросах в 1С

Чем запрос лучше перебора данных в цикле через объектную модель?

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

Как отобрать записи, где строковое поле содержит подстроку?

Используйте оператор ПОДОБНО с шаблоном: Поле ПОДОБНО "%" + &Фрагмент + "%". Учтите, что такой отбор не использует индекс и на больших таблицах работает медленно; спецсимволы шаблона (%, _, []) во введённой строке нужно экранировать через СПЕЦСИМВОЛ.

Почему запрос с итогами по периодам теряет пустые периоды?

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

Можно ли в запросе получить представление ссылки?

Да, функция ПРЕДСТАВЛЕНИЕ(Поле) вернёт строковое представление. Но используйте её только для финального вывода: соединения и отборы по представлению работают медленно и ненадёжно — всегда оперируйте ссылками.

Как ограничить количество возвращаемых строк?

Используйте конструкцию ВЫБРАТЬ ПЕРВЫЕ N вместе с УПОРЯДОЧИТЬ ПО — без сортировки «первые» строки не определены. Для проверки существования хотя бы одной записи выбирайте ПЕРВЫЕ 1 и проверяйте Пустой() у результата — это быстрее, чем считать количество.

Что делать, если запрос в отчёте на СКД работает иначе, чем в консоли?

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