Каждый разработчик 1С рано или поздно сталкивается с мистической ситуацией: один и тот же запрос, запущенный в консоли запросов, выдает полный и корректный список документов, а при выполнении в коде обработки или модуля возвращает лишь одну строку или вовсе оказывается пустым.
Давайте подробно разберем эту проблему на примере реального кейса с документом ВводНачальныхОстатковОС, проанализируем внутренние механизмы работы платформы 1С с базой данных и выясним, как правильно писать подобные запросы, чтобы избежать расхождения результатов.
Рассмотрим код запроса, с которым возникла проблема у автора темы:
ОС = Справочники.ОсновныеСредства.ПустаяСсылка();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВводНачальныхОстатковОС.Ссылка КАК Ссылка
|ИЗ
| Документ.ВводНачальныхОстатковОС КАК ВводНачальныхОстатковОС
|ГДЕ
| ВводНачальныхОстатковОС.Организация = &Организация
| И ВводНачальныхОстатковОС.Проведен = &Проведен
| И ВводНачальныхОстатковОС.ИнвентарныеОбъекты.ИнвентарныйНомер = &ИнвентарныйНомер";
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Проведен", Истина);
Запрос.УстановитьПараметр("ИнвентарныйНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());
РезультатЗапроса = Запрос.Выполнить();
На первый взгляд запрос выглядит просто. Мы пытаемся выбрать проведенные документы по конкретной организации, у которых в табличной части ИнвентарныеОбъекты инвентарный номер не заполнен (равен пустой ссылке справочника ИнвентарныеНомераОС).
Однако при выполнении этого кода в обработке результат содержит всего одну запись, в то время как консоль запросов по тем же параметрам находит десятки документов. Почему так происходит? Разберем две главные архитектурные причины.
---
Самая грубая и неочевидная ошибка в представленном запросе — это конструкция:
ВводНачальныхОстатковОС.ИнвентарныеОбъекты.ИнвентарныйНомер = &ИнвентарныйНомер
Поле ИнвентарныеОбъекты является табличной частью документа. Когда мы обращаемся к реквизитам табличной части «через точку» непосредственно в секции ГДЕ, платформа 1С выполняет трансляцию этого выражения в SQL-запрос с использованием **неявного внутреннего соединения** (INNER JOIN).
В итоге СУБД выполняет соединение таблицы шапки документа с таблицей его табличной части по условию равенства ссылок. Это приводит к следующим критическим последствиям:
ИнвентарныеОбъекты пуста (физически в ней нет строк), то из-за внутреннего соединения (INNER JOIN) такие документы гарантированно будут отброшены СУБД. Они никогда не попадут в результат выборки.Выгрузить().Свернуть(), логика обработки может нарушиться.Правило разработки: Обращение к табличным частям через точку в условиях — это плохой тон и источник трудноуловимых ошибок. Для связи документа с его табличной частью необходимо всегда использовать явные соединения или вложенные запросы.
---
Вторая причина, почему поведение в коде отличается от консоли — жесткая типизация параметров в коде обработки:
Запрос.УстановитьПараметр("ИнвентарныйНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());
В современных конфигурациях 1С (особенно в ERP, Комплексной автоматизации или УХ) реквизиты для хранения инвентарных номеров могут иметь составной тип данных (например, СправочникСсылка.ИнвентарныеНомераОС и Строка) или вовсе являться строками.
Если реквизит имеет составной тип данных, то незаполненное значение в базе данных может быть записано как:
Неопределено;"";В программном коде разработчик жестко передал параметр конкретного типа — пустую ссылку справочника. Запрос вернул исключительно те документы, где в базе физически записана именно пустая ссылка. Строки со значениями Неопределено или пустыми строками СУБД проигнорировала.
Почему же тогда в консоли запросов выводились все записи?
В консоли запросов параметры часто заполняются интерактивно в таблице параметров. Если разработчик оставил поле ввода параметра «ИнвентарныйНомер» пустым, консоль могла передать его в запрос как значение типа Неопределено, либо сама платформа выполнила неявное приведение типов при сравнении. В результате СУБД сочла условия выполненными для гораздо большего числа документов.
---
Для исправления обеих ошибок нам необходимо переписать запрос. Мы избавимся от обращения к табличной части через точку в секции ГДЕ и корректно обработаем возможные пустые значения (включая Неопределено и NULL).
Это самый чистый и производительный способ, если нам нужно просто получить список документов, у которых хотя бы в одной строке табличной части инвентарный номер пустой или не определен. При этом мы избегаем дублирования ссылок в результате.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВводНачальныхОстатковОС.Ссылка КАК Ссылка
|ИЗ
| Документ.ВводНачальныхОстатковОС КАК ВводНачальныхОстатковОС
|ГДЕ
| ВводНачальныхОстатковОС.Организация = &Организация
| И ВводНачальныхОстатковОС.Проведен = &Проведен
| И ВводНачальныхОстатковОС.Ссылка В
| (ВЫБРАТЬ
| ТЧ.Ссылка
| ИЗ
| Документ.ВводНачальныхОстатковОС.ИнвентарныеОбъекты КАК ТЧ
| ГДЕ
| (ТЧ.ИнвентарныйНомер = &ПустаяСсылкаИнвНомер
| ИЛИ ТЧ.ИнвентарныйНомер ПОДХОДИТ НЕОПРЕДЕЛЕНО
| ИЛИ ТЧ.ИнвентарныйНомер ЕСТЬ NULL))";
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Проведен", Истина);
Запрос.УстановитьПараметр("ПустаяСсылкаИнвНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());
РезультатЗапроса = Запрос.Выполнить();
Если нам необходимо также анализировать данные из табличной части, мы можем применить явное ЛЕВОЕ СОЕДИНЕНИЕ. Чтобы исключить дублирование документов в результате, мы используем ключевое слово РАЗЛИЧНЫЕ.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВводНачальныхОстатковОС.Ссылка КАК Ссылка
|ИЗ
| Документ.ВводНачальныхОстатковОС КАК ВводНачальныхОстатковОС
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ВводНачальныхОстатковОС.ИнвентарныеОбъекты КАК ИнвентарныеОбъектыТЧ
| ПО ИнвентарныеОбъектыТЧ.Ссылка = ВводНачальныхОстатковОС.Ссылка
|ГДЕ
| ВводНачальныхОстатковОС.Организация = &Организация
| И ВводНачальныхОстатковОС.Проведен = &Проведен
| И (ИнвентарныеОбъектыТЧ.ИнвентарныйНомер = &ПустаяСсылкаИнвНомер
| ИЛИ ИнвентарныеОбъектыТЧ.ИнвентарныйНомер = НЕОПРЕДЕЛАНО
| ИЛИ ИнвентарныеОбъектыТЧ.ИнвентарныйНомер ЕСТЬ NULL)";
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Проведен", Истина);
Запрос.УстановитьПараметр("ПустаяСсылкаИнвНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());
РезультатЗапроса = Запрос.Выполнить();
Обратите внимание на конструкцию проверки пустых значений в условиях ГДЕ. Мы явно проверяем равенство пустой ссылке, типу НЕОПРЕДЕЛЕНО, а также используем оператор ЕСТЬ NULL на случай, если табличная часть документа вообще пуста и левое соединение вернуло пустую строку.
Если ваш запрос работает в консоли, но "ломается" в программном коде 1С, проверьте два основных фактора:
ГДЕ. Всегда пишите явные соединения (LEFT JOIN / INNER JOIN) или используйте вложенные подзапросы.Неопределено, пустую строку или пустые ссылки всех возможных типов этого реквизита.