Часто начинающие разработчики сталкиваются с ситуацией, когда один и тот же запрос выдает разные результаты в консоли запросов и в программном коде. Рассмотрим подробно, почему это происходит, проанализируем типичные ошибки при работе с виртуальными таблицами и выясним, как правильно передавать параметры даты для получения верных остатков.
Проанализируем фрагмент кода, представленный автором. В запросе используется виртуальная таблица РегистрНакопления.НачисленнаяЗП.ОстаткиИОбороты. Параметры в эту таблицу передаются следующим образом:
Запрос.Текст = "ВЫБРАТЬ
| СУММА(ЕСТЬNULL(НачисленнаяЗПОстаткиИОбороты.СуммаЗПНачальныйОстаток, 0)) КАК СуммаЗПОстаток
|ИЗ
| РегистрНакопления.НачисленнаяЗП.ОстаткиИОбороты(&Дата1, , , , ...) КАК НачисленнаяЗПОстаткиИОбороты";
Запрос.УстановитьПараметр("Дата1", Отчет.Период.ДатаОкончания);
В чем здесь заключается фундаментальная ошибка? Посмотрим на структуру параметров виртуальной таблицы ОстаткиИОбороты. Первый параметр — это НачалоПериода, а второй — КонецПериода. Автор передает ДатаОкончания в первый параметр. В этом случае система начинает расчет итогов именно с этой даты. Следовательно, поле СуммаЗПНачальныйОстаток будет содержать остаток на начало секунды указанной даты окончания, а не на начало месяца или иного отчетного периода.
Если в консоли запросов вы указывали другие даты (например, начало месяца в первый параметр), то и результат будет закономерно отличаться. Выясним причину: автор пытался получить остаток на конец периода, используя поле начального остатка от даты конца. Это избыточная и запутанная логика.
Разберем ситуацию, когда даже при правильных датах цифры могут «плыть». В 1С тип Дата включает в себя время с точностью до секунды. Если мы устанавливаем параметр КонецДня(ДатаОкончания), это будет 23:59:59. Однако в эту же секунду могут существовать движения по регистру. Если мы хотим получить остаток, включая движения этой последней секунды, обычного типа Дата недостаточно.
Для решения этой проблемы рассмотрим использование объекта Граница. Он позволяет явно указать системе, нужно ли включать документы, записанные в ту же секунду, в расчет или нет. Проанализируем пример кода:
КонецПериодаГраница = Новый Граница(КонецДня(Отчет.Период.ДатаОкончания), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Дата2", КонецПериодаГраница);
В консоли запросов часто реализованы механизмы автоматического приведения даты к концу дня или использования границ, что скрывает от разработчика реальную картину. В программном коде же необходимо управлять этим процессом вручную.
Посмотрим на задачу автора: ему нужно получить просто итоговую сумму остатка. Для этого использование таблицы ОстаткиИОбороты нецелесообразно. Эта таблица является наиболее «тяжелой» для сервера базы данных, так как она рассчитывает и начальные остатки, и все обороты за период, и конечные остатки.
Если нам нужен только остаток на конкретный момент, правильнее и быстрее использовать виртуальную таблицу Остатки. Рассмотрим, как изменится запрос:
Запрос.Текст = "ВЫБРАТЬ
| ЕСТЬNULL(СУММА(НачисленнаяЗПОстатки.СуммаЗПОстаток), 0) КАК СуммаЗПОстаток
|ИЗ
| РегистрНакопления.НачисленнаяЗП.Остатки(&Период,
| Организация = &Организация
| И Поликлиника = &Поликлиника
| И Сотрудник = &Сотрудник) КАК НачисленнаяЗПОстатки";
Запрос.УстановитьПараметр("Период", Новый Граница(КонецДня(Отчет.Период.ДатаОкончания), ВидГраницы.Включая));
Обратите внимание на использование функции ЕСТЬNULL. В исходном коде автора было СУММА(ЕСТЬNULL(Поле, 0)). Разберем по шагам: такой подход заставляет СУБД проверять на NULL каждую строку регистра перед суммированием. Правильнее использовать ЕСТЬNULL(СУММА(Поле), 0). В этом случае мы сначала суммируем данные (что быстрее), и только если результат всей суммы равен NULL (данных не найдено), заменяем его на ноль.
Проанализируем еще одну проблему в коде автора — вывод шапки таблицы внутри цикла. Это распространенная ошибка, приводящая к дублированию заголовков в отчете. Рассмотрим правильную структуру формирования документа:
Пока Выборка.Следующий(), в котором выводим только строки (СтрокаТаблицы).Пример исправленного фрагмента вывода:
// Выводим шапку таблицы один раз
ТабДок.Вывести(ОбластьШапкаТаблицыНач);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
// Если нужно заполнить параметр в шапке на основании запроса:
Если Выборка.Следующий() Тогда
ОбластьШапкаТаблицыНач.Параметры.СальдоНачало = Выборка.СуммаЗПОстаток;
// Перевыводим или заполняем параметр, если область еще не выведена
КонецЕсли;
В сообщениях форума автору справедливо советовали воспользоваться отладчиком. Разберем, как именно это помогает в данной ситуации:
1. Ставим точку останова на строке РезультатЗапроса = Запрос.Выполнить();.
2. В табло (или через Shift+F9) проверяем значение переменных, которые передаются в УстановитьПараметр. Важно убедиться, что ДатаОкончания содержит именно ту дату, которую вы ожидаете (включая время).
3. Самое важное: в современных версиях платформы можно скопировать текст запроса прямо из отладчика вместе со значениями параметров и вставить его в консоль запросов. Так вы увидите ровно ту картину, которую видит программа в момент выполнения.
Подведем итог: расхождения в цифрах между консолью и кодом почти всегда связаны либо с разными значениями параметров (особенно дат и пустых ссылок), либо с разным контекстом выполнения (права доступа, разделители данных), либо с неучетом момента времени (игнорирование объекта Граница).