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

· 9 мин

Содержание

Отбор в языке запросов 1С задаёт оператор ГДЕ: он идёт после источника данных и оставляет только те строки, для которых условие истинно. Условие строится из полей, констант, параметров &Имя и операторов сравнения =, <>, >, <, >=, <=, которые можно объединять через И, ИЛИ, НЕ.

Главное, что отличает запросы 1С от привычного SQL, — это работа с NULL, отдельные операторы В, МЕЖДУ, ПОДОБНО, В ИЕРАРХИИ и разница между условием в ГДЕ и условием в параметрах виртуальной таблицы. От того, где стоит условие, напрямую зависит скорость запроса. Разберём каждый случай с примерами, которые можно вставить в консоль запросов и проверить.

Оператор ГДЕ и операторы сравнения

ГДЕ ставится после секции ИЗ (или после перечисления таблиц) и до группировки и упорядочивания. Простейший отбор по одному полю:

ВЫБРАТЬ
    Номенклатура.Наименование,
    Номенклатура.Артикул
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.ПометкаУдаления = ЛОЖЬ

Операторы сравнения работают с любыми сопоставимыми типами: числами, датами, ссылками, булевыми и строковыми значениями. Для ссылочных полей сравнение идёт по равенству объекта, а не по представлению:

ГДЕ
    Товары.Склад = &Склад
    И Товары.Количество > 0
    И Товары.Период >= &НачалоПериода

Логические поля сравнивайте явно с ИСТИНА или ЛОЖЬ. Запись ГДЕ Номенклатура.Услуга без сравнения синтаксически не пройдёт — платформе нужно булево выражение целиком.

Для реквизитов составного типа часто нужна проверка самого типа, а не значения. Её делает оператор ССЫЛКА:

ГДЕ
    РегистрПродаж.Контрагент ССЫЛКА Справочник.Контрагенты

Такое условие оставит строки, где в поле составного типа лежит ссылка именно на справочник «Контрагенты», и отсечёт, например, физлиц.

Отбор по списку и диапазону: В, МЕЖДУ, В ИЕРАРХИИ

Когда нужно сравнить поле с несколькими значениями, перечислять их через ИЛИ не нужно — для этого есть оператор В:

ГДЕ
    Документ.Организация В (&СписокОрганизаций)

В параметр &СписокОрганизаций передают массив или список значений. Можно перечислить значения прямо в тексте — В (&Орг1, &Орг2) — но для динамического набора удобнее один параметр-список.

Диапазон задаёт МЕЖДУ … И …. Границы включаются в отбор:

ГДЕ
    Продажи.Дата МЕЖДУ &ДатаНачала И &ДатаКонца

С датами МЕЖДУ коварно: &ДатаКонца без времени — это полночь (00:00:00). Строки за последний день с ненулевым временем в отбор не попадут. Передавайте в &ДатаКонца конец дня (КонецДня()) или сдвигайте границу на следующий день с оператором <.

Для иерархических справочников есть В ИЕРАРХИИ — он отбирает элемент и всё, что лежит под ним по дереву групп:

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

Отрицание у этих операторов строится через НЕ: НЕ В (…), НЕ МЕЖДУ … И …. Будьте осторожны с НЕ В по большим спискам — об этом в разделе про скорость.

Логические операторы И, ИЛИ, НЕ и скобки

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

Сравните два варианта:

-- Без скобок: ИЛИ "рвёт" условие пополам
ГДЕ
    Товар.Склад = &Склад
    И Товар.Вид = &Вид
    ИЛИ Товар.Количество = 0

-- Со скобками: смысл восстановлен
ГДЕ
    Товар.Склад = &Склад
    И (Товар.Вид = &Вид ИЛИ Товар.Количество = 0)

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

Оператор НЕ инвертирует выражение целиком:

ГДЕ
    НЕ (Документ.Проведён = ИСТИНА И Документ.Сумма = 0)

Внутри условий работает и конструкция ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ — аналог CASE. Она возвращает значение, которое можно сравнивать:

ГДЕ
    ВЫБОР
        КОГДА Заказ.Приоритет = &Высокий ТОГДА Заказ.Сумма
        ИНАЧЕ 0
    КОНЕЦ > &Порог
Проверьте себя
Какой результат даст условие `Склад = &Склад И Вид = &Вид ИЛИ Остаток = 0` без скобок?
Что отберёт `Номенклатура.Ссылка В ИЕРАРХИИ (&Группа)`?
Чему равна `&ДатаКонца`, переданная как дата без времени, в условии `МЕЖДУ`?

Проверка на пустое значение: NULL в условиях

NULL в запросе — это не то же самое, что пустая ссылка или ноль. Он появляется при соединениях (ЛЕВОЕ СОЕДИНЕНИЕ), когда для строки нет пары, и в полях, которых физически нет в наборе данных. Сравнение с NULL через = всегда даёт неопределённость, а не истину. Поэтому условие ГДЕ Поле = NULL никогда не сработает.

Для проверки есть специальные операторы:

-- Правильно
ГДЕ
    Остатки.КоличествоОстаток ЕСТЬ NULL

ГДЕ
    Остатки.КоличествоОстаток ЕСТЬ НЕ NULL

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

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

