Довольно часто возникает ситуация, когда в таблице БД, в коллекции на форме или в таблице значений содержатся данные, логически связанные N:1, и требующие поблочной обработки, т.е. группировки в блоки и некоего действия. Содержимое таблицы надо разбить на смысловые фрагменты-блоки, и обработать каждый блок как отдельное подмножество. Частным случаем являются связки 1:1, подлежащие обработке той же механикой.
Примерами могут быть товары, которые надо инвентаризировать по их видам номенклатуры (колонки "Товар", "Вид") и должно быть столько инвентаризаций, сколько видов товаров; или связки "Товар"+"Склад", словом, задача возникает в текучке разработки постоянно. Есть несколько способов решения, каждый из которых имеет плюсы и минусы, но суть одна - "сгрести" единообразные данные из таблицы в отдельные множества и обработать.
Так, будем говорить о группировочных колонках и о группируемых. Будем оперировать простыми и ссылочными типами, и оставим за кадром статьи специфику поведения 1С в случаях, когда группировать надо по колонкам, содержащим коллекции или всякие экзотические объекты.
Основные отличия в способах зависят от:
- Происхождения данных. Если это таблица БД, то разумно применить запросную технику или СКД; важно учитывать размер выборки. Если это таблица на интерфейсе или в сеансовом кэше (например, прочитанная из Excel), то можно оперировать таблицей значений напрямую (заносить таблицу в запрос ради удобства реализации может оказаться нерационально).
- Наполнения и объёма данных. Если это таблица с высокой селективностью группировочных полей (много мелких фрагментиков), разнотипным наполнением, необходимостью дообработки "по месту" (например, ВРег или СокрЛП), то запрос может проиграть простым действиям с таблицами. Аналогично, если таблица мала по объёму.
- Логики задачи. Просто "перетасовать" данные, записав их в другую коллекцию, или же обращаться к БД, в т.ч. на запись - это влияет на выбор способа в соответствии с количеством и качеством конкретных действий с каждым фрагментом. Важно количество группировочных полей - если оно одно (один критерий), всё просто, а если блок однозначно определяется только кортежем, совокупностью значений нескольких полей, то не все способы годятся.
- Ресурсов, используемых 1С. Разнесённость серверов СУБД и приложений при плохой связи между ними, или малое место под сеансовые данные - это тоже может сказаться на поведении запросов и СКД.
Обзор не ставит целью утвердить превосходство какого-либо из способов, все они выигрышны либо костыльны в той или иной конкретной ситуации.
Будем рассматривать способы решения на примере сотрудников. Есть таблица из колонок "Сотрудник" (ссылка), "Должность" (ссылка), "ДатаРождения" (дата). Задача, например, сформировать списки на премии и штрафы согласно должностям (см. картинку). Для простоты примем, что группировочные колонки однотипны, не могут содержать пустые значения.
В описании каждого способа применён запрос, но указано, обязательно ли это, или можно работать сразу с таблицей. Указаны места, где реализуется конкретная логика. В нашем примере это вывод сообщений в текстовый документ "тЛог". Работа с группировочными (т.н."ключевыми") полями приведена универсально, с группируемыми - на конкретном примере.
Итак:
Обход таблицы, группировка по одной колонке
Основан на запоминании предыдущего, "старого" значения группировочной колонки. Можно работать сразу с таблицей.
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|// сортировка будет сделана уже в таблице
|";
рЗапрос=Новый Запрос(рТекстЗапроса);
рРезультатЗапроса=рЗапрос.Выполнить();
тДанных=рРезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.Прямой);
рГруппировочноеПоле="Должность"; // группируем по должностям
тДанных.Сортировать(рГруппировочноеПоле+" ВОЗР");
старЗначение=Неопределено; // т.к. если возможны пустые ссылки, указание исходного старого значения как ПустаяСсылка() приведёт к ошибке
Для каждого стро Из тДанных Цикл
// проверяем, не начался ли новый блок
Если стро[рГруппировочноеПоле]<>старЗначение Тогда // начался новый блок, с новым значением
Если старЗначение<>Неопределено Тогда // это не первая итерация, и исходим из того, что Неопределено среди данных таблицы нет
// заканчиваем обработку накопленных данных по предыдущему старому значению, согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЕсли;
// обновляем "старое значение"
старЗначение=стро[рГруппировочноеПоле];
// начинаем новый блок согласно логике задачи
тЛог.ДобавитьСтроку("== начало блока "+Строка(старЗначение)+" ======");
КонецЕсли;
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(стро.Сотрудник)+", д.р. "+Формат(стро.ДатаРождения,"ДФ=dd.MM.yy"));
КонецЦикла;
// Последний блок
Если старЗначение<>Неопределено Тогда // если вообще хоть что-то было
// заканчиваем обработку накопленных данных по предыдущему старому значению, согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЕсли;
Обход таблицы, группировка по многим колонкам
Основан на запоминании совокупности предыдущих значений группировочных колонок. Можно работать сразу с таблицей. В примере показано упрощение обработки финального блока.
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|// сортировка будет сделана уже в таблице
|";
рЗапрос=Новый Запрос(рТекстЗапроса);
рРезультатЗапроса=рЗапрос.Выполнить();
тДанных=рРезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.Прямой);
// Запоминание предыдущих данных, универсальный вариант (оправдан, если полей более 3-4,
// чтобы не писать проверку вроде старПоле1<>стро.Поле1 или старПоле2<>стро.Поле2
// Если последней строкой добавить в таблицу нечто самоочевидно пустое, тогда "последний блок" не понадобится,
// но в обработке текущего значения придётся выполнять проверку на эту пустоту, что в цикле займёт больше времени и ресурса.
// Проиллюстрируем такое добавление в текущем примере.
рГруппировочныеПоля="Должность,ДатаРождения";
тДанных.Сортировать(СтрЗаменить(рГруппировочныеПоля,","," ВОЗР,")+" ВОЗР");
тДанных.Добавить(); // уже после сортировки!
струСтарыеЗначения=Новый Структура(рГруппировочныеПоля); // значения не инициализированы
Для каждого стро Из тДанных Цикл
// проверяем, не начался ли новый блок
рИмяИзменённогоПоля="";
Для каждого киз Из струСтарыеЗначения Цикл
Если киз.Значение<>стро[киз.Ключ] Тогда
рИмяИзменённогоПоля=киз.Ключ; Прервать;
КонецЕсли;
КонецЦикла;
Если не ПустаяСтрока(рИмяИзменённогоПоля) Тогда // начался новый блок, с новым значением
Если струСтарыеЗначения[рИмяИзменённогоПоля]<>Неопределено Тогда
// это не первая итерация, и исходим из того, что Неопределено среди данных нет
// заканчиваем обработку накопленных данных по предыдущему старому значению, согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЕсли;
Если ЗначениеЗаполнено(стро.Сотрудник) Тогда // это не добавленная "пустышка"
// обновляем "старые значения"
струСтарыеЗначения.Вставить(рИмяИзменённогоПоля,стро[рИмяИзменённогоПоля]);
// или можно ЗаполнитьЗначенияСвойств(струСтарыеЗначения,стро); если изменились сразу несколько полей и надо учесть это
// начинаем новый блок согласно логике задачи
предстНовогоБлока="";
Для каждого киз Из струСтарыеЗначения Цикл
предстНовогоБлока=предстНовогоБлока+" "+киз.Ключ+": "+Строка(стро[киз.Ключ]);
КонецЦикла;
тЛог.ДобавитьСтроку("== начало блока"+предстНовогоБлока+" ======");
КонецЕсли;
КонецЕсли;
// обрабатываем текущее значение, согласно логике задачи, и с учётом добавленной "пустышки" (позволяющей обойтись без последнего блока)
Если ЗначениеЗаполнено(стро.Сотрудник) Тогда
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(стро.Сотрудник));
КонецЕсли;
КонецЦикла;
Итоги в запросе, группировка по одной колонке
Основан на выстраивании древовидного результата запроса. "Классический". Хорош для одной группировочной колонки, а для многих придётся либо делать вложенный цикл, либо использовать рекурсию обхода, т.к. каждое группировочное поле порождает свой уровень вложенности, а нам нужна не "чистая" иерархия (каждому полю свой уровень), а частичная, "полу-плоская" (все группировочные на старшем уровне, все группируемые на вложенном).
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|УПОРЯДОЧИТЬ ПО
| Сотрудники.Должность ВОЗР // исключительно для удобства восприятия
|ИТОГИ ПО
| Сотрудники.Должность // это группировочное поле
|";
рЗапрос=Новый Запрос(рТекстЗапроса);
рРезультатЗапроса=рЗапрос.Выполнить();
рВыборка=рРезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
// обходим "старшие" группировки
Пока рВыборка.Следующий() Цикл
// начинаем новый блок согласно логике задачи
тЛог.ДобавитьСтроку("== начало блока "+Строка(рВыборка[рВыборка.Группировка()])+" ======");
// обходим вложенные "младшие" группировки
рДетальнаяВыборка=рВыборка.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока рДетальнаяВыборка.Следующий() Цикл
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(рДетальнаяВыборка.Сотрудник)+", д.р. "+Формат(рДетальнаяВыборка.ДатаРождения,"ДФ=dd.MM.yy"));
КонецЦикла;
// заканчиваем обработку накопленных данных по текущему значению группировки, согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЦикла;
Обход результата запроса, группировка по одной колонке
Обход выборки запроса, группировка по одной колонке
Основан на возможностях обхода результата запроса функцией "СледующийПоЗначениюПоля". Хорош для одной группировочной колонки. Использование функции "НайтиСледующий" (пригодной для обработки многих группировочных колонок) не рассматривается, т.к. это по сути будет схоже с обходом таблицы.
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|УПОРЯДОЧИТЬ ПО
| Сотрудники.Должность ВОЗР // обязательно
|";
рЗапрос=Новый Запрос(рТекстЗапроса);
рРезультатЗапроса=рЗапрос.Выполнить();
рВыборка=рРезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.Прямой);
рГруппировочноеПоле="Должность";
// идём по различающимся значениям ключевого поля
Пока рВыборка.СледующийПоЗначениюПоля(рГруппировочноеПоле) Цикл
// начинаем новый блок согласно логике задачи
тЛог.ДобавитьСтроку("== начало блока "+Строка(рВыборка[рГруппировочноеПоле])+" ======");
// идём внутри подмножества
Пока рВыборка.Следующий() Цикл // но не СледующийПоЗначениюПоля, а именно одинаковые значения в рамках заявленного группировочного
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(рВыборка.Сотрудник)+", д.р. "+Формат(рВыборка.ДатаРождения,"ДФ=dd.MM.yy"));
КонецЦикла;
// заканчиваем обработку накопленных данных по текущему значению поля согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЦикла;
Временные таблицы, группировка по многим колонкам
Основан на выборке данных во временную таблицу, из которой сперва получается таблица кортежей значений группировочных полей, а затем циклом по ней запросы получают из неё подтаблицы по условиям этих кортежей. Может оказаться проигрышным по производительности, требователен к месту под временную таблицу.
рГруппировочныеПоля="Должность,ДатаРождения"; // в этом варианте определяем до запроса
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ПОМЕСТИТЬ
| ВТ_Выборка
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|;
|
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| "+СтрЗаменить(рГруппировочныеПоля,",",", вт.")+"
|ИЗ
| ВТ_Выборка КАК вт
|
|// Можно использовать СГРУППИРОВАТЬ ПО, смотря какие поля, их типы и наполнение
|";
рЗапрос=Новый Запрос(рТекстЗапроса);
рЗапрос.МенеджерВременныхТаблиц=Новый МенеджерВременныхТаблиц;
рРезультатЗапроса=рЗапрос.Выполнить();
тГруппировок=рРезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.Прямой);
Для каждого строгруп Из тГруппировок Цикл
предстНовогоБлока="";
рУсловия="";
Для каждого рПоле Из рРезультатЗапроса.Колонки Цикл // это быстрее обхода по колонкам таблицы
рУсловия=рУсловия+"
| И вт."+рПоле.Имя+" = &УслПоле"+рПоле.Имя;
рЗапрос.УстановитьПараметр("УслПоле"+рПоле.Имя,строгруп[рПоле.Имя]);
предстНовогоБлока=предстНовогоБлока+" "+рПоле.Имя+": "+Строка(строгруп[рПоле.Имя]);
КонецЦикла;
рЗапрос.Текст="ВЫБРАТЬ * ИЗ ВТ_Выборка КАК вт ГДЕ Истина "+рУсловия;
тДанных=рЗапрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.Прямой);
// начинаем новый блок согласно логике задачи
тЛог.ДобавитьСтроку("== начало блока"+предстНовогоБлока+" ======");
Для каждого стро Из тДанных Цикл
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(стро.Сотрудник));
КонецЦикла;
// заканчиваем обработку накопленных данных по текущему значению поля согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЦикла;
Таблица-оглавление, группировка по многим колонкам
Основан на 2 таблицах - свёрнутой и детальной. Схож с предыдущим, выборкой из временной таблицы, но использует получение подмножества из таблицы значений (НайтиСтроки или Скопировать). Требователен к месту под таблицу значений. Можно работать сразу с таблицей.
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|УПОРЯДОЧИТЬ ПО
| Сотрудники.Должность ВОЗР // исключительно для удобства восприятия
|";
рЗапрос=Новый Запрос(рТекстЗапроса);
рРезультатЗапроса=рЗапрос.Выполнить();
тДанных=рРезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.Прямой);
рГруппировочныеПоля="Должность,ДатаРождения";
тГруппировок=тДанных.Скопировать(,рГруппировочныеПоля);
тГруппировок.Свернуть(рГруппировочныеПоля);
тДанных.Индексы.Добавить(рГруппировочныеПоля); // если надо по скорости и объёмам
Для каждого строгруп Из тГруппировок Цикл
// получаем подмножество соответствующих отбору строк исходной таблицы
рОтбор=Новый Структура(рГруппировочныеПоля);
ЗаполнитьЗначенияСвойств(рОтбор,строгруп);
рДанныеГруппировки=тДанных.НайтиСтроки(рОтбор);
// или рДанныеГруппировки=тДанных.Скопировать(рОтбор), если нужны ещё внутренние действия с подмножеством именно как с таблицей значений
// начинаем новый блок согласно логике задачи
предстНовогоБлока="";
Для каждого киз Из рОтбор Цикл
предстНовогоБлока=предстНовогоБлока+" "+киз.Ключ+": "+Строка(киз.Значение);
КонецЦикла;
тЛог.ДобавитьСтроку("== начало блока"+предстНовогоБлока+" ======");
Для каждого строДанных Из рДанныеГруппировки Цикл
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(строДанных.Сотрудник));
КонецЦикла;
// заканчиваем обработку накопленных данных по текущему значению поля согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЦикла;
Стандартная СКД, группировка по многим колонкам
Основан на возможности СКД задать структуру результата с группировкой по N полям сразу, и вывести данные в дерево значений. Требует только накладных расходов на работу с СКД. Правда, кода побольше, чем в других случаях.
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|";
рГруппировочныеПоля="Должность,ДатаРождения";
// Создаём саму СКД
рСКД=Новый СхемаКомпоновкиДанных;
//
рИсточникДанных=рСКД.ИсточникиДанных.Добавить();
рИсточникДанных.Имя="ОсновнойИсточник";
рИсточникДанных.ТипИсточникаДанных="Local";
//
рНабор=рСКД.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
рНабор.Имя="ОсновнойНабор";
рНабор.Запрос=рТекстЗапроса;
рНабор.ИсточникДанных="ОсновнойИсточник";
рНабор.АвтоЗаполнениеДоступныхПолей=Истина;
// Устанавливаем настройки
рНастройка=рСКД.НастройкиПоУмолчанию;
//
// создаём группирующую группировку
мИмёнПолей=СтрРазделить(рГруппировочныеПоля,",",Ложь);
рГруппировкаКД=рНастройка.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
рГруппировкаКД.Использование=Истина;
Для каждого рИмяПоля Из мИмёнПолей Цикл
рПоле=рГруппировкаКД.ПоляГруппировки.Элементы.Добавить(Тип("ПолеГруппировкиКомпоновкиДанных"));
рПоле.Поле=Новый ПолеКомпоновкиДанных(рИмяПоля);
// если надо, можно поставить дополнение для полей типа "Дата"
рПоле.ТипГруппировки=ТипГруппировкиКомпоновкиДанных.Элементы;
рПоле.Использование=Истина;
// исключительно для удобства восприятия отсортируем
рПорядок=рГруппировкаКД.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
рПорядок.Поле=Новый ПолеКомпоновкиДанных(рИмяПоля);
рПорядок.ТипУпорядочивания=НаправлениеСортировкиКомпоновкиДанных.Возр;
рПорядок.Использование=Истина;
КонецЦикла;
// и её автополе
рВыбПолеГр=рГруппировкаКД.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
рВыбПолеГр.Использование=Истина;
//
// создадим группировку <детальных записей>
рГруппировкаКДДетальная=рГруппировкаКД.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
рГруппировкаКДДетальная.Использование=Истина;
// и её детальные поля
рВыбПолеГр=рГруппировкаКДДетальная.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
рВыбПолеГр.Поле=Новый ПолеКомпоновкиДанных("Сотрудник");
рВыбПолеГр.Использование=Истина;
рВыбПолеГр=рНастройка.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
рВыбПолеГр.Поле=Новый ПолеКомпоновкиДанных("Сотрудник");
рВыбПолеГр.Использование=Истина;
// Получаем результат в виде дерева значений
рКомпоновщикМакета=Новый КомпоновщикМакетаКомпоновкиДанных;
рМакетКомпоновки=рКомпоновщикМакета.Выполнить(рСКД,рНастройка,,,Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
//
рПроцессорКД=Новый ПроцессорКомпоновкиДанных;
рПроцессорКД.Инициализировать(рМакетКомпоновки);
//
дДанных=Новый ДеревоЗначений;
рПроцессорВывода=Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
рПроцессорВывода.УстановитьОбъект(дДанных);
дДанных=рПроцессорВывода.Вывести(рПроцессорКД,Истина);
// Собственно обработаем
Для каждого рВеткаГрупп Из дДанных.Строки Цикл
предстНовогоБлока="";
Для каждого рИмяПоля Из мИмёнПолей Цикл
предстНовогоБлока=предстНовогоБлока+" "+рИмяПоля+": "+Строка(рВеткаГрупп[рИмяПоля]);
КонецЦикла;
// начинаем новый блок согласно логике задачи
тЛог.ДобавитьСтроку("== начало блока"+предстНовогоБлока+" ======");
Для каждого рПодветка Из рВеткаГрупп.Строки Цикл
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(рПодветка.Сотрудник));
КонецЦикла;
// заканчиваем обработку накопленных данных по текущему значению поля согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЦикла;
Функция СКД, группировка по многим колонкам
Основан на функции СКД "ТаблицаЗначений", чью структуру и состав определяют группировочные поля, и которая для каждой группировки заносится в колонку. Такая колонка делается вычисляемым полем СКД. Требует накладных расходов на работу с СКД и малопонятные лично мне процессорные мощности на сервере 1С. Кода тоже немало.
рТекстЗапроса="ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Сотрудники.Ссылка КАК Сотрудник,
| Сотрудники.Должность,
| Сотрудники.ДатаРождения
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Должность <> ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)
|";
рГруппировочныеПоля="Должность,ДатаРождения";
// Создаём саму СКД
рСКД=Новый СхемаКомпоновкиДанных;
//
рИсточникДанных=рСКД.ИсточникиДанных.Добавить();
рИсточникДанных.Имя="ОсновнойИсточник";
рИсточникДанных.ТипИсточникаДанных="Local";
//
рНабор=рСКД.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
рНабор.Имя="ОсновнойНабор";
рНабор.Запрос=рТекстЗапроса;
рНабор.ИсточникДанных="ОсновнойИсточник";
рНабор.АвтоЗаполнениеДоступныхПолей=Истина;
// Устанавливаем настройки
рНастройка=рСКД.НастройкиПоУмолчанию;
//
// создаём группирующую группировку
мИмёнПолей=СтрРазделить(рГруппировочныеПоля,",",Ложь);
рГруппировкаКД=рНастройка.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
рГруппировкаКД.Использование=Истина;
Для каждого рИмяПоля Из мИмёнПолей Цикл
рПоле=рГруппировкаКД.ПоляГруппировки.Элементы.Добавить(Тип("ПолеГруппировкиКомпоновкиДанных"));
рПоле.Поле=Новый ПолеКомпоновкиДанных(рИмяПоля);
// если надо, можно поставить дополнение для полей типа "Дата"
рПоле.ТипГруппировки=ТипГруппировкиКомпоновкиДанных.Элементы;
рПоле.Использование=Истина;
// исключительно для удобства восприятия отсортируем
рПорядок=рГруппировкаКД.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
рПорядок.Поле=Новый ПолеКомпоновкиДанных(рИмяПоля);
рПорядок.ТипУпорядочивания=НаправлениеСортировкиКомпоновкиДанных.Возр;
рПорядок.Использование=Истина;
КонецЦикла;
// и её автополе
рВыбПолеГр=рГруппировкаКД.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
рВыбПолеГр.Использование=Истина;
//
// группировка <детальных записей> тут не нужна
// вносим само поле-вычислитель
рВычПоле=рСКД.ВычисляемыеПоля.Добавить();
рВычПоле.ПутьКДанным="ДетальныеЗаписи"; // в поле основной таблицы результатов с этим именем будут подтаблицы с колонкой "Сотрудник"
рВычПоле.Выражение="ВычислитьВыражение(""ТаблицаЗначений(Сотрудник КАК Сотрудник)"",,,""Текущая"",""Последняя"")";
//
рИтПоле=рСКД.ПоляИтога.Добавить();
рИтПоле.Выражение=рВычПоле.ПутьКДанным;
Для каждого рИмяПоля Из мИмёнПолей Цикл
рИтПоле.Группировки.Добавить(рИмяПоля);
КонецЦикла;
рИтПоле.ПутьКДанным=рВычПоле.ПутьКДанным;
//
рВыбПоле=рНастройка.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
рВыбПоле.Поле=Новый ПолеКомпоновкиДанных(рВычПоле.ПутьКДанным);
// если общие итоги не выключить, допишет пустую ненужную строку, где и "ДетальныеЗаписи" не таблица
рПараметр=рНастройка.ПараметрыВывода.Элементы.Найти("ВертикальноеРасположениеОбщихИтогов");
рПараметр.Значение=РасположениеИтоговКомпоновкиДанных.Нет;
рПараметр.Использование=Истина;
// Получаем результат в виде таблицы значений
рКомпоновщикМакета=Новый КомпоновщикМакетаКомпоновкиДанных;
рМакетКомпоновки=рКомпоновщикМакета.Выполнить(рСКД,рНастройка,,,Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
//
рПроцессорКД=Новый ПроцессорКомпоновкиДанных;
рПроцессорКД.Инициализировать(рМакетКомпоновки);
//
тДанных=Новый ТаблицаЗначений;
рПроцессорВывода=Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
рПроцессорВывода.УстановитьОбъект(тДанных);
тДанных=рПроцессорВывода.Вывести(рПроцессорКД,Истина);
// Собственно обработаем
Для каждого стро Из тДанных Цикл
предстНовогоБлока="";
Для каждого рИмяПоля Из мИмёнПолей Цикл
предстНовогоБлока=предстНовогоБлока+" "+рИмяПоля+": "+Строка(стро[рИмяПоля]);
КонецЦикла;
// начинаем новый блок согласно логике задачи
тЛог.ДобавитьСтроку("== начало блока"+предстНовогоБлока+" ======");
Для каждого строДетальных Из стро.ДетальныеЗаписи Цикл
// обрабатываем текущее значение, согласно логике задачи
тЛог.ДобавитьСтроку("Сотрудник: "+Строка(строДетальных.Сотрудник));
КонецЦикла;
// заканчиваем обработку накопленных данных по текущему значению поля согласно логике задачи
тЛог.ДобавитьСтроку("=== конец блока ============");
КонецЦикла;
В принципе, можно изобрести ещё что-нибудь извратное, но основные варианты вроде бы все перечислил. Если что-то забыл - дополнения приветствуются.