Как фильтровать данные в запросе 1С: условие ГДЕ без потерь строк и тормозов

· 10 мин

Содержание

Оператор ГДЕ в языке запросов 1С 8.3 отбирает строки источника по условию: в результат попадают только те, для которых условие истинно. Пишется он после ИЗ и соединений, но до СГРУППИРОВАТЬ ПО: ВЫБРАТЬ ... ИЗ ... ГДЕ Условие.

Частое заблуждение — что ГДЕ «просто фильтр» и его место в запросе ни на что не влияет. На деле одно условие в ГДЕ вместо ПО превращает левое соединение во внутреннее, а условие по реквизиту виртуальной таблицы вместо её параметров заставляет СУБД читать весь регистр. Разберём синтаксис и эти ловушки на примерах.

Синтаксис ГДЕ и порядок выполнения запроса

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

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

Логически запрос выполняется в таком порядке:

  1. ИЗ и соединения — собирается набор строк.
  2. ГДЕ — отбрасываются строки, где условие не истинно.
  3. СГРУППИРОВАТЬ ПО — оставшиеся строки сворачиваются.
  4. ИМЕЮЩИЕ — фильтр уже по сгруппированным строкам.
  5. ВЫБРАТЬ, УПОРЯДОЧИТЬ ПО, ИТОГИ.

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

Операции сравнения и функции, которые работают в условии

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

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

Параметры вместо значений в тексте

Значения для сравнения передавайте параметрами, а не склеивайте строку запроса:

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

Параметр защищает от ошибок с кавычками и форматом дат. Кроме того, СУБД может переиспользовать план запроса.

Конец периода передавайте через КонецДня(). Если отдать в &КонецПериода дату без времени, документы последнего дня после 00:00:00 в выборку не попадут.

Предопределённые значения и перечисления в тексте запроса указывают через ЗНАЧЕНИЕ(): ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход), Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказовКлиентов.Закрыт). Даты-константы задаются функцией ДАТАВРЕМЯ(2025, 1, 1).

Порядок И, ИЛИ, НЕ и зачем ставить скобки в условии ГДЕ

Логические операции в языке запросов 1С выполняются по приоритету:

  1. Сначала операции сравнения (=, >, В, ПОДОБНО и т. д.).
  2. Затем НЕ.
  3. Затем И.
  4. Последним — ИЛИ.

На этом спотыкаются чаще всего. Задача: выбрать проведённые документы по складу «Основной» или «Розница». Пишут так:

ГДЕ
    Док.Проведен
    И Док.Склад = &Основной
    ИЛИ Док.Склад = &Розница

Из-за приоритета И запрос читается как (Проведен И Склад = Основной) ИЛИ Склад = Розница. В результат попадут и непроведённые, и помеченные на удаление документы розничного склада. Правильно:

ГДЕ
    Док.Проведен
    И (Док.Склад = &Основной ИЛИ Док.Склад = &Розница)

А ещё лучше — через В, где скобки уже не нужны:

ГДЕ
    Док.Проведен
    И Док.Склад В (&Основной, &Розница)

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

НЕ относится только к ближайшему операнду. НЕ А И Б — это (НЕ А) И Б. Чтобы инвертировать всё условие, берите его в скобки: НЕ (А И Б).

Проверьте себя
Как запрос 1С прочитает условие «А И Б ИЛИ В»?
Можно ли написать в ГДЕ условие СУММА(Количество) > 10?
Что вернёт условие Поле <> 5, если в поле NULL?

NULL, пустые ссылки и Неопределено в условиях отбора

В запросе 1С есть три разных «пустых» значения, и путать их нельзя:

  • 🔹 NULL — значения нет вообще. Появляется в левом соединении, когда справа не нашлось пары, и при обращении через точку к реквизиту, которого у объекта этого типа нет.
  • 🔹 Пустая ссылка — ссылочное поле заполнено, но ни на что не указывает. Это обычное значение, его можно сравнить: Контрагент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка).
  • 🔹 Неопределено — пустое значение поля составного типа.

Почему сравнение с NULL «теряет» строки

Любое сравнение с NULL через =, <>, > и т. п. не даёт истину. Значит, строка в результат не попадает. Условие Цены.Цена <> 0 отбросит и нулевые цены, и товары, у которых цены нет совсем.

Для NULL есть две конструкции:

ГДЕ
    Цены.Цена ЕСТЬ NULL              // цены нет
    ИЛИ Цены.Цена = 0                // или она нулевая

или короче, с заменой NULL на значение:

ГДЕ
    ЕСТЬNULL(Цены.Цена, 0) = 0

Обратная проверка пишется как Поле ЕСТЬ НЕ NULL.

Как проверить, что ссылка не заполнена

Если реквизит может быть и пустой ссылкой, и NULL (например, после соединения), проверяйте оба случая:

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

Для поля составного типа добавьте ещё ИЛИ Поле = НЕОПРЕДЕЛЕНО.

Условие в ГДЕ или в ПО: левое соединение и виртуальные таблицы

Это главный приём, без которого фильтрация в запросах 1С работает неправильно или медленно.

Условие на правую таблицу левого соединения

Задача: все товары, а рядом цена из прайса «Розничная», если она есть.

// НЕВЕРНО: товары без розничной цены исчезнут
ВЫБРАТЬ
    Товары.Ссылка,
    Цены.Цена
