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