Как задать условие отбора в запросе 1С 8.3

· 9 мин

Содержание

Условие отбора в запросе 1С пишут в секции ГДЕ — после списка полей и источника данных. Самый короткий вид выглядит так: ГДЕ Контрагент = &Контрагент. Сравниваемое значение подставляют параметром &Имя, а сам параметр задают в коде методом УстановитьПараметр.

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

Где размещается условие и какие операторы сравнения есть

Отбор в языке запросов живёт не только в ГДЕ. Условие можно поставить в четырёх местах, и путать их не стоит:

  • ГДЕ — основной фильтр строк результата.
  • ПО — условие соединения таблиц (в ЛЕВОЕ СОЕДИНЕНИЕ ... ПО).
  • ИМЕЮЩИЕ — фильтр уже по сгруппированным (агрегированным) данным.
  • ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ — условное выражение прямо в поле выборки.

Операторы сравнения в ГДЕ стандартные, но у языка запросов есть несколько своих:

ОператорЧто проверяетПример
= <>равно / не равноСумма <> 0
> < >= <=больше / меньшеКоличество >= 10
МЕЖДУ ... И ...попадание в диапазон (границы включительно)Дата МЕЖДУ &Нач И &Кон
В (...)вхождение в список значенийСтатус В (&Список)
В ИЕРАРХИИ (...)элемент и все его подчинённые в группеНоменклатура В ИЕРАРХИИ (&Группа)
ПОДОБНОсравнение строки по шаблонуНаименование ПОДОБНО "%кабель%"
ЕСТЬ NULL / ЕСТЬ НЕ NULLполе пустое (NULL) или нетКонтрагент.Телефон ЕСТЬ NULL

Простой пример полного запроса с отбором:

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

В шаблоне ПОДОБНО символ % заменяет любую последовательность знаков, _ — ровно один знак. Если сам процент нужен как текст, экранируйте его: ПОДОБНО "%100\%%" СПЕЦСИМВОЛ "\".

Когда нужно не отфильтровать строку целиком, а вычислить значение по условию, применяют конструкцию ВЫБОР. Она работает прямо в списке полей и возвращает разные результаты в зависимости от проверки:

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

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

Как подставить значение через параметр запроса

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

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты
    |ГДЕ ИНН = &ИНН И НЕ ПометкаУдаления";
Запрос.УстановитьПараметр("ИНН", "7701234567");
Результат = Запрос.Выполнить();

Отбор по списку значений делают через В (&Параметр), где в параметр передают массив или список значений:

МассивСтатусов = Новый Массив;
МассивСтатусов.Добавить(Перечисления.СтатусыЗаказов.Отгружен);
МассивСтатусов.Добавить(Перечисления.СтатусыЗаказов.Закрыт);
Запрос.УстановитьПараметр("Статусы", МассивСтатусов);
ГДЕ Заказ.Статус В (&Статусы)

Даты в самом тексте запроса (когда они фиксированы) задают литералом ДАТАВРЕМЯ(2020, 1, 1), но чаще всё равно передают параметром. Начало и конец дня считают функциями НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) и КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) — иначе отбор = &Дата поймает только записи ровно на 00:00:00.

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

Логические операторы И, ИЛИ, НЕ и порядок их вычисления

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

  1. сначала НЕ,
  2. затем И,
  3. в последнюю очередь ИЛИ.

Из-за этого запись без скобок часто означает не то, что задумано:

ГДЕ Организация = &Орг
    И Статус = &Черновик ИЛИ Статус = &Отменён

Здесь И сильнее ИЛИ, поэтому условие читается как «(Организация И Черновик) ИЛИ Отменён» — и в результат попадут отменённые документы любых организаций. Чтобы получить ожидаемое «нужная организация И один из двух статусов», ставьте скобки:

ГДЕ Организация = &Орг
    И (Статус = &Черновик ИЛИ Статус = &Отменён)

Привычка простая: как только в одном ГДЕ смешиваются И и ИЛИ, группу ИЛИ всегда берите в скобки. Лишние скобки читаемость не портят, а от логической ошибки страхуют.

Оператор НЕ удобно применять к В и ПОДОБНО: НЕ Номенклатура В ИЕРАРХИИ (&Услуги), НЕ Наименование ПОДОБНО "Тест%".

Проверьте себя
В каком порядке вычисляются логические операторы в условии без скобок?
Как в запросе проверить, что поле не заполнено (NULL)?

Отбор по ссылке и по полю составного типа

Ссылку сравнивают напрямую: ГДЕ Номенклатура = &Номенклатура. Чтобы захватить не один элемент, а целую группу справочника вместе со всем, что в ней лежит, используют В ИЕРАРХИИ:

ГДЕ Номенклатура В ИЕРАРХИИ (&ГруппаТоваров)

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

Тип значения проверяют функциями ТИПЗНАЧЕНИЯ и ТИП:

ГДЕ ТИПЗНАЧЕНИЯ(Движения.Регистратор) = ТИП(Документ.РеализацияТоваровУслуг)

