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