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

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Каждый разработчик на платформе 1С хотя бы раз сталкивался с ситуацией, которую в шутку называют «магией» или «мистикой». Вы пишете запрос, тестируете его в консоли запросов — всё работает идеально, данные выводятся в полном объеме. Но стоит перенести этот же самый текст запроса в программный модуль (в обработку, документ или фоновое задание), как он внезапно начинает возвращать пустой результат. При этом параметры передаются, даты совпадают, а структура базы данных не менялась.

В этой статье мы подробно разберем реальный практический кейс с форума 1С, выясним истинную причину такого поведения системы, проанализируем особенности работы метода НайтиПоКоду() и научимся избегать подобных ошибок при разработке конфигураций.

Анализ исходной проблемы и программного кода

Рассмотрим ситуацию, с которой столкнулся автор темы. В своей обработке он выполняет запрос к оборотам регистра накопления РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты с использованием отбора по иерархии номенклатуры. Текст запроса выглядит следующим образом:


ВЫБРАТЬ
    ВыручкаИСебестоимостьПродажОбороты.СуммаВыручкиОборот КАК СуммаВыручкиОборот,
    ВыручкаИСебестоимостьПродажОбороты.СуммаВыручкиБезНДСОборот КАК СуммаВыручкиБезНДСОборот
ИЗ
    РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&ДатаНач, &ДатаКон, Авто, АналитикаУчетаНоменклатуры.Номенклатура В ИЕРАРХИИ (&ГруппаНоменклатуры)) КАК ВыручкаИСебестоимостьПродажОбороты

В коде обработки параметры устанавливаются динамически. Особое внимание уделим поиску группы номенклатуры и заполнению параметров запроса:


Запрос = Новый Запрос;
Запрос.Текст = "..."; // Текст запроса, указанный выше

ГруппаТовары = Справочники.Номенклатура.НайтиПоКоду("00000000001");

Запрос.УстановитьПараметр("ДатаКон", ПериодРасчета.ДатаОкончания);
Запрос.УстановитьПараметр("ДатаНач", ПериодРасчета.ДатаНачала);

Для каждого Стр из РасчетПремии Цикл
    ГруппаНоменклатуры = ГруппаТовары;
    Если ЗначениеЗаполнено(Стр.ГруппаНоменклатуры) тогда
        ГруппаНоменклатуры = новый СписокЗначений;
        Для каждого СтрНом из Стр.ГруппаНоменклатуры.Товары Цикл
            Если ЗначениеЗаполнено(СтрNom.Номенклатура) тогда
                ГруппаНоменклатуры.Добавить(СтрНом.Номенклатура);
            Иначе
                ГруппаНоменклатуры.Добавить(ГруппаТовары);
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    
    Запрос.УстановитьПараметр("ГруппаНоменклатуры", ГруппаНоменклатуры);
    Результат = Запрос.Выполнить();
    // Дальнейшая обработка выборки данных...
КонецЦикла;

При выполнении этого кода в обработке результат запроса всегда оказывался пустым. Однако, когда автор открывал консоль запросов, указывал те же самые даты начала и окончания периода, а в качестве параметра &ГруппаНоменклатуры выбирал ту же самую (как ему казалось) группу из справочника, консоль успешно возвращала заполненные строки с суммами оборотов.

В чем оказалась реальная причина ошибки?

Разгадка этой «магии» кроется в строке поиска группы по коду: Справочники.Номенклатура.НайтиПоКоду("00000000001").

При детальном анализе структуры справочника выяснилось следующее: в информационной базе существовали две разные группы номенклатуры с абсолютно одинаковым кодом "00000000001". Одна группа называлась «Товар», а вторая — «Товары».

Когда разработчик использовал консоль запросов, он выбирал параметр &ГруппаНоменклатуры интерактивно (глазами находил нужную папку «Товар», по которой действительно велись продажи и регистрировалась выручка). Запрос в консоли отрабатывал корректно.

В коде же метод НайтиПоКоду("00000000001") из-за наличия дубликатов кодов находил первую попавшуюся в базе данных группу, которой оказалась папка «Товары» (пустая группа без оборотов). Соответственно, запрос выполнялся по пустой группе номенклатуры и закономерно возвращал пустой результат.

Подводные камни метода НайтиПоКоду() и дублирования кодов в 1С

Давайте подробно разберем, почему в системе 1С вообще могут появиться элементы с одинаковыми кодами и как работает метод НайтиПоКоду().

В платформе 1С уникальность кодов справочников контролируется на уровне конфигурации с помощью свойства "Контроль уникальности". Однако даже при включенном контроле уникальности дубликаты кодов могут возникнуть в следующих случаях:

  1. Серии кодов в пределах подчинения: Если справочник является иерархическим и для него настроена уникальность кодов «В пределах подчинения», система гарантирует уникальность кодов только внутри одной родительской папки. В разных папках могут существовать абсолютно разные элементы с идентичными кодами.
  2. Отключение контроля уникальности: Разработчики часто отключают контроль уникальности для справочников, где коды загружаются из внешних систем или генерируются по сложным правилам.
  3. Ошибки при обмене данными (синхронизации): При переносе данных через планы обмена, правила конвертации данных (КД) или XML-выгрузки контроль уникальности на уровне платформы может не срабатывать, что приводит к появлению «коллизий» и дублей.

