Фильтрация данных в языке запросов 1С: оператор ГДЕ и его возможности

· 11 мин

Содержание

Условие в запросе 1С накладывает оператор ГДЕ — он идёт сразу после ИЗ и отсекает строки, которые не подходят под заданное правило. Значения, которые приходят извне (из формы, из кода, из периода отчёта), подставляют через параметры с префиксом &: ГДЕ Товар = &Товар. Для диапазонов есть МЕЖДУ, для списков — В, для иерархии справочников — В ИЕРАРХИИ.

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

Оператор ГДЕ: сравнение, И, ИЛИ, НЕ

Базовая форма простая: перечисляете поля в ВЫБРАТЬ, таблицу в ИЗ, а условие — в ГДЕ.

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

Доступны обычные операторы сравнения: =, <> (не равно), >, <, >=, <=. Логические связки — И, ИЛИ, НЕ. Несколько условий через И сужают выборку, через ИЛИ — расширяют.

Порядок вычисления задают скобки. Без них ИЛИ и И легко дают не тот результат:

ГДЕ
    (Статус = &Новый ИЛИ Статус = &ВРаботе)
    И Ответственный = &ТекущийПользователь

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

Для строк есть оператор ПОДОБНО с шаблоном. Символ % — любое число любых знаков, _ — ровно один знак:

ГДЕ
    Контрагент.Наименование ПОДОБНО "ООО%"

ПОДОБНО "%текст%" ищет вхождение в середине строки, но не использует индекс и на больших таблицах работает медленно. Для точного совпадения берите =, для начала строки — ПОДОБНО "текст%".

Проверка на пустое значение зависит от того, что именно пустое. У ссылочных типов есть «пустая ссылка», а есть NULL (появляется при левом соединении, когда строки-пары не нашлось):

ГДЕ
    Заказ.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)
    ИЛИ Заказ.Договор ЕСТЬ NULL

Константу-значение из встроенного перечисления или предопределённый элемент подставляют через функцию ЗНАЧЕНИЕ(...) — она не требует параметра и вычисляется на этапе компиляции запроса.

Параметры запроса и динамическая фильтрация

Жёстко зашивать значения в текст запроса нельзя: это открывает дорогу ошибкам и мешает кешированию. Вместо этого объявляют параметр через &Имя, а значение передают из кода.

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

Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
Запрос.УстановитьПараметр("Минимум", 10);
Результат = Запрос.Выполнить().Выбрать();

Имя в УстановитьПараметр пишут без амперсанда и ровно так, как в тексте, с учётом регистра.

Отдельный приём — фильтр по списку значений. Оператор В принимает массив или список значений, и одно условие заменяет длинную цепочку ИЛИ:

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

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

ГДЕ
    Истина
    {И Заказ.Контрагент = &Контрагент}
    {И Заказ.Дата >= &ДатаС}

Блок в {...} попадёт в запрос, только если соответствующий параметр установлен. Так из одного текста получаются разные выборки в зависимости от того, что заполнил пользователь. Этот же механизм использует система компоновки данных (СКД) в пользовательских отборах.

Проверьте себя
Что вернёт условие `ГДЕ Договор В (&Список)`, если в параметр `Список` передать пустой массив?

Отбор по датам и временным интервалам

Даты в запросе сравнивают обычными операторами, но важно помнить: тип Дата в 1С хранит и время. Условие = &Дата сработает только для записей с ровно 00:00:00, поэтому для «за день» берут диапазон.

Константу-дату прямо в тексте задают функцией ДАТАВРЕМЯ(год, месяц, день [, час, мин, сек]):

ГДЕ
    Документ.Дата МЕЖДУ ДАТАВРЕМЯ(2024, 1, 1) И ДАТАВРЕМЯ(2024, 12, 31, 23, 59, 59)

Оператор МЕЖДУ ... И ... включает обе границы. Поэтому верхнюю границу периода доводят до конца суток — иначе документы последнего дня, созданные позже полуночи, выпадут из выборки.

Чаще период приходит параметрами. Границы удобно готовить в коде функциями НачалоДня, КонецДня, НачалоМесяца:

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

КонецДня возвращает время 23:59:59, что как раз закрывает границу МЕЖДУ.

Внутри самого запроса периоды и части дат считают встроенными функциями:

ФункцияЧто делаетПример результата
НАЧАЛОПЕРИОДА(Дата, ДЕНЬ)начало дня/месяца/года01.03.2024 00:00:00
КОНЕЦПЕРИОДА(Дата, МЕСЯЦ)конец периода31.03.2024 23:59:59
ДОБАВИТЬКДАТЕ(Дата, ДЕНЬ, 7)сдвиг даты+7 дней
РАЗНОСТЬДАТ(Д1, Д2, ДЕНЬ)разница в единицахчисло дней
ГОД(Дата), МЕСЯЦ(Дата)выделить часть2024, 3

Пример: отобрать документы, у которых с даты создания прошло больше 30 дней:

ГДЕ
    РАЗНОСТЬДАТ(Документ.Дата, &ТекущаяДата, ДЕНЬ) > 30

&ТекущаяДата передавайте параметром из кода (ТекущаяДата()), а не вычисляйте на сервере СУБД. Функции текущей даты внутри запроса нет — это сознательное ограничение, чтобы запрос давал один и тот же результат при повторном выполнении.

Условия на справочники: иерархия и ссылочные поля

Со справочниками работают два специфических оператора. В ИЕРАРХИИ отбирает элемент и всё, что лежит в выбранной группе и её подгруппах:

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

