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