Рассмотрим распространенную задачу при разработке в 1С: как развернуть результаты выборки по горизонтали. Проанализируем ситуацию: у нас есть справочник участников, и у каждого участника может быть несколько детей с разными датами рождения. Данные хранятся в строках (один участник — много строк с датами), а нам необходимо получить результат, где участник выводится одной строкой, а даты рождения разложены по колонкам: Дата1, Дата2, Дата3 и так далее.
Сложность заключается в ограничениях платформы 1С. Во-первых, в языке запросов 1С отсутствуют оконные функции, такие как ROW_NUMBER(), которые позволили бы легко пронумеровать строки внутри группы. Во-вторых, количество и имена колонок в результате запроса должны быть жестко определены на этапе его компиляции, платформа не умеет генерировать динамическое число колонок "на лету" силами самого языка запросов.
Разберем основные и наиболее эффективные способы решения этой проблемы.
Если конечная цель — формирование отчета, то самым правильным подходом будет отказ от попыток развернуть данные в самом запросе. Универсальный отчет, построитель отчета и, в первую очередь, СКД отлично справляются с этой задачей.
В СКД достаточно использовать настройку структуры отчета в виде таблицы, где в строках будет группировка по участнику, а в колонках — сами даты или номера по порядку. Более того, если результат нужен не для визуального отчета, а для дальнейшей программной обработки, вы можете программно сформировать СКД, передать ей параметры и выгрузить результат в Таблицу Значений.
Если транспонирование необходимо выполнить исключительно внутри большого пакета запросов, можно пронумеровать строки средствами самого языка запросов. Для этого применяется метод самосоединения таблицы.
Проанализируем алгоритм:
Таблица1.ДатаРождения >= Таблица2.ДатаРождения.КОЛИЧЕСТВО(). Она подсчитает, сколько записей во второй таблице меньше или равны текущей записи из первой таблицы, что по сути даст нам порядковый номер строки.ВЫБОР КОГДА... для распределения по колонкам:
МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 1 ТОГДА ДатаРождения ИНАЧЕ ДАТАВРЕМЯ(1,1,1) КОНЕЦ) КАК Дата1,
МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 2 ТОГДА ДатаРождения ИНАЧЕ ДАТАВРЕМЯ(1,1,1) КОНЕЦ) КАК Дата2
Важное предупреждение: Этот метод порождает декартово произведение строк внутри каждой группы. Для небольших выборок это работает отлично, но на больших объемах данных производительность запроса критически упадет.
Если данных много и производительность критична, разделим задачу на этапы, используя возможности встроенного языка 1С:
НомерСтроки (тип Число).НомерСтроки.МАКСИМУМ(ВЫБОР...) для раскладки по колонкам, как в предыдущем примере.Этот метод работает значительно быстрее самосоединения на больших объемах данных.
Поскольку мы не знаем заранее максимальное количество дат (у кого-то один ребенок, у кого-то десять), жесткое кодирование десяти колонок в тексте запроса приведет к появлению пустых колонок у большинства участников. Решением станет программная сборка строки запроса.
Посмотрим на шаги:
КОЛИЧЕСТВО(ДатаРождения). Сортируем результат по убыванию количества и берем ПЕРВЫЕ 1. Это даст нам максимальное количество дат у одного участника в текущей базе.Для от 1 до полученного максимума. Внутри цикла формируем куски текста запроса: "МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = " + Сч + " ТОГДА ДатаРождения ИНАЧЕ NULL КОНЕЦ) КАК Дата" + Сч.Иногда разворачивание по колонкам требуется только для того, чтобы вывести данные на экран или в печатную форму в одну ячейку, и сложная математика с отдельными датами дальше не предполагается.
Если вы используете платформу версии 8.3.21 и выше, воспользуемся новой функцией языка запросов СОЕДИНИТЬСТРОКИ(). Эта функция позволяет сгруппировать данные по участнику и свернуть все даты рождения в одну текстовую колонку, разделив их заданным символом, например, запятой или переводом строки.
Выбор оптимального решения зависит от конкретных требований вашей задачи: нужно ли это для отчета (тогда выбираем СКД), важна ли производительность на тысячах записей (тогда используем ТЗ) или требуется гибкость в количестве колонок (тогда применяем динамический запрос).