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