Как фильтровать данные в запросе 1С: условие ГДЕ без потерь строк и тормозов
Содержание
Оператор ГДЕ в языке запросов 1С 8.3 отбирает строки источника по условию: в результат попадают только те, для которых условие истинно. Пишется он после ИЗ и соединений, но до СГРУППИРОВАТЬ ПО: ВЫБРАТЬ ... ИЗ ... ГДЕ Условие.
Частое заблуждение — что ГДЕ «просто фильтр» и его место в запросе ни на что не влияет. На деле одно условие в ГДЕ вместо ПО превращает левое соединение во внутреннее, а условие по реквизиту виртуальной таблицы вместо её параметров заставляет СУБД читать весь регистр. Разберём синтаксис и эти ловушки на примерах.
Синтаксис ГДЕ и порядок выполнения запроса
Минимальный запрос с отбором выглядит так:
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
И НЕ Номенклатура.ПометкаУдаления
Логически запрос выполняется в таком порядке:
ИЗи соединения — собирается набор строк.ГДЕ— отбрасываются строки, где условие не истинно.СГРУППИРОВАТЬ ПО— оставшиеся строки сворачиваются.ИМЕЮЩИЕ— фильтр уже по сгруппированным строкам.ВЫБРАТЬ,УПОРЯДОЧИТЬ ПО,ИТОГИ.
Отсюда два следствия. В ГДЕ нельзя использовать агрегатные функции (СУММА, КОЛИЧЕСТВО, МАКСИМУМ) — для них есть ИМЕЮЩИЕ. И в ГДЕ нельзя сослаться на псевдоним поля из ВЫБРАТЬ: пишите выражение целиком.
Операции сравнения и функции, которые работают в условии
| Конструкция | Пример | Что делает |
|---|---|---|
=, <>, <, >, <=, >= | Цена > 1000 | Обычное сравнение |
МЕЖДУ ... И ... | Дата МЕЖДУ &Начало И &Конец | Диапазон, границы включаются |
В (...) | Склад В (&СписокСкладов) | Значение входит в список, массив или результат подзапроса |
В ИЕРАРХИИ | Номенклатура В ИЕРАРХИИ (&Группа) | Элемент и всё, что лежит в группе и её подгруппах |
ПОДОБНО | Наименование ПОДОБНО "%болт%" | Поиск по шаблону строки |
ЕСТЬ NULL | Цены.Цена ЕСТЬ NULL | Проверка на NULL |
ССЫЛКА | Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг | Проверка типа значения |
В шаблоне ПОДОБНО символ % означает любую последовательность символов, _ — ровно один символ. Если нужно искать сам знак процента, задайте спецсимвол: Артикул ПОДОБНО "%!%%" СПЕЦСИМВОЛ "!".
Параметры вместо значений в тексте
Значения для сравнения передавайте параметрами, а не склеивайте строку запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка,
| Реализация.СуммаДокумента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Реализация.Контрагент = &Контрагент
| И Реализация.Проведен";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаОкончания));
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Результат = Запрос.Выполнить().Выгрузить();
Параметр защищает от ошибок с кавычками и форматом дат. Кроме того, СУБД может переиспользовать план запроса.
Конец периода передавайте через КонецДня(). Если отдать в &КонецПериода дату без времени, документы последнего дня после 00:00:00 в выборку не попадут.
Предопределённые значения и перечисления в тексте запроса указывают через ЗНАЧЕНИЕ(): ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход), Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказовКлиентов.Закрыт). Даты-константы задаются функцией ДАТАВРЕМЯ(2025, 1, 1).
Порядок И, ИЛИ, НЕ и зачем ставить скобки в условии ГДЕ
Логические операции в языке запросов 1С выполняются по приоритету:
- Сначала операции сравнения (
=,>,В,ПОДОБНОи т. д.). - Затем
НЕ. - Затем
И. - Последним —
ИЛИ.
На этом спотыкаются чаще всего. Задача: выбрать проведённые документы по складу «Основной» или «Розница». Пишут так:
ГДЕ
Док.Проведен
И Док.Склад = &Основной
ИЛИ Док.Склад = &Розница
Из-за приоритета И запрос читается как (Проведен И Склад = Основной) ИЛИ Склад = Розница. В результат попадут и непроведённые, и помеченные на удаление документы розничного склада. Правильно:
ГДЕ
Док.Проведен
И (Док.Склад = &Основной ИЛИ Док.Склад = &Розница)
А ещё лучше — через В, где скобки уже не нужны:
ГДЕ
Док.Проведен
И Док.Склад В (&Основной, &Розница)
Правило практика: если в одном условии встречаются И и ИЛИ, ставьте скобки всегда, даже когда приоритет и так даёт нужный результат. Через полгода ни вы, ни коллега не будете гадать, что имелось в виду.
НЕ относится только к ближайшему операнду. НЕ А И Б — это (НЕ А) И Б. Чтобы инвертировать всё условие, берите его в скобки: НЕ (А И Б).
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)). ПОДОБНО с такими полями работает, но на больших таблицах медленно.
Типичные ошибки в условии ГДЕ и как их отладить
Как искать причину пошагово
- Уберите условие ГДЕ целиком и проверьте, что исходные данные вообще есть.
- Возвращайте условия по одному. На каком условии строки пропали — там и ошибка.
- Выведите проверяемые поля в
ВЫБРАТЬ. Часто видно, что вместо ожидаемой ссылки там NULL или значение другого типа. - Проверьте параметры. Перед
Выполнить()посмотрите в отладчике значения вЗапрос.Параметры: пустая ссылка вместо выбранного контрагента — частая причина пустого результата. - Прогоните запрос в консоли запросов. Внешняя обработка «Консоль запросов» показывает результат, время выполнения и позволяет менять параметры без правки кода.
Ещё один источник путаницы — ограничения доступа на уровне записей (RLS). Пользователь с ограниченными правами получит меньше строк, чем администратор, хотя условие ГДЕ одно и то же. Если результат различается у разных пользователей, начните с проверки прав.
Частые вопросы
Чем ГДЕ отличается от ИМЕЮЩИЕ?
Можно ли передать в условие В массив или список значений?
Поле В (&Список) можно задать массивом, списком значений или таблицей значений с одной колонкой. Пустой массив даст пустой результат по этому условию.Как отобрать записи за весь день, если в поле есть время?
НачалоДня() и КонецДня() и используйте МЕЖДУ. Так условие попадёт в индекс по дате, в отличие от вариантов с функциями над полем.Регистр в ПОДОБНО учитывается?
ВРЕГ(), но помните, что выражение над полем не использует индекс.Как в ГДЕ проверить булево поле?
ГДЕ Док.Проведен. Явное = ИСТИНА тоже работает, но короткая запись читается проще.