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

· 11 мин

Содержание

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

Синтаксис простой, а ошибаются обычно в другом. Теряются строки с NULL, левое соединение незаметно превращается во внутреннее, отбор по дате срезает последний день. Медленный запрос часто тормозит из-за условия, которое не даёт СУБД использовать индекс. Всё это касается языка запросов платформы 8.3 и одинаково работает в любой конфигурации: БП, ЗУП, УТ, ERP.

Где стоит ГДЕ и как выглядит условие

Порядок предложений в запросе фиксирован. Условие отбора идёт строго после источников:

ВЫБРАТЬ
    Реализация.Ссылка,
    Реализация.Дата,
    Реализация.СуммаДокумента
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Проведен
    И Реализация.СуммаДокумента >= 10000
УПОРЯДОЧИТЬ ПО
    Реализация.Дата

Что можно сравнивать в условии:

  • 🔢 числа, строки, даты, булево — операторами =, <>, <, >, <=, >=;
  • 🔗 ссылки на справочники и документы — через =, <>, В, В ИЕРАРХИИ;
  • ✅ булевы поля — без сравнения: ГДЕ Реализация.Проведен или ГДЕ НЕ Реализация.ПометкаУдаления;
  • 🧮 выражения и функции: ГОД(Дата) = 2025, ЕСТЬNULL(Сумма, 0) > 0 (про скорость таких условий — ниже).

Литералы пишутся так: строка — в двойных кавычках, дата — ДАТАВРЕМЯ(2025, 1, 1), булево — ИСТИНА и ЛОЖЬ. В рабочем коде вместо литералов почти всегда нужны параметры.

В конструкторе запроса условия собираются на вкладке «Условия». Поле перетаскивается из списка слева, а флажок «Произвольное» открывает поле для ручного ввода выражения. Сложные условия со скобками удобнее писать руками.

ГДЕ и ИМЕЮЩИЕ

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

ЗадачаКуда писать условие
Только проведённые документы за кварталГДЕ
Контрагенты, у которых сумма продаж больше 1 млнИМЕЮЩИЕ СУММА(СуммаДокумента) > 1000000
Склад = Основной и итог по номенклатуре больше нулясклад — в ГДЕ, итог — в ИМЕЮЩИЕ

Агрегатную функцию в ГДЕ поставить нельзя: запрос не выполнится. И наоборот, условие на исходное поле в ИМЕЮЩИЕ работает медленнее: СУБД сначала сгруппирует всё, а потом отбросит лишнее.

Логика условий: И, ИЛИ, НЕ и скобки

Условия соединяются тремя связками:

  • И — должны выполняться оба условия;
  • ИЛИ — достаточно одного;
  • НЕ — инвертирует условие.

Приоритет такой: сначала НЕ, потом И, последним ИЛИ. На этом ловятся чаще всего. Сравните два варианта:

// Ошибка: отберёт ВСЕ документы Иванова, даже непроведённые
ГДЕ
    Док.Проведен
    И Док.Контрагент = &Иванов
    ИЛИ Док.Контрагент = &Петров

// Правильно: проведённые документы обоих контрагентов
ГДЕ
    Док.Проведен
    И (Док.Контрагент = &Иванов
        ИЛИ Док.Контрагент = &Петров)

Первое условие читается как (Проведен И Иванов) ИЛИ Петров. Непроведённые документы Петрова тоже попадут в выборку.

Ставьте скобки вокруг каждой группы ИЛИ, даже если уверены в приоритете. Через полгода текст будет читать другой человек.

Перечень значений одного поля через цепочку ИЛИ проще заменить на В: Док.Контрагент В (&Иванов, &Петров). Это короче, а для СУБД обычно и выгоднее.

Диапазоны, списки и шаблоны строк

МЕЖДУ — диапазон включительно

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

Обе границы входят в диапазон. МЕЖДУ 1 И 10 равно >= 1 И <= 10.

С датами главный подвох — время. Если в &КонецПериода передать 31.01.2025 0:00:00, документы, проведённые 31 января в 14:20, в выборку не попадут. Передавайте конец дня:

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

Или прямо в тексте: МЕЖДУ &Начало И КОНЕЦПЕРИОДА(&Конец, ДЕНЬ).

В — значение из списка или из подзапроса

Список значений можно перечислить или передать одним параметром:

ГДЕ
    Остатки.Склад В (&Склады)

