Почему один и тот же запрос в коде 1С возвращает одну запись, а в консоли запросов — все?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик 1С рано или поздно сталкивается с мистической ситуацией: один и тот же запрос, запущенный в консоли запросов, выдает полный и корректный список документов, а при выполнении в коде обработки или модуля возвращает лишь одну строку или вовсе оказывается пустым.

Давайте подробно разберем эту проблему на примере реального кейса с документом ВводНачальныхОстатковОС, проанализируем внутренние механизмы работы платформы 1С с базой данных и выясним, как правильно писать подобные запросы, чтобы избежать расхождения результатов.

Исходный запрос с ошибкой

Рассмотрим код запроса, с которым возникла проблема у автора темы:


ОС = Справочники.ОсновныеСредства.ПустаяСсылка();
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ВводНачальныхОстатковОС.Ссылка КАК Ссылка
    |ИЗ
    |    Документ.ВводНачальныхОстатковОС КАК ВводНачальныхОстатковОС
    |ГДЕ
    |    ВводНачальныхОстатковОС.Организация = &Организация
    |    И ВводНачальныхОстатковОС.Проведен = &Проведен
    |    И ВводНачальныхОстатковОС.ИнвентарныеОбъекты.ИнвентарныйНомер = &ИнвентарныйНомер";

Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Проведен", Истина);
Запрос.УстановитьПараметр("ИнвентарныйНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());

РезультатЗапроса = Запрос.Выполнить();

На первый взгляд запрос выглядит просто. Мы пытаемся выбрать проведенные документы по конкретной организации, у которых в табличной части ИнвентарныеОбъекты инвентарный номер не заполнен (равен пустой ссылке справочника ИнвентарныеНомераОС).

Однако при выполнении этого кода в обработке результат содержит всего одну запись, в то время как консоль запросов по тем же параметрам находит десятки документов. Почему так происходит? Разберем две главные архитектурные причины.

---

Причина 1. Обращение к табличной части через точку в секции ГДЕ

Самая грубая и неочевидная ошибка в представленном запросе — это конструкция:

ВводНачальныхОстатковОС.ИнвентарныеОбъекты.ИнвентарныйНомер = &ИнвентарныйНомер

Поле ИнвентарныеОбъекты является табличной частью документа. Когда мы обращаемся к реквизитам табличной части «через точку» непосредственно в секции ГДЕ, платформа 1С выполняет трансляцию этого выражения в SQL-запрос с использованием **неявного внутреннего соединения** (INNER JOIN).

В итоге СУБД выполняет соединение таблицы шапки документа с таблицей его табличной части по условию равенства ссылок. Это приводит к следующим критическим последствиям:

  1. Исключение пустых табличных частей: Если в базе данных есть документы, у которых табличная часть ИнвентарныеОбъекты пуста (физически в ней нет строк), то из-за внутреннего соединения (INNER JOIN) такие документы гарантированно будут отброшены СУБД. Они никогда не попадут в результат выборки.
  2. Дублирование строк: Если в документе несколько строк в табличной части соответствуют условию, СУБД вернет ссылку на этот документ столько раз, сколько совпадений было найдено. Если в коде используется обход выборки без группировки или без метода Выгрузить().Свернуть(), логика обработки может нарушиться.

Правило разработки: Обращение к табличным частям через точку в условиях — это плохой тон и источник трудноуловимых ошибок. Для связи документа с его табличной частью необходимо всегда использовать явные соединения или вложенные запросы.

---

Причина 2. Особенности типизации параметров и составные типы

Вторая причина, почему поведение в коде отличается от консоли — жесткая типизация параметров в коде обработки:

Запрос.УстановитьПараметр("ИнвентарныйНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());

В современных конфигурациях 1С (особенно в ERP, Комплексной автоматизации или УХ) реквизиты для хранения инвентарных номеров могут иметь составной тип данных (например, СправочникСсылка.ИнвентарныеНомераОС и Строка) или вовсе являться строками.

Если реквизит имеет составной тип данных, то незаполненное значение в базе данных может быть записано как:

В программном коде разработчик жестко передал параметр конкретного типа — пустую ссылку справочника. Запрос вернул исключительно те документы, где в базе физически записана именно пустая ссылка. Строки со значениями Неопределено или пустыми строками СУБД проигнорировала.

Почему же тогда в консоли запросов выводились все записи?

В консоли запросов параметры часто заполняются интерактивно в таблице параметров. Если разработчик оставил поле ввода параметра «ИнвентарныйНомер» пустым, консоль могла передать его в запрос как значение типа Неопределено, либо сама платформа выполнила неявное приведение типов при сравнении. В результате СУБД сочла условия выполненными для гораздо большего числа документов.

---

Решение проблемы: правильное написание запроса

Для исправления обеих ошибок нам необходимо переписать запрос. Мы избавимся от обращения к табличной части через точку в секции ГДЕ и корректно обработаем возможные пустые значения (включая Неопределено и NULL).

Вариант 1. Использование конструкции В (ВЫБРАТЬ...)

Это самый чистый и производительный способ, если нам нужно просто получить список документов, у которых хотя бы в одной строке табличной части инвентарный номер пустой или не определен. При этом мы избегаем дублирования ссылок в результате.


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ВводНачальныхОстатковОС.Ссылка КАК Ссылка
    |ИЗ
    |    Документ.ВводНачальныхОстатковОС КАК ВводНачальныхОстатковОС
    |ГДЕ
    |    ВводНачальныхОстатковОС.Организация = &Организация
    |    И ВводНачальныхОстатковОС.Проведен = &Проведен
    |    И ВводНачальныхОстатковОС.Ссылка В
    |        (ВЫБРАТЬ
    |            ТЧ.Ссылка
    |        ИЗ
    |            Документ.ВводНачальныхОстатковОС.ИнвентарныеОбъекты КАК ТЧ
    |        ГДЕ
    |            (ТЧ.ИнвентарныйНомер = &ПустаяСсылкаИнвНомер
    |            ИЛИ ТЧ.ИнвентарныйНомер ПОДХОДИТ НЕОПРЕДЕЛЕНО
    |            ИЛИ ТЧ.ИнвентарныйНомер ЕСТЬ NULL))";

Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Проведен", Истина);
Запрос.УстановитьПараметр("ПустаяСсылкаИнвНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());

РезультатЗапроса = Запрос.Выполнить();

Вариант 2. Явное левое соединение с проверкой на пустые значения

Если нам необходимо также анализировать данные из табличной части, мы можем применить явное ЛЕВОЕ СОЕДИНЕНИЕ. Чтобы исключить дублирование документов в результате, мы используем ключевое слово РАЗЛИЧНЫЕ.


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |    ВводНачальныхОстатковОС.Ссылка КАК Ссылка
    |ИЗ
    |    Документ.ВводНачальныхОстатковОС КАК ВводНачальныхОстатковОС
    |    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ВводНачальныхОстатковОС.ИнвентарныеОбъекты КАК ИнвентарныеОбъектыТЧ
    |        ПО ИнвентарныеОбъектыТЧ.Ссылка = ВводНачальныхОстатковОС.Ссылка
    |ГДЕ
    |    ВводНачальныхОстатковОС.Организация = &Организация
    |    И ВводНачальныхОстатковОС.Проведен = &Проведен
    |    И (ИнвентарныеОбъектыТЧ.ИнвентарныйНомер = &ПустаяСсылкаИнвНомер
    |        ИЛИ ИнвентарныеОбъектыТЧ.ИнвентарныйНомер = НЕОПРЕДЕЛАНО
    |        ИЛИ ИнвентарныеОбъектыТЧ.ИнвентарныйНомер ЕСТЬ NULL)";

Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("Проведен", Истина);
Запрос.УстановитьПараметр("ПустаяСсылкаИнвНомер", Справочники.ИнвентарныеНомераОС.ПустаяСсылка());

РезультатЗапроса = Запрос.Выполнить();

Обратите внимание на конструкцию проверки пустых значений в условиях ГДЕ. Мы явно проверяем равенство пустой ссылке, типу НЕОПРЕДЕЛЕНО, а также используем оператор ЕСТЬ NULL на случай, если табличная часть документа вообще пуста и левое соединение вернуло пустую строку.

Резюме

Если ваш запрос работает в консоли, но "ломается" в программном коде 1С, проверьте два основных фактора:

  1. Связи с табличными частями: Никогда не обращайтесь к табличным частям через точку в секции ГДЕ. Всегда пишите явные соединения (LEFT JOIN / INNER JOIN) или используйте вложенные подзапросы.
  2. Точные типы параметров: Внимательно проверяйте тип данных реквизита. Если он составной — обязательно закладывайте в условия запроса проверку на Неопределено, пустую строку или пустые ссылки всех возможных типов этого реквизита.
← На главную