Создание мер в табличной модели из файла
Как управлять созданием мер из файла. И зачем это может пригодиться
- Модель с множеством таблиц фактов, планов. Из-за чего все меры типа "Объем продаж" в нескольких вариациях - сценариях. Объем продаж план, факт, прогноз и т.п. По мимо объема продаж есть выручка, чистая прибыль. Итого набирается порядка 250 мер.
Пользователям не удобно использовать столько мер. Помнить как называется Объем продаж в том или ином сценарии. Поступил запрос.
- Сделать одну меру на каждый показатель и фильтр на сценарий.
- Дополнительно рассчитать сценарий прогноза года, что в зависимости от периода использует лишь один сценарий
Разберу на примере меры Объема продаж.
- Объем продаж каждой таблицы фактов принадлежит своему сценарию. Плановый объем в таблице планов, фактический - в фактах и т.п.
- Есть несколько сценариев, которые собирают данные из разных таблиц фактов, например Прогноз года на картинке выше.
- Нужно создать около 50 мер на замену 250, что учтут все сценарии.
- Будут добавляться новые сценарии, их количество и архитектура не известна
- Создать таблицу сценариев. Отметить сценарии, что не требуют доп. вычислений. Как Прогноз года, что берет разные сценарии.
- В каждую таблицу фактов добавить идентификатор сценария
- Сделать неактивную связь справочника сценариев с таблицами факта
- Создать первую меру из 50. Проверить работу связей и логику. Взять этот скрипт за основу шаблона, для создания остальных мер. Мера должна возвращать результат, только при выборе одного сценария.
Мера будет делиться на типы сценариев
если сценарий Факт, то [Факт объем] если сценарий План, то [План объем] если сценарий Прогноз, то [Прогноз объем]
Если сценарий = прогноз года, то [Факт объем] за январь + [План объем] за февраль + [Прогноз объем] за март
- В итоге можно создать шаблон остальных мер, который можно использовать при создании новых мер или добавлении новых сценариев, логики
var curscen = SELECTEDVALUE('Сценарии'[Сценарий])
var nocalcscen = CALCULATETABLE(SUMMARIZE('Сценарии','Сценарии'[Сценарий])
,'Сценарии'[Calculated]=FALSE())
return
SWITCH(TRUE()
, curscen = "Факт"
, @fact
, curscen = "План"
, @plan
, curscen = "Прогноз"
, @prognosis
,curscen = "Прогноз года"
,@fact за январь
+ @plan за февраль
+ @prognosis за март
,blank()
)В коде обозначили параметры вместо реальных мер. Сохранить как текстовый файл в формате UTF-8.
Сохранить как текстовый файл с разделителем табуляции. Кодировка UTF-8. В шаблоне прописать формат меры. Тк это важный атрибут при создании меры. Можно указать любые нужные свойства меры.
- Начинается самое интересное. В Tabular Editor написать код C#, что будет использовать заготовленные файлы и создавать меры.
За основу взят мануал по Tabular editor.
https://docs.tabulareditor.com/te2/Useful-script-snippets.html?q=ReadFile
Назначить целевую таблицу для мер:
var targetTable = Model.Tables["Сценарии"];
Назначить основные переменные:
var filePath = @"\\путь к файлу с мерами\Меры.txt"; var measurescriptpath = @"\\путь к шаблону скрипта мер\measurescript.txt"; //номера колонок в файле int _measurenameindex = 0; int _totalexpindex = 1; int _planindex = 2; int _prognosisindex = 3; int _factindex = 4; int _formatindex = 5;
string measureMetadata;
string measurescript;
using (var fileStream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
using (var textReader = new System.IO.StreamReader(fileStream, System.Text.Encoding.Default))
{
measureMetadata = textReader.ReadToEnd();
}
using (var fileStream = new System.IO.FileStream(measurescriptpath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
using (var textReader = new System.IO.StreamReader(fileStream, System.Text.Encoding.Default))
{
measurescript = textReader.ReadToEnd();
}Разделить measureMetadata на строки и запустить по ним цикл, пропустив строку заголовков:
var tsvRows = measureMetadata.Split(new[] {'\r','\n'},StringSplitOptions.RemoveEmptyEntries);
foreach(var row in tsvRows.Skip(1))
{
}В цикле получить массив колонок одной строки. Узнать имя меры:
var tsvColumns = row.Split('\t');
name = tsvColumns[_measurenameindex]; //имя мерыПолучить свойство expression для меры. Если колонка total не пустая, то expression взять оттуда. Иначе подменить параметры в measurescript:
if(tsvColumns[_totalexpindex]!="")
{
expression = tsvColumns[_totalexpindex];
}
else // ДОПОЛНИТЬ ЕСЛИ БУДУТ ЕЩЕ СЦЕНАРИИ =================
{
expression = measurescript.Replace("@fact",tsvColumns[_factindex]);
expression = expression.Replace("@prognosis",tsvColumns[_prognosisindex]);
expression = expression.Replace("@plan",tsvColumns[_planindex]);
} //====================================================
Проверить существование меры. Если существует, обновить свойства, иначе создать:
var exists = targetTable.Measures.Where(m => m.Name == name).ToList();
if ( exists.Count == 0 ) //нет такой меры, создаем новую
{
var measure = targetTable.AddMeasure(name);
measure.Expression = expression;
measure.FormatString = tsvColumns[_formatindex];
measure.SetAnnotation("AUTOGEN", "1"); // Set a special annotation on the measure, so we can find it and delete it the next time the script is executed.
}
else //обновляем выражение и формат если мера сущ-т
{
foreach(var m in exists)
{
m.Expression = expression;
m.FormatString = tsvColumns[_formatindex];
}
}Скрипт готов. Можно запустить его на выполнение и получить список мер в таблице Сценарии:)
Я представила, как бы я выполняла задачу вручную, как горько бы было при любом чихе заказчика в сторону этих мер. Больше у меня такой проблемы нет. Я изменяю файлы + переменные в коде. Готово.