Если бы стоял обычный В, в выборку попала бы только сама группа как элемент, а её содержимое — нет. В ИЕРАРХИИ раскрывает дерево целиком на любую глубину вложенности.

Условие можно накладывать и на реквизиты связанного объекта через точку — язык запросов сам достроит соединение:

ГДЕ
    Заказ.Контрагент.Регион = &Регион
    И Заказ.Контрагент.ЮридическоеФизическоеЛицо = ЗНАЧЕНИЕ(Перечисление.ЮрФизЛицо.ЮрЛицо)

Обращение через точку (Контрагент.Регион) удобно, но каждая такая точка — это скрытое левое соединение с таблицей Справочник.Контрагенты. Для одной-двух точек это нормально. Если условий по реквизитам контрагента много, выгоднее явно соединить таблицу один раз.

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

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

Без ВЫРАЗИТЬ обращение к реквизиту Регистратор.Организация для составного поля либо не скомпилируется, либо соберёт соединения со всеми возможными типами разом.

Условие ВЫБОР и фильтр в соединениях

Условие внутри запроса пригождается не только для отбора строк. Оператор ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ вычисляет значение по правилу прямо в выборке:

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

Условия соединения задаёт секция ПО, и это не то же самое, что ГДЕ. Разница критична для левых соединений:

  • условие в ПО отбирает пары строк для соединения;
  • условие в ГДЕ отсекает результат уже после соединения.
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Цены КАК Цены
ПО Товары.Ссылка = Цены.Номенклатура
    И Цены.ТипЦен = &РозничныйТип

Если это условие на ТипЦен вынести в ГДЕ, строки товаров без розничной цены (где после левого соединения в полях цены пришёл NULL) отсеются, и левое соединение фактически превратится во внутреннее. Фильтр соединяемой таблицы при левом соединении держите в ПО.

Условия можно накладывать и на агрегаты — но не через ГДЕ, а через ИМЕЮЩИЕ (аналог HAVING), которое работает уже после группировки:

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура
ИМЕЮЩИЕ
    СУММА(Продажи.Количество) > 100

ГДЕ отбирает строки до группировки, ИМЕЮЩИЕ — группы после подсчёта итогов.

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

ВЫБОР
    КОГДА ЕСТЬNULL(Остатки.КоличествоОстаток, 0) > 0 ТОГДА "Есть"
    ИНАЧЕ "Нет"
КОНЕЦ КАК Наличие

Без ЕСТЬNULL сравнение NULL > 0 вернёт не «ложь», а неопределённость, и строка не попадёт ни в одну ветку так, как вы ожидаете. Это одна из самых частых причин «исчезающих» строк после левого соединения регистра.

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

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

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

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

Для регистров условие чаще всего выгоднее передать не в ГДЕ, а прямо в параметры виртуальной таблицы. Виртуальная таблица Остатки, ОстаткиИОбороты, СрезПоследних принимает отбор в скобках — и СУБД применит его до расчёта итогов, а не после:

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

Это один из главных приёмов оптимизации: условие в параметрах виртуальной таблицы работает в разы быстрее, чем тот же отбор в ГДЕ по итоговой выборке. Разница в том, что движок рассчитывает остатки сразу по нужному срезу данных, а не поднимает весь регистр, чтобы потом отфильтровать.

Оптимизация условий и работа с индексами

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

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

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

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

Что реально происходит с запросом, видно в технологическом журнале по событию DBMSSQL или в консоли запросов с замером времени. Гадать по тексту бессмысленно — смотрите план на боевом объёме данных.

Проверка условия перед вводом в работу
Выполнено: 0 / 6

Типичные ошибки при наложении условий

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

Ещё одна частая путаница — разница между пустой ссылкой и NULL. Пустая ссылка существует всегда и проверяется через ЗНАЧЕНИЕ(...ПустаяСсылка). NULL возникает динамически при соединениях, когда пары не нашлось, и ловится оператором ЕСТЬ NULL. Условие Поле = NULL не работает — для NULL только ЕСТЬ NULL и ЕСТЬ НЕ NULL.

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

Чем отличается условие в ГДЕ от условия в ПО?
ПО задаёт правило соединения пар строк и выполняется в момент соединения, ГДЕ фильтрует уже готовый результат. При левом соединении фильтр присоединяемой таблицы держат в ПО, иначе строки с NULL отсекутся и соединение станет внутренним.
Как в запросе отобрать по списку значений?
Оператором В: ГДЕ Поле В (&Список). В параметр передают массив или список значений. Для иерархических справочников с раскрытием групп используют В ИЕРАРХИИ.
Почему условие по дате не находит документы за нужный день?
Тип Дата хранит время, и сравнение = &Дата ищет записи ровно на полночь. Берите диапазон МЕЖДУ НачалоДня(...) И КонецДня(...) — КонецДня возвращает 23:59:59 и закрывает границу.
Можно ли менять условия запроса во время работы?
Да, через необязательные блоки в фигурных скобках: {И Поле = &Параметр}. Блок войдёт в запрос, только если параметр установлен. Так собирают динамические отборы без склейки текста строками.
Как ускорить запрос с фильтром по регистру?
Переносите отбор в параметры виртуальной таблицы (Остатки(&Дата, Склад = &Склад)), а не в ГДЕ по итоговой выборке. Движок применит условие до расчёта итогов. Поля, по которым идёт постоянный отбор, индексируйте в конфигураторе.