Метод НайтиПоКоду(Код, НаправлениеПоиска, Родитель, Владелец) при наличии дублирующихся записей возвращает ссылку на первый найденный объект. Порядок, в котором СУБД возвращает записи при поиске по неуникальному коду, зависит от внутренних индексов базы данных, СУБД (MS SQL Server, PostgreSQL) и даже версии платформы. Полагаться на стабильность такого поиска категорически нельзя.

Как правильно искать элементы справочников в коде: лучшие практики

Использование жестко зашитых («захардкоженных») строковых кодов в программном коде — это критическая ошибка проектирования. Проанализируем правильные альтернативные подходы, которые гарантируют стабильную работу системы.

Способ 1. Использование предопределенных элементов

Если конкретная группа номенклатуры (например, основная папка товаров) важна для бизнес-логики алгоритма и должна быть жестко зафиксирована, сделайте эту группу предопределенным элементом в конфигураторе. В коде к ней можно будет обратиться напрямую и абсолютно безопасно:


ГруппаТовары = Справочники.Номенклатура.Товары; // Где "Товары" - имя предопределенного элемента

Этот способ полностью исключает зависимость от кодов, наименований и человеческого фактора в режиме «1С:Предприятие».

Способ 2. Поиск по Уникальному Идентификатору (UUID)

Если элемент создается в режиме предприятия и не может быть предопределенным, надежнее искать его по Уникальному Идентификатору (GUID/UUID), а не по коду или наименованию. UUID гарантированно уникален для всей информационной базы.


ИдентификаторГруппы = Новый УникальныйИдентификатор("d3b07384-d113-11e4-80c1-00155d013e01");
ГруппаТовары = Справочники.Номенклатура.ПолучитьСсылку(ИдентификаторГруппы);

// Обязательно проверяем, существует ли физически объект в базе данных
Если Не ОбщегоНазначения.СсылкаСуществует(ГруппаТовары) Тогда
    ВызватьИсключение "Не найдена базовая группа номенклатуры в базе данных!";
КонецЕсли;

Способ 3. Передача родительской группы в параметры поиска

Если уникальность кодов контролируется в пределах подчинения (внутри папок), при использовании метода НайтиПоКоду() необходимо в обязательном порядке передавать третий параметр — родительскую группу:


РодительскаяПапка = Справочники.Номенклатура.НайтиПоНаименованию("Каталог продукции");
ГруппаТовары = Справочники.Номенклатура.НайтиПоКоду("00000000001", Ложь, РодительскаяПапка);

Особенности работы с конструкцией В ИЕРАРХИИ

В коде автора темы переменная ГруппаНоменклатуры динамически меняет свой тип данных:

Такое динамическое переопределение типов в рамках одной переменной существенно усложняет чтение кода и отладку. Конструкция запроса В ИЕРАРХИИ (&ГруппаНоменклатуры) может принимать как одиночную ссылку, так и массив или список значений. Однако при передаче пустого списка значений поведение запроса в СУБД может приводить к ошибкам или возвращать пустой результат без предупреждений (особенно на СУБД PostgreSQL).

Рекомендуется придерживаться типизации и всегда передавать в параметры подобных запросов строго типизированный массив ссылок, предварительно очищенный от пустых значений:


МассивГрупп = Новый Массив;
// Заполнение массива группами...
Если МассивГрупп.Количество() = 0 Тогда
    // Если отбирать нечего, можно прервать выполнение алгоритма или не накладывать фильтр
    Продолжить;
КонецЕсли;
Запрос.УстановитьПараметр("ГруппаНоменклатуры", МассивГрупп);

Чек-лист для диагностики расхождений «Запрос в Консоли vs Запрос в Коде»

Если вы столкнулись с ситуацией, когда один и тот же запрос выдает разные результаты в коде и в консоли, выполните следующие шаги для локализации проблемы:

  1. Проверка реального наполнения коллекции параметров в отладчике.

    Не верьте локальным переменным вашего кода. Установите точку останова непосредственно на строке Результат = Запрос.Выполнить(). Откройте табло отладчика и проанализируйте коллекцию Запрос.Параметры. Убедитесь, что типы переданных параметров (например, даты, ссылки) в точности совпадают с типами, которые вы указываете в консоли запросов.

  2. Сравнение значений дат и времени.

    Обратите внимание на границы периодов. Если в консоли вы указываете дату окончания вручную (например, 31.12.2025 00:00:00), а в коде рассчитываете ее функцией КонецДня(), то в СУБД передается время 23:59:59.999. Разница в несколько миллисекунд или секунд может приводить к тому, что документы, записанные на границе периода, выпадают из выборки виртуальной таблицы.

  3. Учет прав доступа и RLS (Row-Level Security).

    Консоль запросов выполняется под учетной записью текущего пользователя и учитывает ограничения прав на уровне записей (RLS). В то же время программный код (особенно если он выполняется в привилегированном общем модуле на сервере или в фоновом задании) может игнорировать RLS и считывать полные данные базы данных.

  4. Проверка кэширования транзакций.

    Если ваш программный код выполняется внутри открытой транзакции записи, запросы могут видеть промежуточные измененные данные, которые еще не зафиксированы в базе данных. Внешняя консоль запросов, запущенная параллельно, эти данные не увидит до тех пор, пока транзакция в коде не будет успешно завершена.

И помните главное правило разработки в 1С: чудес не бывает. Если один и тот же запрос возвращает разные данные — значит, на вход СУБД подаются разные параметры или условия выполнения. Тщательный анализ типов данных и структуры метаданных всегда позволяет найти и устранить ошибку.

← На главную