SSAS
October 31, 2023

Создание мер в табличной модели из файла 

Как управлять созданием мер из файла. И зачем это может пригодиться

Дано:

  • Модель с множеством таблиц фактов, планов. Из-за чего все меры типа "Объем продаж" в нескольких вариациях - сценариях. Объем продаж план, факт, прогноз и т.п. По мимо объема продаж есть выручка, чистая прибыль. Итого набирается порядка 250 мер.

Пользователям не удобно использовать столько мер. Помнить как называется Объем продаж в том или ином сценарии. Поступил запрос.

Задача:

  • Сделать одну меру на каждый показатель и фильтр на сценарий.
  • Дополнительно рассчитать сценарий прогноза года, что в зависимости от периода использует лишь один сценарий

Разберу на примере меры Объема продаж.

Изменение архитектуры куба

Дополнительные вводные:

  1. Объем продаж каждой таблицы фактов принадлежит своему сценарию. Плановый объем в таблице планов, фактический - в фактах и т.п.
  2. Есть несколько сценариев, которые собирают данные из разных таблиц фактов, например Прогноз года на картинке выше.
  3. Нужно создать около 50 мер на замену 250, что учтут все сценарии.
  4. Будут добавляться новые сценарии, их количество и архитектура не известна

Решение:

  • Создать таблицу сценариев. Отметить сценарии, что не требуют доп. вычислений. Как Прогноз года, что берет разные сценарии.
  • В каждую таблицу фактов добавить идентификатор сценария
  • Сделать неактивную связь справочника сценариев с таблицами факта
  • Создать первую меру из 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.

  • Далее создать список мер и параметров в excel для наполнения шаблона

Сохранить как текстовый файл с разделителем табуляции. Кодировка 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];
       } 
    }

Скрипт готов. Можно запустить его на выполнение и получить список мер в таблице Сценарии:)

Вывод:

Я представила, как бы я выполняла задачу вручную, как горько бы было при любом чихе заказчика в сторону этих мер. Больше у меня такой проблемы нет. Я изменяю файлы + переменные в коде. Готово.

Дорабатывай, дорогой заказчик, так, чтобы тебе было удобно!