Без обёртки ЕСТЬNULL строки с NULL в остатке выпали бы из отбора > 0 молча — а именно они обычно и означают «товара на складе нет».

Запомните разницу: пустую ссылку проверяют сравнением со ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка), пустую дату — с ДАТАВРЕМЯ(1, 1, 1), а отсутствие строки после соединения — только через ЕСТЬ NULL.

Параметры и временные таблицы в условиях

Значения, которые приходят извне, подставляют через параметры &Имя. Параметр не вставляется в текст как строка — платформа передаёт его в СУБД отдельно, поэтому экранирование и «инъекции» запросу 1С не грозят.

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

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

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

-- Медленно: отбор в ГДЕ, после расчёта итогов по всему регистру
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ГДЕ
    Остатки.Склад = &Склад

-- Быстро: отбор внутри виртуальной таблицы
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки

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

Сравнение строк и чувствительность к регистру

Для строк кроме = есть оператор ПОДОБНО — поиск по шаблону. В шаблоне работают подстановочные символы:

  • % — любое количество любых символов (в том числе ноль);
  • _ — ровно один любой символ;
  • [абв] — один символ из перечисленных;
  • [а-я] — один символ из диапазона;
  • [^0-9] — один символ, не входящий в набор.
ГДЕ
    Номенклатура.Наименование ПОДОБНО "%кабель%"

Если сам символ % или _ нужно искать как текст, экранируйте его спецсимволом:

ГДЕ
    Товар.Артикул ПОДОБНО "100\%%" СПЕЦСИМВОЛ "\"

Здесь первый % — это literal «процент», второй — подстановочный «что угодно дальше».

С регистром ситуация тонкая: будет сравнение строк учитывать регистр или нет, определяется настройками базы данных и СУБД, а не самим языком запросов. Чтобы результат не зависел от этого, приводите обе стороны к одному регистру функциями ВРЕГ() (в верхний) или НРЕГ() (в нижний):

ГДЕ
    ВРЕГ(Контрагент.Наименование) ПОДОБНО ВРЕГ(&Шаблон)

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

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

Как ускорить условия: индексы и порядок действий

Скорость отбора определяется тем, может ли СУБД опереться на индекс. Несколько правил, которые дают основной эффект:

  1. Поле — слева, выражение — справа. Товар.Дата >= &Дата использует индекс, а ДОБАВИТЬКДАТЕ(Товар.Дата, …) >= &Дата — уже нет: функция на поле «прячет» индекс.
  2. Отбор по измерениям — в параметрах виртуальной таблицы, как показано выше, а не в ГДЕ.
  3. Длинные списки — через временную таблицу с ИНДЕКСИРОВАТЬ ПО, а не одним гигантским параметром в В (…).
  4. Избегайте ИЛИ по разным полям — такой отбор часто не ложится на индекс. Иногда быстрее объединить два запроса через ОБЪЕДИНИТЬ, каждый со своим простым условием.
  5. НЕ В (большой список) заставляет перебирать почти всю таблицу. Если возможно, переформулируйте через положительное условие или соединение.

Отдельно про агрегаты. Условие на результат СУММА(), КОЛИЧЕСТВО() и других агрегатных функций в ГДЕ поставить нельзя — оно пишется в секции ИМЕЮЩИЕ после группировки:

ВЫБРАТЬ
    Продажи.Контрагент,
    СУММА(Продажи.Сумма) КАК Оборот
ИЗ
    РегистрНакопления.Продажи.Обороты КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент
ИМЕЮЩИЕ
    СУММА(Продажи.Сумма) > 100000

ГДЕ фильтрует строки до группировки, ИМЕЮЩИЕ — итоги после. Путать их — частая причина ошибки «Агрегатные функции не могут использоваться в условии».

Частые ошибки при написании условий

Ниже — ситуации, которые не дают ошибку компиляции, но тихо портят результат. Их стоит узнавать в лицо.

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

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

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

Чем отличается условие в ГДЕ от условия в параметрах виртуальной таблицы?
ГДЕ отбирает строки уже после того, как платформа рассчитала итоги по всему регистру. Параметр виртуальной таблицы (в скобках после её имени) ограничивает данные до расчёта, поэтому по измерениям регистра он всегда быстрее.
Почему условие `Поле = NULL` не работает?
Сравнение чего угодно с NULL через = даёт не истину, а неопределённость. Для проверки нужен оператор ЕСТЬ NULL или ЕСТЬ НЕ NULL, а для подстановки значения — функция ЕСТЬNULL(Поле, Значение).
Как передать в условие `В` список из нескольких значений?
Соберите массив или список значений и передайте его одним параметром: Поле В (&Список). Для больших наборов положите значения во временную таблицу с индексом и используйте её в подзапросе В (ВЫБРАТЬ … ИЗ ВТ).
Учитывает ли сравнение строк регистр букв?
Это зависит от настроек базы и СУБД, а не от языка запросов. Чтобы результат был предсказуемым, приводите обе стороны условия к одному регистру функцией ВРЕГ() или НРЕГ().
Можно ли поставить условие на сумму или количество строк?
На агрегатную функцию — только через секцию ИМЕЮЩИЕ после группировки. В ГДЕ агрегаты запрещены: оно фильтрует исходные строки, а не итоги.