Отбор данных в языке запросов 1С: ГДЕ, логика, NULL и параметры
Содержание
Условие в запросе 1С пишут после ключевого слова ГДЕ, а значения передают через параметры с амперсандом: ГДЕ Реализация.Контрагент = &Контрагент. Параметр заполняют из кода методом Запрос.УстановитьПараметр("Контрагент", Ссылка). Несколько условий связывают через И, ИЛИ, НЕ, а для списков, диапазонов и масок есть операторы В, МЕЖДУ и ПОДОБНО.
Синтаксис одинаков для всех конфигураций на платформе 8.3: Бухгалтерии, ЗУП, УТ и самописных баз. Различаются только имена справочников и регистров. Примеры ниже построены на объектах БП 3.0, имена своих таблиц подставьте сами.
Базовое условие в запросе: секция ГДЕ и параметры
Секция ГДЕ идёт после ИЗ и соединений, но до СГРУППИРОВАТЬ ПО и УПОРЯДОЧИТЬ ПО. В выборку попадут только строки, для которых выражение истинно.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Документ,
| Реализация.СуммаДокумента КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Контрагент = &Контрагент
| И Реализация.Проведен";
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Результат = Запрос.Выполнить().Выгрузить();
Булево поле можно писать без сравнения: И Реализация.Проведен равносильно = ИСТИНА. Для отрицания ставят НЕ Реализация.ПометкаУдаления.
Операторы сравнения привычные:
| Оператор | Смысл | Пример |
|---|---|---|
= | равно | Номенклатура.Услуга = ЛОЖЬ |
<> | не равно | Контрагенты.ИНН <> "" |
>, < | больше, меньше | СуммаДокумента > 100000 |
>=, <= | больше или равно, меньше или равно | Дата >= &НачалоПериода |
Почему значения передают параметром, а не вклеивают в текст
Склеивать текст запроса со значением ("ГДЕ Сумма > " + Сумма) — плохая привычка. Число превратится в строку с пробелами-разделителями разрядов, ссылку так вообще не передать, а строка от пользователя с кавычкой сломает запрос. Параметр передаёт значение с типом, и платформе не нужно ничего разбирать.
Если параметр объявлен в тексте, но не установлен, запрос упадёт при выполнении. Поэтому проверяйте, что каждому &Имени соответствует свой вызов УстановитьПараметр.
Условие через конструктор запроса
В Конфигураторе откройте модуль, поставьте курсор в строку с текстом запроса и через контекстное меню вызовите конструктор. Вкладка «Условия» принимает поля перетаскиванием из левой панели. Конструктор сам предложит параметр с именем поля. Флажок «Произвольное» позволяет вписать выражение руками, например с ИЛИ или функцией.
Как объединить несколько условий: И, ИЛИ, НЕ и скобки
И требует, чтобы выполнялись все части. ИЛИ — хотя бы одна. НЕ переворачивает результат.
Приоритет такой: сначала НЕ, потом И, последним ИЛИ. Из-за этого условие без скобок часто работает не так, как задумано.
// Ошибка: сюда попадут ВСЕ проведённые документы по Петрову,
// независимо от суммы
ГДЕ
Р.Контрагент = &Иванов
ИЛИ Р.Контрагент = &Петров
И Р.СуммаДокумента > 100000
// Верно: сумма проверяется для обоих контрагентов
ГДЕ
(Р.Контрагент = &Иванов
ИЛИ Р.Контрагент = &Петров)
И Р.СуммаДокумента > 100000
В первом варианте платформа читает условие как Иванов ИЛИ (Петров И сумма > 100 000). Документы Иванова попадут в выборку на любую сумму.
Правило простое: если в одном условии встречаются и И, и ИЛИ, ставьте скобки всегда, даже когда приоритет вас устраивает. Через полгода текст откроет другой человек.
НЕ удобно сочетать с другими операторами: НЕ Р.Склад В (&ЗакрытыеСклады), НЕ Н.Наименование ПОДОБНО "%архив%".
Специальные операторы: В, В ИЕРАРХИИ, МЕЖДУ, ПОДОБНО
Эти операторы заменяют длинные цепочки из ИЛИ и сравнений.
В — значение из списка
ГДЕ Р.Контрагент В (&СписокКонтрагентов)
В параметр можно передать Массив или СписокЗначений. Внутри скобок допустимы и литералы, и вложенный запрос:
ГДЕ Р.Контрагент В
(ВЫБРАТЬ
Договоры.Владелец
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
ГДЕ
Договоры.ВидДоговора = ЗНАЧЕНИЕ(Перечисление.ВидыДоговоровКонтрагентов.СПокупателем))
Вложенный запрос в В может возвращать несколько полей, тогда слева пишут их набор в скобках: (Т.Номенклатура, Т.Склад) В (ВЫБРАТЬ ...).
В ИЕРАРХИИ — элемент вместе с подчинёнными
Для иерархических справочников Н.Ссылка В ИЕРАРХИИ (&Группа) отберёт саму группу, все вложенные группы и элементы на любой глубине. Обычное В вернёт только саму группу. Это главный приём для отчётов «по группе номенклатуры» или «по группе контрагентов».
МЕЖДУ — диапазон с включёнными границами
ГДЕ Р.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Обе границы входят в диапазон. Запись равносильна паре >= и <=.
Конец периода передавайте как КонецДня(Дата), а не как саму дату. Дата без времени равна 00:00:00, и все документы последнего дня, проведённые позже полуночи, выпадут из выборки.
ПОДОБНО — поиск по маске строки
| Символ | Что означает | Пример |
|---|---|---|
% | любое количество любых символов | "%болт%" |
_ | ровно один любой символ | "ДГ-___" |
[...] | один символ из перечня или диапазона | "[А-В]%" |
[^...] | один символ не из перечня | "[^0-9]%" |
Маску тоже лучше передавать параметром: Н.Наименование ПОДОБНО &Маска, а в коде собрать "%" + Строка + "%". Если в искомой строке есть сам % или _, их экранируют: ПОДОБНО "%50!%%" СПЕЦСИМВОЛ "!".
Сравнение ссылок, перечислений, дат и типов
С датами и ссылками новички спотыкаются чаще, чем с числами. Сводка приёмов:
- 📌 Пустая ссылка:
Р.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка). - 📌 Значение перечисления:
ЗНАЧЕНИЕ(Перечисление.ИмяПеречисления.ИмяЗначения). Имена берите из дерева метаданных, не по памяти. - 📌 Предопределённый элемент:
ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетыСПокупателями). - 📌 Конкретная дата в тексте:
ДАТАВРЕМЯ(2025, 3, 31), пустая дата —ДАТАВРЕМЯ(1, 1, 1). - 📌 Тип документа-регистратора:
Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг. - 📌 Тип через функции:
ТИПЗНАЧЕНИЯ(Т.Субконто1) = ТИП(Справочник.Контрагенты).
Оператор ССЫЛКА нужен для полей составного типа. Например, у регистратора движений могут быть десятки видов документов. Условие ССЫЛКА отсекает лишние до того, как вы начнёте обращаться к их реквизитам через точку.
Условие с параметрами, которое меняет пользователь
В отчётах и обработках отбор часто необязательный: пользователь заполнил склад — фильтруем, оставил пустым — показываем всё. Есть два подхода.
Вариант 1. Одно условие с проверкой параметра
ГДЕ
(&Склад = ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка)
ИЛИ Т.Склад = &Склад)
Текст запроса не меняется, и это удобно. Минус: из-за ИЛИ СУБД хуже использует индекс по полю склада. На маленьких таблицах разницы нет, на больших регистрах запрос заметно тормозит.
Вариант 2. Собрать текст условий в коде
ТекстУсловий = "";
Если ЗначениеЗаполнено(Склад) Тогда
ТекстУсловий = ТекстУсловий + "
| И Т.Склад = &Склад";
Запрос.УстановитьПараметр("Склад", Склад);
КонецЕсли;
Если ЗначениеЗаполнено(ГруппаТоваров) Тогда
ТекстУсловий = ТекстУсловий + "
| И Т.Номенклатура В ИЕРАРХИИ (&Группа)";
Запрос.УстановитьПараметр("Группа", ГруппаТоваров);
КонецЕсли;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура,
| Т.Количество
|ИЗ
| ВременнаяТаблицаОстатков КАК Т
|ГДЕ
| ИСТИНА" + ТекстУсловий;
Конструкция ГДЕ ИСТИНА позволяет приклеивать любые условия через И, не думая, какое из них первое. Значения по-прежнему идут параметрами, в текст попадают только имена полей.
Склеивайте в текст только то, что пишете сами в коде. Строку, которую ввёл пользователь, в текст запроса не вставляйте никогда: только через параметр.
В отчётах на системе компоновки данных (СКД) отбор обычно вообще не пишут в тексте. Поля попадают в отбор пользователя автоматически, а СКД сама достраивает условие при выполнении.
NULL и пустые значения: как не потерять строки
NULL в запросе означает «значения нет вовсе». Оно появляется в трёх случаях:
- 🔗 после левого соединения, если справа не нашлось пары;
- 🔗 при обращении через точку к реквизиту, которого нет у этого типа составного поля;
- 🔗 в итоговых строках и при агрегатах по пустому набору.
Любое сравнение с NULL через = или <> даёт не истину, а неопределённый результат. Строка в выборку не попадёт. Поэтому так писать нельзя:
ГДЕ Цены.Цена = NULL // не сработает никогда
Правильно:
ГДЕ Цены.Цена ЕСТЬ NULL
ГДЕ Цены.Цена ЕСТЬ НЕ NULL
ГДЕ ЕСТЬNULL(Цены.Цена, 0) = 0
Последняя строка находит сразу и товары без цены вообще, и товары с нулевой ценой. Функция ЕСТЬNULL подставляет второе значение вместо NULL.
Пустая ссылка, пустая строка и нулевая дата — это не NULL. ЕСТЬ NULL их не поймает. Для них нужны свои проверки:
| Что проверяем | Условие |
|---|---|
| Ссылка не заполнена | Поле = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка) |
| Строка пустая | Поле = "" |
| Дата пустая | Поле = ДАТАВРЕМЯ(1, 1, 1) |
| Число не задано | Поле = 0 |
| Поле составного типа не заполнено | Поле = НЕОПРЕДЕЛЕНО |
ГДЕ, ИМЕЮЩИЕ или ПО: куда ставить условие
Одно и то же условие в разных местах запроса даёт разный результат.
ГДЕ фильтрует исходные строки до группировки. Агрегатные функции здесь писать нельзя.
ИМЕЮЩИЕ фильтрует уже сгруппированные строки. Сюда идут условия на СУММА, КОЛИЧЕСТВО, МАКСИМУМ:
ВЫБРАТЬ
Р.Контрагент,
СУММА(Р.СуммаДокумента) КАК Выручка
ИЗ
Документ.РеализацияТоваровУслуг КАК Р
ГДЕ
Р.Проведен
И Р.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Р.Контрагент
ИМЕЮЩИЕ
СУММА(Р.СуммаДокумента) > 500000
ПО в левом соединении ограничивает только присоединяемую таблицу. Строки левой таблицы останутся все.
Типичная ловушка: условие на поле правой таблицы перенесли из ПО в ГДЕ. Строки без пары получили там NULL, условие для них ложно, и левое соединение фактически превратилось во внутреннее. Хотите оставить все товары, а цены взять только нужного вида — пишите вид цены в ПО, а не в ГДЕ.
Условие в параметрах виртуальной таблицы
У регистров есть виртуальные таблицы Остатки, Обороты, СрезПоследних. Отбор для них передают вторым (у оборотов — четвёртым) параметром:
ИЗ
РегистрСведений.КурсыВалют.СрезПоследних(
&НаДату,
Валюта = &Валюта) КАК Курсы
Условие внутри скобок платформа применит до расчёта среза или остатков. То же условие в ГДЕ сработает уже после: сначала посчитаются остатки по всем складам и товарам, а потом лишнее отбросится. Результат может совпасть, а время выполнения — нет. Для СрезПоследних разница бывает и в результате: отбор в ГДЕ по реквизиту может отсечь строку, хотя предыдущая запись по этому измерению подошла бы.
Как ускорить запрос с условиями
Медленный запрос почти всегда упирается в то, что СУБД не может воспользоваться индексом. Что помогает:
- ⚡ Отбор в параметрах виртуальных таблиц, а не в
ГДЕпосле них. - ⚡ Условие на само поле, а не на функцию от него.
ГОД(Р.Дата) = 2025заставляет перебрать все документы.Р.Дата МЕЖДУ &Начало И &Конецидёт по индексу. - ⚡ ИЛИ по разным полям заменить на ОБЪЕДИНИТЬ ВСЕ из двух запросов, у каждого своё простое условие.
- ⚡ Без обращения через точку в условии.
Р.Контрагент.Родитель = &Группанеявно присоединяет справочник. ЛучшеР.Контрагент В ИЕРАРХИИ (&Группа)или отдельная временная таблица. - ⚡ Временная таблица вместо большого вложенного запроса. Поместите список во временную таблицу с
ИНДЕКСИРОВАТЬ ПО, а в условии используйтеВ (ВЫБРАТЬ ... ИЗ ВТСписок). - ⚡ Индекс у реквизита. Если по реквизиту справочника или документа фильтруют постоянно, в его свойствах в Конфигураторе можно включить индексирование. Это изменение конфигурации, на типовой базе его делают через расширение или после согласования с тем, кто её сопровождает.
Проверить эффект проще всего замером: выполните запрос в консоли запросов дважды, до и после правки, на одной и той же базе.
Ошибки в условиях, которые не видно сразу
Синтаксическую ошибку платформа покажет при выполнении. Опаснее те, после которых запрос работает, но возвращает не те строки.
- ❌ Сравнение строки неограниченной длины. Реквизиты вроде
Комментарийчасто объявлены без ограничения длины, и сравнение через=с ними не допускается. Оберните поле:ВЫРАЗИТЬ(Р.Комментарий КАК СТРОКА(200)) = &Текстили используйтеПОДСТРОКА(Р.Комментарий, 1, 200). - ❌ Параметр не того типа. В параметр передали строку с наименованием, а сравнивают со ссылкой. Ошибки не будет, выборка просто окажется пустой.
- ❌ Помеченные на удаление. Условие по датам и контрагенту есть, а
НЕ Р.ПометкаУдаленияилиР.Проведензабыли. В отчёт попадают черновики и удалённые документы. - ❌ Параметр массива пуст.
В (&Список)с пустым массивом не даст ни одной строки. Если пустой список должен означать «все», проверяйте это в коде и не добавляйте условие. - ❌ Регистр букв в ПОДОБНО. Чувствительность к регистру зависит от СУБД и её настроек. Если поиск должен работать везде одинаково, приводите обе части к одному регистру функциями
ВРЕГ()илиНРЕГ().
Частые вопросы
Можно ли использовать ВЫБОР в условии ГДЕ?
ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ возвращает значение, и его можно сравнить: ГДЕ ВЫБОР КОГДА Т.Склад = &Основной ТОГДА Т.Количество ИНАЧЕ 0 КОНЕЦ > 0. Но индекс в таком условии СУБД не использует.Как отобрать документы за текущий месяц без параметров?
Р.Дата >= НАЧАЛОПЕРИОДА(&ТекущаяДата, МЕСЯЦ). Текущую дату всё равно передают параметром, потому что функции «сейчас» в языке запросов нет.Чем В с вложенным запросом лучше соединения?
В просто проверит наличие.Можно ли передать в В таблицу значений?
ПОМЕСТИТЬ временной таблицы, а потом используйте В (ВЫБРАТЬ Поле ИЗ ВТ). Либо выгрузите нужную колонку в массив методом ВыгрузитьКолонку.