gifts2017

Свёртка таблицы без потери данных

Опубликовал Яков Коган (Yashazz) в раздел Программирование - Практика программирования

Было в таблице значений 3 колонки, по одной группировали, по другой суммировали, а третья и потерялась... Знакомая сказка? Чтобы не плодить вспомогательные данные, эта функция сворачивает таблицу так, что в специальной колонке будут подтаблицы, хранящие "свёрнутые" куски в первозданном виде. Может быть интересно изучающим некоторые фичи СКД.

Собственно, всё упихнуто в одну функцию, и стандартная свёртка тоже. Проверено на практике, но на тестовом массиве с небольшим разнообразием. Я вот пока не очень уверен, что в любом случае будет работать - например, при каких-нибудь экзотических типах колонок или их наполнении. Но если кому пригодится, и хорошо. Как пример работы с СКД, кстати ))) 

// рИсточник - собственно объект-источник (обычно таблица значений, её имя определяется автоматически)
Функция ПоместитьРезультатСКДвТаблицуЗначений(рСКД,рКомпНастроекИлиНастройки,рИсточник="") Экспорт
Попытка
   
РезультатнаяТаблица=Новый ТаблицаЗначений;
   
//
    // исходя из типа источника
   
рНабор=рСКД.НаборыДанных.Получить(0); // основной набор
    //
   
КомпоновщикМакета=Новый КомпоновщикМакетаКомпоновкиДанных;
   
рТипГенератора=Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений");
    Если
ТипЗнч(рКомпНастроекИлиНастройки)=Тип("НастройкиКомпоновкиДанных") Тогда
       
МакетКомпоновки=КомпоновщикМакета.Выполнить(рСКД,рКомпНастроекИлиНастройки,,,рТипГенератора);
    ИначеЕсли
ТипЗнч(рКомпНастроекИлиНастройки)=Тип("КомпоновщикНастроекКомпоновкиДанных") Тогда
       
МакетКомпоновки=КомпоновщикМакета.Выполнить(рСКД,рКомпНастроекИлиНастройки.Настройки,,,рТипГенератора);
    КонецЕсли;
   
ПроцессорКД=Новый ПроцессорКомпоновкиДанных;
    Если
ТипЗнч(рНабор)=Тип("НаборДанныхОбъектСхемыКомпоновкиДанных") Тогда
       
ПроцессорКД.Инициализировать(МакетКомпоновки,Новый Структура(СокрЛП(рНабор.ИмяОбъекта),рИсточник));
    Иначе
       
ПроцессорКД.Инициализировать(МакетКомпоновки);
    КонецЕсли;
   
ПроцессорВывода=Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
   
ПроцессорВывода.УстановитьОбъект(РезультатнаяТаблица);
   
#Если Клиент Тогда
       
ПроцессорВывода.ОтображатьПроцентВывода=Истина;
   
#КонецЕсли
   
РезультатнаяТаблица=ПроцессорВывода.Вывести(ПроцессорКД,Истина);
    Возврат
РезультатнаяТаблица;
Исключение
   
