В практике разработки на платформе 1С нередко возникает нестандартная задача: требуется размножить строки выборки данных в зависимости от числового поля. Например, если в регистре или табличной части документа числится позиция с количеством 3 штуки, в результирующей таблице или отчете системы компоновки данных (СКД) необходимо получить ровно три отдельные одинаковые строки.
Такая потребность часто возникает в следующих типовых сценариях:
Разберем основные подходы к решению этой задачи на уровне запроса 1С и средствами СКД, а также рассмотрим важные ограничения и подводные камни.
Рассмотрим классический прием: создание временной таблицы с последовательностью натуральных чисел и последующее неэквивалентное соединение с исходными данными по условию ВТЧисла.Число <= ИсходныеДанные.Количество. Если заранее известно, что количество объектов не превышает небольшого значения (например, от 1 до 10), можно собрать вспомогательную таблицу через конструкцию ОБЪЕДИНИТЬ ВСЕ.
Посмотрим на пример текста запроса:
ВЫБРАТЬ 1 КАК Число ПОМЕСТИТЬ ВТНомера
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 10
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ИсходныеДанные.Номенклатура КАК Номенклатура,
1 КАК Количество,
ИсходныеДанные.Количество КАК ИсходноеКоличество,
ВТНомера.Число КАК НомерПоПорядку
ИЗ
ВТ_Данные КАК ИсходныеДанные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТНомера КАК ВТНомера
ПО ВТНомера.Число <= ИсходныеДанные.Количество
В результате такого соединения строка, где количество равно 1, соединится только с числом 1 (даст 1 строку). Строка с количеством 3 соединится с числами 1, 2 и 3, породив ровно 3 строки с сохранением порядкового номера экземпляра.
Если максимальное количество заранее точно не ограничено десятью единицами, но может достигать сотен, вручную писать сотни строк с ОБЪЕДИНИТЬ ВСЕ нерационально. Проанализируем, как сгенерировать последовательность чисел от 1 до 1000 с помощью декартова произведения цифр разрядов (единицы, десятки, сотни):
ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ ВТЦифры
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Единицы.Цифра + Десятки.Цифра * 10 + Сотни.Цифра * 100 + 1 КАК Номер
ПОМЕСТИТЬ ВТЧисла
ИЗ
ВТЦифры КАК Единицы
СОЕДИНЕНИЕ ВТЦифры КАК Десятки ПО (ИСТИНА)
СОЕДИНЕНИЕ ВТЦифры КАК Сотни ПО (ИСТИНА)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
Товары.Количество КАК ВсегоВДокументе,
1 КАК КоличествоВСтроке,
ВТЧисла.Номер КАК НомерЭкземпляра
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТЧисла КАК ВТЧисла
ПО ВТЧисла.Номер <= Товары.Количество
ГДЕ
Товары.Ссылка = &Ссылка
Чтобы не перегружать текст запроса вспомогательными выборками цифр, сформируем вспомогательную таблицу чисел программно на встроенном языке 1С и передадим ее в параметр запроса:
// Подготовим таблицу чисел нужного размера
ТаблицаЧисел = Новый ТаблицаЗначений;
ТаблицаЧисел.Колонки.Добавить("Число", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));
МаксимальноеКоличество = 500; // или вычисляем максимум из выборки
Для Счетчик = 1 По МаксимальноеКоличество Цикл
НоваяСтрока = ТаблицаЧисел.Добавить();
НоваяСтрока.Число = Счетчик;
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Число КАК Число
|ПОМЕСТИТЬ ВТЧисла
|ИЗ
| &ТаблицаЧисел КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| 1 КАК Количество,
| ВТЧисла.Число КАК НомерКопии
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТЧисла КАК ВТЧисла
| ПО ВТЧисла.Число <= Остатки.ВНаличииОстаток";
Запрос.УстановитьПараметр("ТаблицаЧисел", ТаблицаЧисел);
Результат = Запрос.Выполнить();
Если отчет строится с использованием СКД, альтернативным вариантом является использование набора данных «Объект». Мы можем получить агрегированные данные обычным быстрым запросом, размножить строки циклом в коде модуля отчета или формы и передать готовую плоскую таблицу в СКД через ВнешниеНаборыДанных.
Разберем пример размножения строк в коде 1С:
ТаблицаИсходная = Запрос.Выполнить().Выгрузить();
ТаблицаРезультат = ТаблицаИсходная.СкопироватьКолонки();
ТаблицаРезультат.Колонки.Добавить("НомерЭкземпляра", Новый ОписаниеТипов("Число"));
Для Каждого СтрДанных Из ТаблицаИсходная Цикл
КоличествоИтераций = Цел(СтрДанных.Количество);
Для Счетчик = 1 По КоличествоИтераций Цикл
СтрНовая = ТаблицаРезультат.Добавить();
ЗаполнитьЗначенияСвойств(СтрНовая, СтрДанных);
СтрНовая.Количество = 1;
СтрНовая.НомерЭкземпляра = Счетчик;
КонецЦикла;
КонецЦикла;
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаДанных", ТаблицаРезультат);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.Настройки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);
При использовании описанных способов необходимо учитывать несколько критически важных нюансов платформы:
1000 рублей при количестве 5 шт., то при обычном размножении строк мы получим 5 строк по 1000 рублей. Если поле назначено ресурсом со стандартным выражением Сумма(Сумма), общий итог составит 5000 рублей вместо 1000. В запросе или выражении ресурса СКД обязательно пересчитывайте цену за единицу: Сумма / Количество.<= некорректно обрабатывает дробные остатки (например, 2.5). Дробная часть отсечется, либо возникнет логическая ошибка. При наличии дробных величин необходимо округлять или приводить поле к целому: ВЫРАЗИТЬ(Количество КАК ЧИСЛО(15, 0)).ВНУТРЕННЕМ СОЕДИНЕНИИ строки с нулевым или отрицательным количеством полностью исключаются из результирующей выборки. Если такие позиции должны отображаться в отчете, применяйте ЛЕВОЕ СОЕДИНЕНИЕ и обрабатывайте пустые значения через ЕСТЬNULL.<= транслируются СУБД во вложенные циклы (Nested Loops). На десятках тысяч строк такой запрос может выполняться заметно медленнее, поэтому для огромных массивов данных предпочтительнее программная постобработка строк.