В параметр Склады передают массив, список значений или таблицу значений с одной колонкой. Если массив пустой, запрос вернёт ноль строк — это не ошибка, а честный результат «ни один склад не подходит».

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

ГДЕ
    (Цены.Номенклатура, Цены.Период) В
        (ВЫБРАТЬ
            Т.Номенклатура,
            МАКСИМУМ(Т.Период)
        ИЗ
            РегистрСведений.ЦеныНоменклатуры КАК Т
        СГРУППИРОВАТЬ ПО
            Т.Номенклатура)

В ИЕРАРХИИ — группа со всеми вложенными

Для иерархических справочников ГДЕ Номенклатура В ИЕРАРХИИ (&Группа) вернёт элементы группы и всех её подгрупп на любой глубине. Обычное В (&Группа) найдёт только саму группу. Если передать в параметр не группу, а элемент, условие сработает как обычное равенство.

ПОДОБНО — отбор по шаблону

СимволЧто значитПример
%любая последовательность символов, в том числе пустая"%подшипник%"
_ровно один любой символ"А_12"
[...]один символ из перечисленных или диапазона"[0-9]%"
[^...]один символ, кроме перечисленных"[^0-9]%"

Чтобы искать сам символ % или _, задайте спецсимвол экранирования:

ГДЕ
    Номенклатура.Наименование ПОДОБНО "%10~%%" СПЕЦСИМВОЛ "~"

Такое условие найдёт «Скидка 10% на…». Регистр букв при сравнении зависит от СУБД и её настроек сортировки, поэтому для надёжности приводят обе стороны к одному регистру: ВРЕГ(Наименование) ПОДОБНО ВРЕГ(&Шаблон).

Отбор по ссылкам, перечислениям и типам

Ссылку на предопределённое значение, перечисление или пустую ссылку в тексте запроса задают через ЗНАЧЕНИЕ():

ГДЕ
    Док.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОперацийРеализацияТоваров.ПродажаКомиссия)
    И Док.Договор <> ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)

Имя перечисления и его значения выше приведены для примера: точные имена берите из дерева метаданных своей конфигурации. Ошибка в имени внутри ЗНАЧЕНИЕ() даёт синтаксическую ошибку запроса, а не пустую выборку.

Поля составного типа (например, Регистратор у регистров) фильтруют по типу двумя способами:

// вариант 1
ГДЕ Движения.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг

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

Оба дают одинаковый результат. ССЫЛКА короче и читается легче.

NULL и пустые значения в условии

NULL в запросе 1С — это «значения нет вообще». Он появляется в трёх местах: в полях из левого соединения, когда пары не нашлось; при разыменовании пустой ссылки (Док.Договор.Организация при пустом договоре); в полях составного типа.

Сравнение с NULL всегда даёт не «истину», а неизвестность, и строка отбрасывается. Отсюда правила:

  • ❌ ГДЕ Поле = NULL — не найдёт ничего;
  • ✅ ГДЕ Поле ЕСТЬ NULL — строки без значения;
  • ✅ ГДЕ Поле ЕСТЬ НЕ NULL или ГДЕ НЕ Поле ЕСТЬ NULL — строки со значением;
  • ✅ ГДЕ ЕСТЬNULL(Поле, 0) = 0 — и NULL, и ноль одним условием.

Неочевидный эффект: ГДЕ НЕ Цены.Цена = 100 тоже не вернёт строки, где цена NULL. Если они нужны, допишите их явно: ИЛИ Цены.Цена ЕСТЬ NULL.

NULL, пустая ссылка и пустая строка — три разные вещи

ЗначениеОткуда берётсяКак отобрать
NULLлевое соединение без пары, разыменование пустой ссылкиЕСТЬ NULL
Пустая ссылкареквизит не заполнен в документе= ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка)
Пустая строкастроковый реквизит не заполнен= ""

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

Ловушка с левым соединением

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

Хотели список всей номенклатуры с ценами, а получили только ту, у которой цена есть. Позиции без цены пришли с NULL, а NULL > 0 строку отбросил. По сути получилось внутреннее соединение.

Как исправить зависит от задачи:

  1. Нужна вся номенклатура, а цена — только положительная: перенесите условие в ПО: ПО Цены.Номенклатура = Номенклатура.Ссылка И Цены.Цена > 0.
  2. Нужна вся номенклатура, а без цены — тоже показать: ГДЕ ЕСТЬNULL(Цены.Цена, 0) >= 0 или уберите условие совсем.