Сообщить("ПоместитьРезультатСКДвТаблицуЗначений: ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
    Возврат
РезультатнаяТаблица;
КонецПопытки;
КонецФункции

// Свёртка таблицы, возвращаемое значение типа "ТаблицаЗначений"
// Параметры:
//      рТаблица - исходная таблица значений; допустимы любые типы колонок, доступные к выборке в СКД;
//      рПоляСвертки - имена колонок таблицы через запятую; по их одинаковым значениям будет выполняться группировка;
//           если строка пуста, и не пуста строка рПоляСуммы, то все колонки, не входящие в суммируемые, войдут в группируемые.
//      рПоляСумма - имена колонок таблицы через запятую, значения этих колонок суммируются по общим принципам свёртки
//           и суммирования полей в СКД; если строка пуста, то: а) при пустой строке свёртки будет предпринята попытка заполнить
//           строку именами колонок, имеющих числовой тип (хотя бы и среди других типов), и если не удастся, завершит функцию;
//           б) при непустой строке свёртки и непустом имени колонки-агрегатора выполнит свёртку, не суммируя ни по какому полю;
//           в) при непустой строке свёртки и пустом имени колонки-агрегатора выполнит обычную свёртку без суммовых колонок;
//      рИмяКолонкиАгрегатора - необязательное имя поля колонки для вложенных таблиц значений; если не указано, происходит
//           обычная свёртка средствами 1С, а если указано, в результатную таблицу вносится колонка с этим именем, содержащая
//           таблицы значений "фрагментов" (блоков), соответствующих правилам свёртки; причём колонки, участвующие в суммиро-
//           вании (т.е. из рПоляСуммы) также в исходном, непросуммированном виде присутствуют в этих вложеных подтаблицах.
//
Функция СвернутьТаблицу(Знач рТаблица,Знач рПоляСвертки,Знач рПоляСуммы,рИмяКолонкиАгрегатора="") Экспорт
Попытка
   
мПоляСвертки=ОбщегоНазначения.РазложитьСтрокуВМассивПодстрок(рПоляСвертки,",");
   
мПоляСуммы=ОбщегоНазначения.РазложитьСтрокуВМассивПодстрок(рПоляСуммы,",");
    Если
ТипЗнч(рТаблица)<>Тип("ТаблицаЗначений") Тогда Возврат рТаблица КонецЕсли;
    Если
рТаблица.Колонки.Количество()=0 Тогда Возврат рТаблица КонецЕсли;

    Если
мПоляСвертки.Количество()=0 и мПоляСуммы.Количество()=0 Тогда
       
// определяем по типам значений
       
рПоляСуммы=""; разд="";
        Для каждого
кол Из рТаблица.Колонки Цикл
            Если
кол.ТипЗначения.СодержитТип(Тип("Число")) Тогда
               
мПоляСуммы.Добавить(СокрЛП(кол.Имя));
               
рПоляСуммы=рПоляСуммы+разд+СокрЛП(кол.Имя); разд=",";
            КонецЕсли;
        КонецЦикла;
        Если
мПоляСуммы.Количество()=0 Тогда // ничего поделать нельзя
           
Возврат рТаблица;
        КонецЕсли;
    КонецЕсли;

    Если
мПоляСвертки.Количество()=0 и мПоляСуммы.Количество()<>0 Тогда
       
// все поля, не входящие в суммируемые, подразумеваются как свёрточные
       
рПоляСвертки=""; разд="";
        Для каждого
кол Из рТаблица.Колонки Цикл
            Если
мПоляСуммы.Найти(СокрЛП(кол.Имя))=Неопределено Тогда
               
мПоляСвертки.Добавить(СокрЛП(кол.Имя));
               
рПоляСвертки=рПоляСвертки+разд+СокрЛП(кол.Имя); разд=",";
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;

    Если
ПустаяСтрока(рИмяКолонкиАгрегатора) Тогда // режим агрегирования не используется
       
рТаблица.Свернуть(рПоляСвертки,рПоляСуммы);
    КонецЕсли;

    Если
ПустаяСтрока(рПоляСвертки)
    или
мПоляСвертки.Количество()=0
   
или ПустаяСтрока(рИмяКолонкиАгрегатора)
    Тогда Возврат
рТаблица КонецЕсли;

   
рСКД=Новый СхемаКомпоновкиДанных;
   
//
   
рИсточникДанных=рСКД.ИсточникиДанных.Добавить();
   
рИсточникДанных.Имя="ОсновнойИсточник";
   
рИсточникДанных.ТипИсточникаДанных="Local";
   
//
   
рНабор=рСКД.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
   
рНабор.Имя="ОсновнойНабор";
   
рНабор.ИмяОбъекта="ТаблицаИсточник"; // связывание с внешними данными идёт именно по нему
   
рНабор.ИсточникДанных="ОсновнойИсточник";
   
//
   
рНастройка=рСКД.НастройкиПоУмолчанию;
   
рГруппировкаКД=рНастройка.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
    Для каждого
рИмяПоляСвертки Из мПоляСвертки Цикл
       
рПолеГр=рГруппировкаКД.ПоляГруппировки.Элементы.Добавить(Тип("ПолеГруппировкиКомпоновкиДанных"));
       
рПолеГр.Поле=Новый ПолеКомпоновкиДанных(СокрЛП(рИмяПоляСвертки));
       
рПолеГр.ТипГруппировки=ТипГруппировкиКомпоновкиДанных.Элементы;
    КонецЦикла;
   
рГруппировкаКД.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
   
//
   
мОстающихся=Новый Массив;
   
стрОстающихся=""; разд="";
    Для каждого
кол Из рТаблица.Колонки Цикл
       
#Если Клиент Тогда
           
ОбработкаПрерыванияПользователя();
       
#КонецЕсли
       
// вносим поля в набор, это обязательно
       
рПоле=рНабор.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
       
рПоле.Заголовок=кол.Заголовок;
       
рПоле.Поле=кол.Имя;
       
рПоле.ПутьКДанным=кол.Имя;
       
рПоле.ТипЗначения=СКДиПостроители.ПолучитьОписаниеТипаБезПустых(кол.ТипЗначения);
       
// выбранные поля добавляем на уровень самой настройки, т.е. группы "Отчёт"
       
рВыбПоле=рНастройка.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
       
рВыбПоле.Заголовок=рПоле.Заголовок;
       
рВыбПоле.Поле=Новый ПолеКомпоновкиДанных(рПоле.ПутьКДанным);
       
// выясним, куда попадает эта колонка
       
Если мПоляСвертки.Найти(СокрЛП(кол.Имя))=Неопределено Тогда // она может быть и сумматорной
           
мОстающихся.Добавить(кол.Имя);
           
стрОстающихся=стрОстающихся+разд+кол.Имя; разд=",";
        КонецЕсли;
    КонецЦикла;
    Если
ПустаяСтрока(стрОстающихся) Тогда // вообще ничего, по сути, не надо
       
рТаблица.Свернуть(СокрЛП(рПоляСвертки));
        Возврат
рТаблица;
    КонецЕсли;

   
// вносим само поле-вычислитель
   
рВыражение="ВЫБОР КОГДА Уровень()=0 ТОГДА ВычислитьВыражение(""ТаблицаЗначений("+стрОстающихся+")"",,,""Текущая"",""Последняя"") КОНЕЦ";
   
рВычПоле=рСКД.ВычисляемыеПоля.Добавить();
   
рВычПоле.ПутьКДанным=СокрЛП(рИмяКолонкиАгрегатора);
   
рВычПоле.Заголовок="Таблицы";
   
рВычПоле.Выражение=рВыражение;
   
//
   
рИтПоле=рСКД.ПоляИтога.Добавить();
   
рИтПоле.Выражение=СокрЛП(рИмяКолонкиАгрегатора);
    Для каждого
рИмяПоляСвертки Из мПоляСвертки Цикл
       
рИтПоле.Группировки.Добавить(СокрЛП(рИмяПоляСвертки));
    КонецЦикла;
   
рИтПоле.ПутьКДанным=СокрЛП(рИмяКолонкиАгрегатора);
   
//
   
Для каждого рИмяПоляСуммы Из мПоляСуммы Цикл
       
рИтПоле=рСКД.ПоляИтога.Добавить();
       
рИтПоле.Выражение="Сумма("+СокрЛП(рИмяПоляСуммы)+")";
       
рИтПоле.ПутьКДанным=СокрЛП(рИмяПоляСуммы);
       
// а группировки оставим пустыми, чтобы шёл по всем
   
КонецЦикла;
   
//
   
рВыбПоле=рНастройка.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
   
рВыбПоле.Заголовок="Таблицы";
   
рВыбПоле.Поле=Новый ПолеКомпоновкиДанных(рВычПоле.ПутьКДанным);

   
рКомпоновщикН=Новый КомпоновщикНастроекКомпоновкиДанных;
   
рКомпоновщикН.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(рСКД));
   