А чтобы добраться до реквизита объекта составного типа, значение приводят к нужному типу оператором ВЫРАЗИТЬ:

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

Если тип не тот, ВЫРАЗИТЬ вернёт NULL, а не ошибку, — такие строки просто не пройдут условие равенства. Это штатное поведение, на него можно опираться.

Отбор на пустую ссылку делают сравнением с пустым значением соответствующего типа: ГДЕ Контрагент <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка). Оператор ЗНАЧЕНИЕ(...) подставляет предопределённое значение или пустую ссылку прямо в текст, без параметра.

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

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

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

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

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

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

Если по полю временной таблицы потом идёт соединение или В (...), объявите по нему индекс: ПОМЕСТИТЬ ВТ_Товары (Ссылка) ... ИНДЕКСИРОВАТЬ ПО Ссылка. На таблице в тысячи строк это заметно ускоряет второй запрос пакета.

Как ускорить отбор: индексы и условия в виртуальных таблицах

Медленный запрос — это чаще всего не «много данных», а условие, которое мешает использовать индекс. Несколько приёмов, которые реально влияют на скорость:

  • Отбирайте по индексированным полям. У ссылки, даты документа, измерений регистра индексы есть по умолчанию. Условие по обычному реквизиту справочника без индекса заставит систему перебирать все строки.
  • Не оборачивайте поле в функции. ГДЕ ПОДСТРОКА(Код, 1, 3) = "100" или ГДЕ ГОД(Дата) = 2024 — индекс по такому условию не работает . Перепишите через диапазон: Дата МЕЖДУ &Нач И &Кон.
  • Избегайте ведущего % в ПОДОБНО. Шаблон "кабель%" индекс использовать может, а "%кабель%" — уже нет, он приводит к полному сканированию.
  • Условие по регистру ставьте в параметры виртуальной таблицы, а не в ГДЕ. Это самый частый промах. Фильтровать итоги нужно внутри виртуальной таблицы — тогда система отберёт данные ещё при расчёте, а не посчитает всё и отбросит лишнее:
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &НаДату,
        Склад = &Склад И Номенклатура В ИЕРАРХИИ (&Группа)) КАК Остатки

Сравните с антипаттерном ИЗ ...Остатки(&НаДату, ) ... ГДЕ Склад = &Склад — во втором случае платформа сначала рассчитает остатки по всем складам и только потом выбросит ненужные.

  • ЛЕВОЕ СОЕДИНЕНИЕ с проверкой ГДЕ Поле ЕСТЬ NULL применяйте осознанно — это рабочий приём «найти записи без пары», но на больших таблицах он тяжёлый.

Отдельно про NULL в условиях после соединений: если правая таблица не нашлась, её поля вернут NULL, и любое сравнение с ними (> 0, = &Значение) окажется ложным. Чтобы такие строки учитывались, оборачивайте поле в ЕСТЬNULL(Остаток, 0) — тогда вместо «неизвестно» подставится ноль, и условие ЕСТЬNULL(Остаток, 0) <= 0 корректно поймает позиции без остатка.

Частые ошибки в условиях запроса

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

Ещё один тонкий момент — строки. Язык запросов сравнивает строки как дополненные пробелами справа, поэтому "Петров" и "Петров " в условии = считаются равными. А вот в ПОДОБНО хвостовые пробелы уже значимы — об этом легко забыть при сверке с внешними данными.

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

Чем отличается условие в `ПО` от условия в `ГДЕ`?
Для ВНУТРЕННЕГО СОЕДИНЕНИЯ разницы в результате почти нет. А вот для ЛЕВОГО: условие в ПО отбирает строки правой таблицы до соединения (левая остаётся полной), а то же условие в ГДЕ отсекает строки уже после соединения — и фактически превращает левое соединение во внутреннее.
Можно ли писать условие на английском (WHERE вместо ГДЕ)?
Да, язык запросов поддерживает английский синтаксис: WHERE, AND, OR, SELECT. Но в типовых конфигурациях и справочниках принят русский вариант, смешивать их в одном запросе не стоит.
Как отобрать строки, где поле попадает в один из нескольких диапазонов?
Объедините условия МЕЖДУ через ИЛИ в скобках: (Сумма МЕЖДУ 0 И 100) ИЛИ (Сумма МЕЖДУ 1000 И 5000). Границы в МЕЖДУ включаются с обеих сторон.
Почему условие по реквизиту через точку (`Товар.Поставщик = &П`) замедляет запрос?
Обращение через точку порождает неявное соединение с таблицей Номенклатура. На больших выборках это дороже, чем явное СОЕДИНЕНИЕ, которым соединение можно контролировать. Для разовых отборов это некритично.
Как задать условие «значение не входит в список»?
Поставьте НЕ перед В: ГДЕ НЕ Статус В (&Исключения). Учтите, что если в поле попадаются NULL, такие строки НЕ В (...) не вернёт — при необходимости добавьте ИЛИ Статус ЕСТЬ NULL.