ИЗ
    Справочник.Номенклатура КАК Товары
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Цены
    ПО Товары.Ссылка = Цены.Номенклатура
ГДЕ
    Цены.ТипЦен = &Розничная

У товаров без пары Цены.ТипЦен равен NULL, сравнение не истинно, строка отбрасывается. Левое соединение фактически стало внутренним. Условие на правую таблицу переносят в ПО:

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

Ещё лучше — сразу в параметры виртуальной таблицы СрезПоследних(, ТипЦен = &Розничная), тогда срез строится только по нужному типу цен. Имена регистров и реквизитов здесь условные: в вашей конфигурации регистр цен может называться иначе.

Отбор по виртуальной таблице регистра

У виртуальных таблиц (Остатки, Обороты, ОстаткиИОбороты, СрезПоследних) есть параметр «Условие». Сравните:

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

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

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

В параметр виртуальной таблицы переносите отбор по измерениям. По ресурсам (например, КоличествоОстаток > 0) условие в параметре поставить нельзя — его оставляют в ГДЕ.

Вложенные запросы, В и сложные условия в блоке ГДЕ

Оператор В принимает не только список значений, но и результат подзапроса:

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

Можно сравнивать сразу несколько полей: (Ост.Номенклатура, Ост.Склад) В (ВЫБРАТЬ ...). Количество и порядок полей в подзапросе должны совпадать с левой частью.

Отрицание — НЕ ... В (...). Так ищут, например, контрагентов без продаж за период.

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

Ограничения подзапросов в ГДЕ

  • ❌ Подзапрос в условии В не может ссылаться на поля внешнего запроса — коррелированных подзапросов, как в SQL, язык запросов 1С не поддерживает.
  • ❌ Конструкции СУЩЕСТВУЕТ нет. Её заменяют через В или левое соединение с проверкой ЕСТЬ NULL.
  • ⚠️ Подзапрос с большим результатом СУБД может выполнять неэффективно.

Временная таблица вместо подзапроса

Тяжёлый подзапрос выносят в пакет и индексируют:

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

////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Контрагенты.Ссылка
ИЗ
    Справочник.Контрагенты КАК Контрагенты
ГДЕ
    Контрагенты.Ссылка В
        (ВЫБРАТЬ ВТ.Контрагент ИЗ ВТ_Покупатели КАК ВТ)

Запрос пакетный: его выполняют одним вызовом Запрос.Выполнить(), временная таблица живёт до конца работы менеджера временных таблиц или самого запроса.

Условия по типу и через точку

Поле Регистратор или любой реквизит составного типа сначала проверяют по типу:

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

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

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

СУБД использует индекс, только если условие «понятно» индексу. Несколько правил из практики:

ПлохоЛучшеПочему
ГОД(Док.Дата) = 2025Док.Дата МЕЖДУ &Начало И &КонецФункция над полем мешает использовать индекс по дате
ЕСТЬNULL(Поле, 0) = 0 на большой таблицеПоле ЕСТЬ NULL ИЛИ Поле = 0Выражение над полем вместо прямого сравнения
Наименование ПОДОБНО "%болт%"Наименование ПОДОБНО "Болт%"Шаблон с % в начале не идёт по индексу
Склад = &С1 ИЛИ Контрагент = &К1Два запроса через ОБЪЕДИНИТЬ ВСЕИЛИ по разным полям часто приводит к сканированию таблицы
Отбор по измерению в ГДЕ виртуальной таблицыОтбор в параметрах виртуальной таблицыМеньше данных на входе

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

  • 🔑 у справочников — Ссылка, Код, Наименование (если заданы длины), у документов — Ссылка, Номер, Дата;
  • 🔑 измерения регистров сведений и накопления — в составе основного индекса, в порядке, заданном в конфигураторе;
  • 🔑 реквизиты, у которых в свойствах конфигуратора включено «Индексировать».

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

Не бросайтесь включать «Индексировать» у реквизита ради одного отчёта. Каждый индекс замедляет запись объектов и требует реструктуризации базы при обновлении. Сначала попробуйте переписать условие.

Отдельный случай: строки неограниченной длины

Поле типа «Строка» с неограниченной длиной (например, Комментарий) нельзя сравнивать через = — запрос не выполнится. Обрежьте значение:

ГДЕ
    ПОДСТРОКА(Док.Комментарий, 1, 200) = &Текст

или приведите тип: ВЫРАЗИТЬ(Док.Комментарий КАК СТРОКА(200)). ПОДОБНО с такими полями работает, но на больших таблицах медленно.

Типичные ошибки в условии ГДЕ и как их отладить

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

Как искать причину пошагово

  1. Уберите условие ГДЕ целиком и проверьте, что исходные данные вообще есть.
  2. Возвращайте условия по одному. На каком условии строки пропали — там и ошибка.
  3. Выведите проверяемые поля в ВЫБРАТЬ. Часто видно, что вместо ожидаемой ссылки там NULL или значение другого типа.
  4. Проверьте параметры. Перед Выполнить() посмотрите в отладчике значения в Запрос.Параметры: пустая ссылка вместо выбранного контрагента — частая причина пустого результата.
  5. Прогоните запрос в консоли запросов. Внешняя обработка «Консоль запросов» показывает результат, время выполнения и позволяет менять параметры без правки кода.

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

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

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