рКомпоновщикН.ЗагрузитьНастройки(рНастройка);

   
резТаблица=СКДиПостроители.ПоместитьРезультатСКДвТаблицуЗначений(рсКД,рКомпоновщикН,рТаблица);
    Если
резТаблица.Количество()<>0 Тогда
       
послстро=резТаблица[резТаблица.Количество()-1];
        Если
ТипЗнч(послстро[СокрЛП(рИмяКолонкиАгрегатора)])<>Тип("ТаблицаЗначений") Тогда
           
резТаблица.Удалить(послстро);
        КонецЕсли;
    КонецЕсли;
   
//
   
Возврат резТаблица;
Исключение
   
Сообщить("СвернутьТаблицу, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
    Возврат
рТаблица;
КонецПопытки;
КонецФункции

 

Внимание! Если у кого-то не заработает или будет работать неверно - пожалуйста, сообщите, постараюсь оперативно разобраться.

Пы.Сы. Не уверен, что за такую фиговинку имеет смысл плюсить.

 

 

См. также

Подписаться Добавить вознаграждение

Комментарии

1. anry mc (AnryMc) 01.07.13 12:51
А зачем? Всегда можно оперировать копией ТЗ.
2. Яков Коган (Yashazz) 01.07.13 13:56
Можно. Только копия ресурс кушает, если большая, и "оперировать", т.е. искать даже по индексированной - иногда скучное дело. Я всего лишь показал альтернативу, когда "всё в одном". ))
3. Ирли Бёрд (EarlyBird) 01.07.13 20:43
жениться тебе надо
тогда будет жаль времени на всякую ненужную ботву
4. Гость 02.07.13 03:28
Как пример простой программной работы с СКД очень хорошо. Стоило преподносить публикацию именно как пример работы с СКД, тогда польза от нее очевидна.
5. Rett Batler (batlerrett) 02.07.13 07:51
Смысл тогда сворачивать? Если нужны списки, так и надо в 3-й колонке значением список делать
6. Яков Коган (Yashazz) 02.07.13 12:04
(3) Позвольте представить, моя жена: http://infostart.ru/community/profile/41675/ - и уже восемь лет вместе )))
(5) Смысл простой - и свернуть надо, и данные не потерять. А список или массив хороши, когда такая колонка одна. Если было 10 колонок, по 1-й и 3-й свернули, пятую суммировали, то, когда нужны все остальные, подтаблица удобнее списка.
smirnov.a; +1 Ответить
7. Ирли Бёрд (EarlyBird) 02.07.13 12:25
честно говоря, пока для себя не вижу практического применения данного решения
если я что-то сворачиваю, я это сворачиваю. Если не надо сворачивать, я не сворачиваю.
Но всё же плюс, за конструктивные ответы (и красивую жену :) )
8. Constantin Welicobratov (Const1C) 02.07.13 13:01
В семерки часто сталкиваюсь с такой задачей. Что нужно данные сохранять, после свертки. В частности при формировании группировок в отчетах. Но, ИМХО, в такой ситуации, лучше, действительно, оперировать таблицей значений. А уж если ресурсы жалко (когда, например, нужно работать с 100500 записями). Лучше использовать временные таблицы.
9. Яков Коган (Yashazz) 02.07.13 13:41
(7) Мне часто приходится сталкиваться, раньше делал через 2 таблицы (свёрнутая и исходная), но оперативку жалко. А так пыхтит сервер приложения, насколько я понял распределение нагрузки. И что-то мне подсказывает, что напрячь механизм СКД в этом случае более экономно, нежели крутить временные таблицы.

