В практике разработчика 1С часто встречается загадочная ситуация: один и тот же текст запроса, запущенный в Консоли запросов, выдает ожидаемые строки, а при выполнении внутри модуля формы — возвращает пустой результат. Особенно это сбивает с толку, когда конфигурация на тестовой и основной базе идентична, и на тесте всё работает корректно. Разберем эту ситуацию на практическом примере из конфигурации «Зарплата и Управление Персоналом».
Рассмотрим сценарий, в котором по нажатию кнопки на форме выполняется сложный расчет премии. Код выполняет запрос к нескольким регистрам (сведений и накопления), используя левые соединения и вложенные подзапросы. Основная проблема заключается в том, что Запрос.Выполнить().Выгрузить() возвращает пустую таблицу значений в основной базе, хотя визуально данные в регистрах присутствуют.
Посмотрим на фрагмент кода, который устанавливает параметры запроса:
Запрос.УстановитьПараметр("тДатаНач", НачалоМесяца(ДатаНачалаБП));
Запрос.УстановитьПараметр("тДатаКон", КонецМесяца(ДатаОкончанияБП));
Запрос.УстановитьПараметр("ГоловнаяОрганизация", Справочники.Организации.НайтиПоКоду("000000001"));
Запрос.УстановитьПараметр("тГрафик", Справочники.ГрафикиРаботы.НайтиПоНаименованию("Пятидневка 40"));
Запрос.УстановитьПараметр("парамПодразделение", УдалитьПодразделениеОрганизации);
Проанализируем используемые методы НайтиПоКоду и НайтиПоНаименованию. Это «слабое звено» в коде. В рассматриваемом нами случае выяснилось, что в справочнике ГрафикиРаботы существовало два элемента с одинаковым наименованием «Пятидневка 40». Один из них был корректно заполнен, а второй — пустой и помеченный на удаление.
Метод НайтиПоНаименованию возвращает первую попавшуюся ссылку, соответствующую строке. В основной базе система нашла именно помеченный на удаление «пустой» график. В результате параметр &тГрафик получил ссылку на объект, к которому в регистре ГрафикиРаботыПоВидамВремени не было привязано ни одной записи. Естественно, условие связи в запросе не сработало, и вся выборка «схлопнулась» до нуля.
Чтобы избежать подобных ошибок, мы рекомендуем отказаться от жесткого кодирования строковых наименований. Рассмотрим более надежные способы:
Справочники.Организации.ГлавнаяОрганизация.
ГрафикСсылка = Справочники.ГрафикиРаботы.НайтиПоНаименованию("Пятидневка 40");
Если ГрафикСсылка.Пустая() Тогда
Сообщить("График работы не найден!");
Возврат;
КонецЕсли;
Посмотрим на структуру самого запроса. Он содержит множество соединений. В таких случаях очень сложно понять, в каком именно месте «теряются» данные. Проанализируем типичный фрагмент из примера:
ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
РабочееВремяРаботниковОрганизацийОбороты.Сотрудник КАК Сотрудник,
СУММА(...) КАК ДнейОборот
ИЗ РегистрНакопления.РабочееВремяРаботниковОрганизаций.Обороты(&тДатаНач, &тДатаКон, Месяц, ) КАК РабочееВремяРаботниковОрганизацийОбороты
СГРУППИРОВАТЬ ПО РабочееВремяРаботниковОрганизацийОбороты.Сотрудник) КАК ОтработанныеДни
ПО ПроцентыКомпенсацииНормаДней.СотрудникОрганизации = ОтработанныеДни.Сотрудник
Важный момент: Если в первом подзапросе (источнике связи) нет данных, то ЛЕВОЕ СОЕДИНЕНИЕ не поможет — общий результат все равно будет пустым. Мы рекомендуем использовать Временные таблицы вместо вложенных подзапросов.
Разобьем сложную задачу на этапы. Это не только ускорит работу СУБД, но и значительно упростит отладку. Мы можем пошагово проверить содержимое каждой временной таблицы.
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Шаг 1: Получаем параметры компенсации во временную таблицу
Запрос.Текст =
"ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ_Параметры ИЗ РегистрСведений.ПараметрыКомпенсацииЗаИнтенсивность.СрезПоследних(&тДатаКон, ...)";
Запрос.Выполнить();
// Шаг 2: Теперь мы можем проверить, есть ли данные в ВТ_Параметры
Запрос.Текст = "ВЫБРАТЬ КОЛИЧЕСТВО(*) ИЗ ВТ_Параметры";
РезультатПроверки = Запрос.Выполнить().Выгрузить()[0][0];
// Если РезультатПроверки = 0, значит проблема в срезе последних или параметрах связи.
Если параметры установлены верно, но данных все равно нет, проанализируем следующие системные механизмы:
Выясним, под каким пользователем запускается код. Консоль запросов часто запускается под «Полными правами», которые игнорируют ограничения доступа. В то же время модуль формы подчиняется правилам RLS. Если у пользователя нет прав на чтение конкретного подразделения или организации, запрос в коде вернет пустую таблицу. Для проверки можно временно использовать привилегированный режим:
УстановитьПривилегированныйРежим(Истина);
РезЗапроса = Запрос.Выполнить().Выгрузить();
УстановитьПривилегированныйРежим(Ложь);
Обратим внимание на работу со СрезПоследних. В исходном коде автора условие по организации стоит внутри подзапроса в секции ГДЕ. Это классическая ошибка. Для виртуальных таблиц условия нужно передавать обязательно в параметры самой таблицы:
Плохо: ...СрезПоследних(&тДатаКон) ГДЕ Организация = &Орг
Хорошо: ...СрезПоследних(&тДатаКон, Организация = &Орг)
Почему это важно? В первом случае система сначала получает срез последних по всем организациям в базе, а потом фильтрует их. Если последний срез по сотруднику был в другой организации, в выборку он не попадет. Во втором случае система найдет срез именно по нужной организации.
В клиент-серверном варианте работы (SQL) данные могут «застревать» в кэше. Если вы только что изменили данные, а запрос их не видит — попробуйте очистить кэш или перезапустить сеанс. Также для баз ЗУП критична актуальность статистики SQL-сервера, так как сложные соединения при неверном плане выполнения могут просто не завершаться вовремя.
Подведем итог нашему исследованию ситуации. Для того чтобы ваши запросы работали стабильно во всех базах, придерживайтесь следующих правил:
&тДатаКон передается именно дата с временем 23:59:59, а не 00:00:00, иначе данные за последний день месяца не попадут в отчет.