Отбор данных в языке запросов 1С: ГДЕ, логика, NULL и параметры

· 10 мин

Содержание

Условие в запросе 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 в запросе означает «значения нет вовсе». Оно появляется в трёх случаях:

  1. 🔗 после левого соединения, если справа не нашлось пары;
  2. 🔗 при обращении через точку к реквизиту, которого нет у этого типа составного поля;
  3. 🔗 в итоговых строках и при агрегатах по пустому набору.

Любое сравнение с NULL через = или <> даёт не истину, а неопределённый результат. Строка в выборку не попадёт. Поэтому так писать нельзя:

ГДЕ Цены.Цена = NULL // не сработает никогда

Правильно:

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

Последняя строка находит сразу и товары без цены вообще, и товары с нулевой ценой. Функция ЕСТЬNULL подставляет второе значение вместо NULL.

Пустая ссылка, пустая строка и нулевая дата — это не NULL. ЕСТЬ NULL их не поймает. Для них нужны свои проверки:

Что проверяемУсловие
Ссылка не заполненаПоле = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка)
Строка пустаяПоле = ""
Дата пустаяПоле = ДАТАВРЕМЯ(1, 1, 1)
Число не заданоПоле = 0
Поле составного типа не заполненоПоле = НЕОПРЕДЕЛЕНО

ГДЕ, ИМЕЮЩИЕ или ПО: куда ставить условие

Одно и то же условие в разных местах запроса даёт разный результат.

ГДЕ фильтрует исходные строки до группировки. Агрегатные функции здесь писать нельзя.

ИМЕЮЩИЕ фильтрует уже сгруппированные строки. Сюда идут условия на СУММА, КОЛИЧЕСТВО, МАКСИМУМ:

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

ПО в левом соединении ограничивает только присоединяемую таблицу. Строки левой таблицы останутся все.

Типичная ловушка: условие на поле правой таблицы перенесли из ПО в ГДЕ. Строки без пары получили там NULL, условие для них ложно, и левое соединение фактически превратилось во внутреннее. Хотите оставить все товары, а цены взять только нужного вида — пишите вид цены в ПО, а не в ГДЕ.

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

У регистров есть виртуальные таблицы Остатки, Обороты, СрезПоследних. Отбор для них передают вторым (у оборотов — четвёртым) параметром:

ИЗ
	РегистрСведений.КурсыВалют.СрезПоследних(
		&НаДату,
		Валюта = &Валюта) КАК Курсы

Условие внутри скобок платформа применит до расчёта среза или остатков. То же условие в ГДЕ сработает уже после: сначала посчитаются остатки по всем складам и товарам, а потом лишнее отбросится. Результат может совпасть, а время выполнения — нет. Для СрезПоследних разница бывает и в результате: отбор в ГДЕ по реквизиту может отсечь строку, хотя предыдущая запись по этому измерению подошла бы.

Как ускорить запрос с условиями

Медленный запрос почти всегда упирается в то, что СУБД не может воспользоваться индексом. Что помогает:

  • ⚡ Отбор в параметрах виртуальных таблиц, а не в ГДЕ после них.
  • ⚡ Условие на само поле, а не на функцию от него. ГОД(Р.Дата) = 2025 заставляет перебрать все документы. Р.Дата МЕЖДУ &Начало И &Конец идёт по индексу.
  • ⚡ ИЛИ по разным полям заменить на ОБЪЕДИНИТЬ ВСЕ из двух запросов, у каждого своё простое условие.
  • ⚡ Без обращения через точку в условии. Р.Контрагент.Родитель = &Группа неявно присоединяет справочник. Лучше Р.Контрагент В ИЕРАРХИИ (&Группа) или отдельная временная таблица.
  • ⚡ Временная таблица вместо большого вложенного запроса. Поместите список во временную таблицу с ИНДЕКСИРОВАТЬ ПО, а в условии используйте В (ВЫБРАТЬ ... ИЗ ВТСписок).
  • ⚡ Индекс у реквизита. Если по реквизиту справочника или документа фильтруют постоянно, в его свойствах в Конфигураторе можно включить индексирование. Это изменение конфигурации, на типовой базе его делают через расширение или после согласования с тем, кто её сопровождает.

Проверить эффект проще всего замером: выполните запрос в консоли запросов дважды, до и после правки, на одной и той же базе.

Проверьте себя
Какой оператор отберёт группу номенклатуры вместе со всеми вложенными элементами?
Документы проведены 31 марта в 15:00. Конец периода передан как дата 31.03 без времени. Попадут ли они в МЕЖДУ?
Как найти товары, у которых после левого соединения нет цены?
Где писать условие СУММА(Сумма) > 1000?

Ошибки в условиях, которые не видно сразу

Синтаксическую ошибку платформа покажет при выполнении. Опаснее те, после которых запрос работает, но возвращает не те строки.

  • ❌ Сравнение строки неограниченной длины. Реквизиты вроде Комментарий часто объявлены без ограничения длины, и сравнение через = с ними не допускается. Оберните поле: ВЫРАЗИТЬ(Р.Комментарий КАК СТРОКА(200)) = &Текст или используйте ПОДСТРОКА(Р.Комментарий, 1, 200).
  • ❌ Параметр не того типа. В параметр передали строку с наименованием, а сравнивают со ссылкой. Ошибки не будет, выборка просто окажется пустой.
  • ❌ Помеченные на удаление. Условие по датам и контрагенту есть, а НЕ Р.ПометкаУдаления или Р.Проведен забыли. В отчёт попадают черновики и удалённые документы.
  • ❌ Параметр массива пуст. В (&Список) с пустым массивом не даст ни одной строки. Если пустой список должен означать «все», проверяйте это в коде и не добавляйте условие.
  • ❌ Регистр букв в ПОДОБНО. Чувствительность к регистру зависит от СУБД и её настроек. Если поиск должен работать везде одинаково, приводите обе части к одному регистру функциями ВРЕГ() или НРЕГ().

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

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