А если честно, просто захотелось побаловаться с этими функциями СКД, и всё ))
10. Александр Лыткин (TrinitronOTV) 04.07.13 17:18
не смотря на комментарии, для меня всё равно полезно было ознакомиться, спасибо
11. anry mc (AnryMc) 04.07.13 21:48
(10) TrinitronOTV, Кто к нам пожаловал...
12. Саўка Збянтэжаны (Збянтэжаны Саўка) 05.07.13 09:39
(0) СКДиПостроители - это откуда? не встречал такого модуля.
13. Сергей (sstar90) 05.07.13 10:18
(0) Присоединяюсь к (12)
Если это твой модуль, было бы логично выложить и его (в части, касающейся данного решения).
14. Яков Коган (Yashazz) 05.07.13 12:00
Ёлы-палы, сколько ж я ещё на эти грабли наступать буду... Ща, выложу нужные функции.
15. Саўка Збянтэжаны (Збянтэжаны Саўка) 05.07.13 14:24
ок, ПоместитьРезультатСКДвТаблицуЗначений() уже есть, но нужна еще ПолучитьОписаниеТипаБезПустых()
16. Яков Коган (Yashazz) 05.07.13 21:40
(15) Она совсем простая и необязательная:
Функция ПолучитьОписаниеТипаБезПустых(рОписТипов) Экспорт
мТипов=Новый Массив;
Для каждого рТип Из рОписТипов.Типы() Цикл
Если рТип=Тип("Неопределено") или рТип=Тип("NULL") или рТип=Неопределено или рТип=Null Тогда Продолжить КонецЕсли;
мТипов.Добавить(рТип);
КонецЦикла;
Возврат Новый ОписаниеТипов(мТипов,рОписТипов.КвалификаторыЧисла,рОписТипов.КвалификаторыСтроки,рОписТипов.КвалификаторыДаты);
КонецФункции
17. Аркадий Кучер (Abadonna) 09.07.13 17:08
(0)
Пы.Сы. Не уверен, что за такую фиговинку имеет смысл плюсить.

Вот именно за это плюс и поставлю ;)
18. Марина Чирина (chmv) 10.07.13 13:36
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа