Каждый разработчик на платформе 1С хотя бы раз сталкивался с ситуацией, которую в шутку называют «магией» или «мистикой». Вы пишете запрос, тестируете его в консоли запросов — всё работает идеально, данные выводятся в полном объеме. Но стоит перенести этот же самый текст запроса в программный модуль (в обработку, документ или фоновое задание), как он внезапно начинает возвращать пустой результат. При этом параметры передаются, даты совпадают, а структура базы данных не менялась.
В этой статье мы подробно разберем реальный практический кейс с форума 1С, выясним истинную причину такого поведения системы, проанализируем особенности работы метода НайтиПоКоду() и научимся избегать подобных ошибок при разработке конфигураций.
Рассмотрим ситуацию, с которой столкнулся автор темы. В своей обработке он выполняет запрос к оборотам регистра накопления РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты с использованием отбора по иерархии номенклатуры. Текст запроса выглядит следующим образом:
ВЫБРАТЬ
ВыручкаИСебестоимостьПродажОбороты.СуммаВыручкиОборот КАК СуммаВыручкиОборот,
ВыручкаИСебестоимостьПродажОбороты.СуммаВыручкиБезНДСОборот КАК СуммаВыручкиБезНДСОборот
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&ДатаНач, &ДатаКон, Авто, АналитикаУчетаНоменклатуры.Номенклатура В ИЕРАРХИИ (&ГруппаНоменклатуры)) КАК ВыручкаИСебестоимостьПродажОбороты
В коде обработки параметры устанавливаются динамически. Особое внимание уделим поиску группы номенклатуры и заполнению параметров запроса:
Запрос = Новый Запрос;
Запрос.Текст = "..."; // Текст запроса, указанный выше
ГруппаТовары = Справочники.Номенклатура.НайтиПоКоду("00000000001");
Запрос.УстановитьПараметр("ДатаКон", ПериодРасчета.ДатаОкончания);
Запрос.УстановитьПараметр("ДатаНач", ПериодРасчета.ДатаНачала);
Для каждого Стр из РасчетПремии Цикл
ГруппаНоменклатуры = ГруппаТовары;
Если ЗначениеЗаполнено(Стр.ГруппаНоменклатуры) тогда
ГруппаНоменклатуры = новый СписокЗначений;
Для каждого СтрНом из Стр.ГруппаНоменклатуры.Товары Цикл
Если ЗначениеЗаполнено(СтрNom.Номенклатура) тогда
ГруппаНоменклатуры.Добавить(СтрНом.Номенклатура);
Иначе
ГруппаНоменклатуры.Добавить(ГруппаТовары);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Запрос.УстановитьПараметр("ГруппаНоменклатуры", ГруппаНоменклатуры);
Результат = Запрос.Выполнить();
// Дальнейшая обработка выборки данных...
КонецЦикла;
При выполнении этого кода в обработке результат запроса всегда оказывался пустым. Однако, когда автор открывал консоль запросов, указывал те же самые даты начала и окончания периода, а в качестве параметра &ГруппаНоменклатуры выбирал ту же самую (как ему казалось) группу из справочника, консоль успешно возвращала заполненные строки с суммами оборотов.
Разгадка этой «магии» кроется в строке поиска группы по коду: Справочники.Номенклатура.НайтиПоКоду("00000000001").
При детальном анализе структуры справочника выяснилось следующее: в информационной базе существовали две разные группы номенклатуры с абсолютно одинаковым кодом "00000000001". Одна группа называлась «Товар», а вторая — «Товары».
Когда разработчик использовал консоль запросов, он выбирал параметр &ГруппаНоменклатуры интерактивно (глазами находил нужную папку «Товар», по которой действительно велись продажи и регистрировалась выручка). Запрос в консоли отрабатывал корректно.
В коде же метод НайтиПоКоду("00000000001") из-за наличия дубликатов кодов находил первую попавшуюся в базе данных группу, которой оказалась папка «Товары» (пустая группа без оборотов). Соответственно, запрос выполнялся по пустой группе номенклатуры и закономерно возвращал пустой результат.
Давайте подробно разберем, почему в системе 1С вообще могут появиться элементы с одинаковыми кодами и как работает метод НайтиПоКоду().
В платформе 1С уникальность кодов справочников контролируется на уровне конфигурации с помощью свойства "Контроль уникальности". Однако даже при включенном контроле уникальности дубликаты кодов могут возникнуть в следующих случаях:
Метод НайтиПоКоду(Код, НаправлениеПоиска, Родитель, Владелец) при наличии дублирующихся записей возвращает ссылку на первый найденный объект. Порядок, в котором СУБД возвращает записи при поиске по неуникальному коду, зависит от внутренних индексов базы данных, СУБД (MS SQL Server, PostgreSQL) и даже версии платформы. Полагаться на стабильность такого поиска категорически нельзя.
Использование жестко зашитых («захардкоженных») строковых кодов в программном коде — это критическая ошибка проектирования. Проанализируем правильные альтернативные подходы, которые гарантируют стабильную работу системы.
Если конкретная группа номенклатуры (например, основная папка товаров) важна для бизнес-логики алгоритма и должна быть жестко зафиксирована, сделайте эту группу предопределенным элементом в конфигураторе. В коде к ней можно будет обратиться напрямую и абсолютно безопасно:
ГруппаТовары = Справочники.Номенклатура.Товары; // Где "Товары" - имя предопределенного элемента
Этот способ полностью исключает зависимость от кодов, наименований и человеческого фактора в режиме «1С:Предприятие».
Если элемент создается в режиме предприятия и не может быть предопределенным, надежнее искать его по Уникальному Идентификатору (GUID/UUID), а не по коду или наименованию. UUID гарантированно уникален для всей информационной базы.
ИдентификаторГруппы = Новый УникальныйИдентификатор("d3b07384-d113-11e4-80c1-00155d013e01");
ГруппаТовары = Справочники.Номенклатура.ПолучитьСсылку(ИдентификаторГруппы);
// Обязательно проверяем, существует ли физически объект в базе данных
Если Не ОбщегоНазначения.СсылкаСуществует(ГруппаТовары) Тогда
ВызватьИсключение "Не найдена базовая группа номенклатуры в базе данных!";
КонецЕсли;
Если уникальность кодов контролируется в пределах подчинения (внутри папок), при использовании метода НайтиПоКоду() необходимо в обязательном порядке передавать третий параметр — родительскую группу:
РодительскаяПапка = Справочники.Номенклатура.НайтиПоНаименованию("Каталог продукции");
ГруппаТовары = Справочники.Номенклатура.НайтиПоКоду("00000000001", Ложь, РодительскаяПапка);
В коде автора темы переменная ГруппаНоменклатуры динамически меняет свой тип данных:
СправочникСсылка.Номенклатура.СписокЗначений.Такое динамическое переопределение типов в рамках одной переменной существенно усложняет чтение кода и отладку. Конструкция запроса В ИЕРАРХИИ (&ГруппаНоменклатуры) может принимать как одиночную ссылку, так и массив или список значений. Однако при передаче пустого списка значений поведение запроса в СУБД может приводить к ошибкам или возвращать пустой результат без предупреждений (особенно на СУБД PostgreSQL).
Рекомендуется придерживаться типизации и всегда передавать в параметры подобных запросов строго типизированный массив ссылок, предварительно очищенный от пустых значений:
МассивГрупп = Новый Массив;
// Заполнение массива группами...
Если МассивГрупп.Количество() = 0 Тогда
// Если отбирать нечего, можно прервать выполнение алгоритма или не накладывать фильтр
Продолжить;
КонецЕсли;
Запрос.УстановитьПараметр("ГруппаНоменклатуры", МассивГрупп);
Если вы столкнулись с ситуацией, когда один и тот же запрос выдает разные результаты в коде и в консоли, выполните следующие шаги для локализации проблемы:
Не верьте локальным переменным вашего кода. Установите точку останова непосредственно на строке Результат = Запрос.Выполнить(). Откройте табло отладчика и проанализируйте коллекцию Запрос.Параметры. Убедитесь, что типы переданных параметров (например, даты, ссылки) в точности совпадают с типами, которые вы указываете в консоли запросов.
Обратите внимание на границы периодов. Если в консоли вы указываете дату окончания вручную (например, 31.12.2025 00:00:00), а в коде рассчитываете ее функцией КонецДня(), то в СУБД передается время 23:59:59.999. Разница в несколько миллисекунд или секунд может приводить к тому, что документы, записанные на границе периода, выпадают из выборки виртуальной таблицы.
Консоль запросов выполняется под учетной записью текущего пользователя и учитывает ограничения прав на уровне записей (RLS). В то же время программный код (особенно если он выполняется в привилегированном общем модуле на сервере или в фоновом задании) может игнорировать RLS и считывать полные данные базы данных.
Если ваш программный код выполняется внутри открытой транзакции записи, запросы могут видеть промежуточные измененные данные, которые еще не зафиксированы в базе данных. Внешняя консоль запросов, запущенная параллельно, эти данные не увидит до тех пор, пока транзакция в коде не будет успешно завершена.
И помните главное правило разработки в 1С: чудес не бывает. Если один и тот же запрос возвращает разные данные — значит, на вход СУБД подаются разные параметры или условия выполнения. Тщательный анализ типов данных и структуры метаданных всегда позволяет найти и устранить ошибку.