Пустой запрос 1С нужно обрабатывать отдельной веткой кода: отчёт должен написать «Нет данных», обработка не должна создавать пустой документ, а печатная форма не должна открываться чистым листом. Проверить, что результат запроса пустой, можно одним вызовом РезультатЗапроса.Пустой(). Ниже разобрано, когда этого метода достаточно, чем он отличается от Следующий() и Количество(), как проверять пакетный запрос и где искать причину, если запрос пустой, а данные в базе точно есть.
Метод Пустой() есть у объекта РезультатЗапроса, который возвращает Запрос.Выполнить(). Он возвращает Истина, если в результате нет ни одной записи, и при этом не создаёт выборку. Типичный сценарий — команда формы обработки, которая заполняет табличную часть продажами за период:
&НаКлиенте
Процедура ЗаполнитьТовары(Команда)
Если Не ЗначениеЗаполнено(Объект.КонецПериода)
Или Объект.НачалоПериода > Объект.КонецПериода Тогда
ПоказатьПредупреждение(, "Укажите корректный период.");
Возврат;
КонецЕсли;
Если Не ЗаполнитьТоварыНаСервере() Тогда
ПоказатьПредупреждение(, "За выбранный период проведённых продаж нет.");
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ЗаполнитьТоварыНаСервере()
Объект.Товары.Очистить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| СУММА(Товары.Количество) КАК Количество,
| СУММА(Товары.Сумма) КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка.Проведен
| И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|
|СГРУППИРОВАТЬ ПО
| Товары.Номенклатура";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(Объект.НачалоПериода));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(Объект.КонецПериода));
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат Ложь;
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
ЗаполнитьЗначенияСвойств(Объект.Товары.Добавить(), Выборка);
КонецЦикла;
Возврат Истина;
КонецФункции
Имена объектов в примере условные. Документ РеализацияТоваровУслуг с табличной частью Товары есть в УТ 11 и БП 3.0, но состав колонок в них отличается. Реквизиты НачалоПериода, КонецПериода и табличную часть Товары у обработки вы создаёте сами.
РезультатЗапроса = Запрос.Выполнить(); — результат сохраняется в переменную, чтобы и проверка, и выборка работали с одним выполнением запроса.Если РезультатЗапроса.Пустой() Тогда Возврат Ложь; — ранний выход. Если запрос пустой, дальше код не идёт, и основная ветка не обрастает вложенными условиями.Объект.Товары.Очистить() стоит до проверки: если за новый период продаж нет, строки от прошлого заполнения не останутся на форме и не введут пользователя в заблуждение.Ложь: сервер только сообщает факт.КонецДня(Объект.КонецПериода) — без него документы последнего дня периода не попадут в выборку. Подробнее о границах периода — в статье про даты в запросе.Проверить результат запроса на пустоту можно несколькими способами, и они не взаимозаменяемы:
| Способ | Что делает | Когда подходит |
|---|---|---|
РезультатЗапроса.Пустой() | Отвечает только на вопрос, есть ли записи | Ранний выход или сообщение «нет данных» до обработки |
Если Выборка.Следующий() Тогда … Иначе … | Переходит к первой записи; Ложь — записей нет | Запрос возвращает одну строку: цену, остаток, последний документ |
Выборка.Количество() | Возвращает число записей выборки | Нужно само число: для индикатора прогресса или текста «найдено N документов» |
РезультатЗапроса.Выгрузить().Количество() | Выгружает весь результат в таблицу значений и считает строки | Только если таблица значений всё равно нужна дальше |
Для одиночного значения отдельная проверка не нужна: Следующий() сам сообщает, есть ли запись.
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Цена = Выборка.Цена;
Иначе
Цена = 0;
КонецЕсли;
Если нужен только факт существования — есть ли у контрагента хоть один непроведённый документ, есть ли движения по складу за месяц, — ограничьте запрос конструкцией ВЫБРАТЬ ПЕРВЫЕ 1. Пустой() ответит так же, но СУБД не придётся отдавать тысячи строк, которые никто не прочитает.
ВЫБРАТЬ ПЕРВЫЕ 1
ИСТИНА КАК ЕстьДанные
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Контрагент = &Контрагент
И НЕ Реализация.Проведен
И НЕ Реализация.ПометкаУдаления
Запрос с СУММА, МАКСИМУМ или КОЛИЧЕСТВО без СГРУППИРОВАТЬ ПО возвращает одну строку, даже если под условие не попал ни один документ: в поле суммы будет NULL, в поле количества — 0. Пустой() для такого результата вернёт Ложь. Здесь проверяют не результат, а значение: агрегат оборачивают в ЕСТЬNULL() и сравнивают с нулём уже в коде.
ВЫБРАТЬ
ЕСТЬNULL(СУММА(Товары.Сумма), 0) КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка.Контрагент = &Контрагент
И Товары.Ссылка.Проведен
Выполнить() возвращает результат только последнего запроса пакета. Если нужны данные из нескольких запросов — например, шапки документов и их товары для печати, — вызывают ВыполнитьПакет(). Он возвращает массив результатов в том порядке, в котором запросы стоят в тексте, и пустоту проверяют у каждого нужного элемента. Функция ниже рассчитана на модуль менеджера документа или серверный общий модуль:
Функция ДанныеДляПечати(МассивДокументов) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Ссылка,
| Реализация.Номер КАК Номер,
| Реализация.Дата КАК Дата,
| Реализация.Контрагент КАК Контрагент
|ПОМЕСТИТЬ ВТДокументы
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Ссылка В (&МассивДокументов)
| И Реализация.Проведен
|
|ИНДЕКСИРОВАТЬ ПО
| Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТДокументы.Ссылка КАК Ссылка,
| ВТДокументы.Номер КАК Номер,
| ВТДокументы.Дата КАК Дата,
| ВТДокументы.Контрагент КАК Контрагент
|ИЗ
| ВТДокументы КАК ВТДокументы
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка,
| Товары.НомерСтроки КАК НомерСтроки,
| Товары.Номенклатура КАК Номенклатура,
| Товары.Количество КАК Количество,
| Товары.Сумма КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТДокументы КАК ВТДокументы
| ПО Товары.Ссылка = ВТДокументы.Ссылка
|
|УПОРЯДОЧИТЬ ПО
| Ссылка,
| НомерСтроки";
Запрос.УстановитьПараметр("МассивДокументов", МассивДокументов);
Результаты = Запрос.ВыполнитьПакет();
// Результаты[0] — служебный результат ПОМЕСТИТЬ, данных документов в нём нет.
РезультатШапки = Результаты[1];
РезультатТоваров = Результаты[2];
Если РезультатШапки.Пустой() Тогда
Возврат Неопределено; // среди переданных нет ни одного проведённого документа
КонецЕсли;
Данные = Новый Структура;
Данные.Вставить("Шапки", РезультатШапки.Выгрузить());
Данные.Вставить("Товары", РезультатТоваров.Выгрузить());
Возврат Данные;
КонецФункции
Для запроса с ПОМЕСТИТЬ в массиве лежит не содержимое временной таблицы, а служебный результат: одна строка с полем Количество, где записано число помещённых записей. Пустой() для него вернёт Ложь даже при нуле записей. Поэтому пустоту временной таблицы проверяют либо по этому числу, либо, что нагляднее, по результату следующего запроса, который из неё читает. Как устроены временные таблицы и пакеты, подробно описано в статье про таблицы запроса.
В длинном пакете индексы легко перепутать при правке текста. На время отладки пройдите по массиву циклом и выведите, что вернул каждый запрос:
Результаты = Запрос.ВыполнитьПакет();
Для Индекс = 0 По Результаты.ВГраница() Цикл
Результат = Результаты[Индекс];
Если Результат = Неопределено Тогда
Сообщить(СтрШаблон("Запрос %1: результата нет", Индекс + 1));
ИначеЕсли Результат.Пустой() Тогда
Сообщить(СтрШаблон("Запрос %1: пусто", Индекс + 1));
Иначе
Сообщить(СтрШаблон("Запрос %1: записей %2", Индекс + 1, Результат.Выбрать().Количество()));
КонецЕсли;
КонецЦикла;
Для запросов с ПОМЕСТИТЬ цикл покажет одну запись — ту самую служебную строку. Проверка на Неопределено страхует от элементов без результата, например от запросов УНИЧТОЖИТЬ. СтрШаблон() доступна с платформы 8.3.6; в конфигурациях на БСП 3.x вместо Сообщить() в серверном коде принято вызывать ОбщегоНазначения.СообщитьПользователю().
Запрос выполнился без ошибки, но вернул пусто. Обычно причина в одном из пунктов таблицы:
| Причина | Как выглядит | Что сделать |
|---|---|---|
Параметр равен Неопределено или значению другого типа | В форме не заполнен реквизит; в параметр передали строку вместо ссылки или ссылку на другой справочник | Вывести параметры перед выполнением (код ниже), не выполнять запрос с незаполненными обязательными параметрами |
| Конец периода без времени | &КонецПериода = 31.01.2026 0:00:00 — документы 31 января в выборку не попадают, а при одинаковых датах начала и конца результат пустой | Передавать КонецДня() от даты окончания |
| Внутреннее соединение | Отбрасываются строки без пары во второй таблице: товар без цены, документ без движений | Временно заменить на ЛЕВОЕ СОЕДИНЕНИЕ и посмотреть, где появился NULL |
Условие на правую таблицу левого соединения в ГДЕ | ГДЕ Цены.Цена > 0 отсекает строки, где цены нет, и левое соединение работает как внутреннее | Перенести условие в ПО (ЛЕВОЕ СОЕДИНЕНИЕ ... ПО Товары.Номенклатура = Цены.Номенклатура И Цены.Цена > 0) или явно пропустить NULL в ГДЕ: (Цены.Цена > 0 ИЛИ Цены.Цена ЕСТЬ NULL) |
Сравнение с NULL через «=» | ГДЕ Поле = NULL не выполняется ни для одной строки | Писать Поле ЕСТЬ NULL |
| Срез последних на слишком раннюю дату | Цены установлены 10-го числа, запрос на 5-е — строки нет | Сверить период среза с датой первой записи регистра |
| Ограничение доступа на уровне записей | Под администратором данные есть, под пользователем — пусто: с РАЗРЕШЕННЫЕ недоступные записи молча отбрасываются | Проверить запрос под учётной записью этого пользователя и его права |
| Отбор по проведению | Тестовые документы записаны, но не проведены | Проверить условия на Проведен и ПометкаУдаления |
Если параметр не установлен совсем, пустого результата не будет — запрос упадёт с ошибкой «Не задано значение параметра». Коварнее вариант, когда параметр установлен, но в нём Неопределено: ошибки нет, строк тоже нет.
Неопределено, пустая дата или ссылка не на тот справочник.Запрос.Выполнить() и посмотрите Запрос.Текст и Запрос.Параметры через «Вычислить выражение».ГДЕ по одному, пока не появятся строки. Последнее убранное условие и есть причина.NULL покажет таблицу, в которой не нашлось пары.ВЫБРАТЬ * ИЗ ВТИмя и выполните пакет до этого места.Процедура для первого шага. Её можно положить в серверный общий модуль и вызвать перед Запрос.Выполнить() на время отладки:
Процедура ВывестиПараметрыЗапроса(Запрос) Экспорт
Для Каждого ОписаниеПараметра Из Запрос.НайтиПараметры() Цикл
Имя = ОписаниеПараметра.Имя;
Если Не Запрос.Параметры.Свойство(Имя) Тогда
Сообщить(СтрШаблон("&%1 - не установлен", Имя));
Продолжить;
КонецЕсли;
Значение = Запрос.Параметры[Имя];
Если Значение = Неопределено Тогда
Представление = "Неопределено";
ИначеЕсли ТипЗнч(Значение) = Тип("Массив") Тогда
Представление = СтрШаблон("массив, элементов: %1", Значение.Количество());
Иначе
Представление = Строка(Значение);
КонецЕсли;
Сообщить(СтрШаблон("&%1 = %2 [%3]", Имя, Представление, Строка(ТипЗнч(Значение))));
КонецЦикла;
КонецПроцедуры
НайтиПараметры() возвращает все параметры из текста запроса, а Запрос.Параметры — структура с тем, что реально установлено. Сравнение сразу показывает забытый параметр или опечатку в имени. Дата выводится со временем, так что конец периода в 0:00:00 тоже заметен.
Новый Запрос в процедуре с директивой &НаКлиенте в тонком клиенте даёт ошибку «Тип не определен (Запрос)». Проверку пустоты переносите в серверную функцию.Если Выборка.Следующий() Тогда для проверки, потом Пока Выборка.Следующий() Цикл — цикл начнётся со второй записи. Для проверки берите Пустой(), а если Следующий() уже вызван, обработайте текущую запись до цикла или вызовите Выборка.Сбросить().Пустой() вернул Ложь, а сложение с полем падает с ошибкой «Преобразование значения к типу Число не может быть выполнено». Решение — ЕСТЬNULL() в тексте запроса.Если Запрос.Выполнить().Пустой(), а затем снова Запрос.Выполнить().Выбрать() — два обращения к базе вместо одного. Результат сохраняйте в переменную.Ложь: там служебная строка с количеством. Проверяйте запрос пакета, который читает временную таблицу.МенеджерВременныхТаблиц второй запрос не видит таблицу первого. Это ошибка «Таблица не найдена», а не пустой результат.Ответ одинаковый, но Пустой() не создаёт выборку и сразу читается как намерение: данных нет — выходим. Количество() берите, когда само число нужно дальше.
В пакете — по полю Количество результата запроса с ПОМЕСТИТЬ или по результату запроса, который из неё выбирает. Если таблица хранится в менеджере временных таблиц между запросами, выполните отдельный ВЫБРАТЬ ПЕРВЫЕ 1 из неё с тем же менеджером и проверьте Пустой().
Агрегатная функция без СГРУППИРОВАТЬ ПО всегда возвращает одну строку, при отсутствии данных сумма в ней — NULL. Используйте ЕСТЬNULL(СУММА(...), 0) и проверяйте значение, а не пустоту результата.
В тонком и веб-клиенте — нет: объекты Запрос и РезультатЗапроса доступны только на сервере, в толстом клиенте и во внешнем соединении. Выполните запрос в серверной функции и верните на клиент Булево или нужные значения.
Ограничение доступа на уровне записей (запрос с РАЗРЕШЕННЫЕ отбрасывает недоступные строки без ошибки) и параметры, которые берутся из настроек пользователя. Проще всего повторить запрос в консоли под его учётной записью.
Если отчёт или обработка молча ничего не выводит, а разбирать чужой запрос на десяток соединений некогда, отдайте задачу программисту. Разместить задачу на площадке можно бесплатно, оценку пришлют в течение рабочего дня. Исправление и доработка запросов входят в услугу по отчётам и обработкам 1С.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.