Как отфильтровать данные в запросе 1С через ГДЕ и не потерять скорость
Содержание
Оператор ГДЕ в запросе 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 строку отбросил. По сути получилось внутреннее соединение.
Как исправить зависит от задачи:
- Нужна вся номенклатура, а цена — только положительная: перенесите условие в
ПО:ПО Цены.Номенклатура = Номенклатура.Ссылка И Цены.Цена > 0. - Нужна вся номенклатура, а без цены — тоже показать:
ГДЕ ЕСТЬNULL(Цены.Цена, 0) >= 0или уберите условие совсем.
Параметры вместо склейки текста
Значения в условие передают через параметры с амперсандом:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка,
| Контрагенты.Наименование
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("ИНН", ИННИзФормы);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Почему не собирать текст запроса сложением строк:
- 🛡 строка от пользователя, вклеенная в текст, может поменять логику запроса: лишняя кавычка ломает синтаксис, а подобранная — расширяет отбор;
- 📅 даты и числа не надо приводить к строковому формату, платформа передаст их сама;
- 🔁 текст запроса один и тот же при разных значениях, его легче читать и отлаживать.
Для поиска по части строки шаблон тоже собирают в параметре, а не в тексте:
Запрос.Текст = "... ГДЕ Номенклатура.Наименование ПОДОБНО &Шаблон СПЕЦСИМВОЛ ""~""";
Шаблон = СтрЗаменить(СтрокаПоиска, "~", "~~");
Шаблон = СтрЗаменить(Шаблон, "%", "~%");
Шаблон = СтрЗаменить(Шаблон, "_", "~_");
Шаблон = СтрЗаменить(Шаблон, "[", "~[");
Запрос.УстановитьПараметр("Шаблон", "%" + Шаблон + "%");
Экранирование нужно, чтобы пользователь, введя % или _, искал этот символ, а не получал «всё подряд».
Если параметр используется в тексте, но не установлен, запрос упадёт с ошибкой о незаданном значении параметра. Проверяйте имена: &Склад и УстановитьПараметр("Склады", …) — разные параметры.
Опциональный отбор (фильтр «по складу, если выбран») делают без склейки:
ГДЕ
(&ВсеСклады ИЛИ Остатки.Склад = &Склад)
Это работает, но такое ИЛИ мешает СУБД выбрать индекс. На больших таблицах надёжнее держать два текста запроса или добавлять условие в текст программно, оставляя значение в параметре.
Как писать ГДЕ, чтобы запрос не тормозил
Условия виртуальной таблицы — в её параметры
Самый частый резерв скорости. Сравните:
// Медленно: сначала считаются остатки по всем складам, потом отбор
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Остатки
ГДЕ
Остатки.Склад = &Склад
// Быстро: отбор встроен в расчёт остатков
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
Регистр взят условный, имена полей в вашей конфигурации могут отличаться. Сам принцип верен для Остатки, Обороты, ОстаткиИОбороты, СрезПоследних и СрезПервых: отбор по измерениям ставьте в параметр таблицы. А вот условие на ресурс (КоличествоОстаток > 0) оставьте в ГДЕ: в параметре виртуальной таблицы оно даст другой результат, потому что применится к движениям, а не к итогу.
Не оборачивайте поле в функцию
ГДЕ ГОД(Док.Дата) = 2025 заставляет СУБД вычислить год для каждой строки таблицы. Индекс по дате не используется. То же условие диапазоном работает по индексу:
ГДЕ
Док.Дата МЕЖДУ ДАТАВРЕМЯ(2025, 1, 1) И ДАТАВРЕМЯ(2025, 12, 31, 23, 59, 59)
Похожая история с ПОДОБНО "%текст%": шаблон с % в начале не может использовать индекс. ПОДОБНО "текст%" — может.
Меньше разыменований через точку
ГДЕ Док.Контрагент.ИНН = &ИНН — это неявное соединение со справочником контрагентов. Для поля составного типа (Регистратор.Дата) платформа соединит таблицы всех возможных типов сразу. Помогают два приёма:
- Сначала найти ссылку отдельным запросом, а в условии сравнивать уже ссылку.
- Для составных полей — уточнить тип:
ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата.
Большой список в В — через временную таблицу
Когда в В (&Список) уходят тысячи значений или В (ВЫБРАТЬ …) обращается к тяжёлому подзапросу, выгоднее положить значения во временную таблицу с индексом и соединиться с ней:
ВЫБРАТЬ
Т.Номенклатура
ПОМЕСТИТЬ ВТ_Отбор
ИЗ
&ТаблицаОтбора КАК Т
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&Дата,
Номенклатура В (ВЫБРАТЬ ВТ_Отбор.Номенклатура ИЗ ВТ_Отбор)
) КАК Остатки
В параметр ТаблицаОтбора передаётся таблица значений, у колонки должен быть задан тип.
ИЛИ по разным полям — через ОБЪЕДИНИТЬ ВСЕ
ГДЕ Док.Контрагент = &К ИЛИ Док.Ответственный = &О часто приводит к полному просмотру таблицы. Два запроса с простыми условиями, объединённые через ОБЪЕДИНИТЬ, могут использовать каждый свой индекс. Если строка может попасть в обе части, а дубли не нужны, используйте ОБЪЕДИНИТЬ без ВСЕ.
| Приём | Когда применять | Чем рискуете |
|---|---|---|
| Отбор в параметрах виртуальной таблицы | всегда, если условие по измерению | условие на ресурс там меняет смысл |
| Диапазон вместо функции от поля | отбор по году, месяцу, дню | забыть конец дня на правой границе |
| Временная таблица с индексом | список из сотен и тысяч значений | лишний пакет, если значений пять |
ОБЪЕДИНИТЬ вместо ИЛИ | ИЛИ по разным полям на больших таблицах | дубли при ОБЪЕДИНИТЬ ВСЕ |
Проверять эффект удобно замером: в конфигураторе есть замер производительности, а на клиент-серверной базе — технологический журнал и план запроса в СУБД.
Ошибки фильтрации и как их распознать
Частые вопросы
Можно ли использовать в ГДЕ поле, которого нет в ВЫБРАТЬ?
Можно ли ссылаться в ГДЕ на псевдоним поля из ВЫБРАТЬ?
Как отобрать строки, где поле равно одному из значений в другой таблице?
Поле В (ВЫБРАТЬ … ИЗ …) или через внутреннее соединение с этой таблицей. На больших объёмах соединение с индексированной временной таблицей обычно быстрее.