Параметры вместо склейки текста

Значения в условие передают через параметры с амперсандом:

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

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Наименование);
КонецЦикла;

Почему не собирать текст запроса сложением строк:

  • 🛡 строка от пользователя, вклеенная в текст, может поменять логику запроса: лишняя кавычка ломает синтаксис, а подобранная — расширяет отбор;
  • 📅 даты и числа не надо приводить к строковому формату, платформа передаст их сама;
  • 🔁 текст запроса один и тот же при разных значениях, его легче читать и отлаживать.

Для поиска по части строки шаблон тоже собирают в параметре, а не в тексте:

Запрос.Текст = "... ГДЕ Номенклатура.Наименование ПОДОБНО &Шаблон СПЕЦСИМВОЛ ""~""";
Шаблон = СтрЗаменить(СтрокаПоиска, "~", "~~");
Шаблон = СтрЗаменить(Шаблон, "%", "~%");
Шаблон = СтрЗаменить(Шаблон, "_", "~_");
Шаблон = СтрЗаменить(Шаблон, "[", "~[");
Запрос.УстановитьПараметр("Шаблон", "%" + Шаблон + "%");

Экранирование нужно, чтобы пользователь, введя % или _, искал этот символ, а не получал «всё подряд».

Если параметр используется в тексте, но не установлен, запрос упадёт с ошибкой о незаданном значении параметра. Проверяйте имена: &Склад и УстановитьПараметр("Склады", …) — разные параметры.

Опциональный отбор (фильтр «по складу, если выбран») делают без склейки:

ГДЕ
    (&ВсеСклады ИЛИ Остатки.Склад = &Склад)

Это работает, но такое ИЛИ мешает СУБД выбрать индекс. На больших таблицах надёжнее держать два текста запроса или добавлять условие в текст программно, оставляя значение в параметре.

Как писать ГДЕ, чтобы запрос не тормозил

Условия виртуальной таблицы — в её параметры

Самый частый резерв скорости. Сравните:

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

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

Регистр взят условный, имена полей в вашей конфигурации могут отличаться. Сам принцип верен для Остатки, Обороты, ОстаткиИОбороты, СрезПоследних и СрезПервых: отбор по измерениям ставьте в параметр таблицы. А вот условие на ресурс (КоличествоОстаток > 0) оставьте в ГДЕ: в параметре виртуальной таблицы оно даст другой результат, потому что применится к движениям, а не к итогу.

Не оборачивайте поле в функцию

ГДЕ ГОД(Док.Дата) = 2025 заставляет СУБД вычислить год для каждой строки таблицы. Индекс по дате не используется. То же условие диапазоном работает по индексу:

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

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

Меньше разыменований через точку

ГДЕ Док.Контрагент.ИНН = &ИНН — это неявное соединение со справочником контрагентов. Для поля составного типа (Регистратор.Дата) платформа соединит таблицы всех возможных типов сразу. Помогают два приёма:

  1. Сначала найти ссылку отдельным запросом, а в условии сравнивать уже ссылку.
  2. Для составных полей — уточнить тип: ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата.

Большой список в В — через временную таблицу

Когда в В (&Список) уходят тысячи значений или В (ВЫБРАТЬ …) обращается к тяжёлому подзапросу, выгоднее положить значения во временную таблицу с индексом и соединиться с ней:

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

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

ИЛИ по разным полям — через ОБЪЕДИНИТЬ ВСЕ

ГДЕ Док.Контрагент = &К ИЛИ Док.Ответственный = &О часто приводит к полному просмотру таблицы. Два запроса с простыми условиями, объединённые через ОБЪЕДИНИТЬ, могут использовать каждый свой индекс. Если строка может попасть в обе части, а дубли не нужны, используйте ОБЪЕДИНИТЬ без ВСЕ.

ПриёмКогда применятьЧем рискуете
Отбор в параметрах виртуальной таблицывсегда, если условие по измерениюусловие на ресурс там меняет смысл
Диапазон вместо функции от поляотбор по году, месяцу, днюзабыть конец дня на правой границе
Временная таблица с индексомсписок из сотен и тысяч значенийлишний пакет, если значений пять
ОБЪЕДИНИТЬ вместо ИЛИИЛИ по разным полям на больших таблицахдубли при ОБЪЕДИНИТЬ ВСЕ

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

Ошибки фильтрации и как их распознать

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

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

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