Como calcular / armazenar os 10 principais em um modelo de tabela?

23

Recentemente, criamos um modelo tabular do SSAS para que nossos usuários possam acessá-lo via PowerView. Temos uma medida em uma de nossas tabelas de fatos para obter o TotalActiveItemsuso de uma fórmula:

TotalActive:=COUNTAX(FILTER('Stats', ISBLANK([DeactDate]) = TRUE), 1)

Isso funciona muito bem, conforme necessário, mas agora temos um pedido para obter os 10 principais pais de cada mês no TotalActive.

Para referência, aqui faz parte do nosso modelo:

create table factStats
(
    StatsID INT IDENTITY NOT NULL PRIMARY KEY,
    DevID INT NOT NULL,
    DeactDate DATETIME NULL,
    BillDateTimeID BIGINT NOT NULL,
    CustID INT NOT NULL,
    ParentID INT NOT NULL
);

create table dimCust
(
    CustID INT NOT NULL PRIMARY KEY,
    CustName varchar(150) NOT NULL
);

create table dimParent
(
    ParentID INT NOT NULL PRIMARY KEY,
    ParentName varchar(100) NOT NULL
);

create table dimDateTime
(
    DateTimeID BIGINT NOT NULL PRIMARY KEY
);

SQL Fiddle com tabelas e dados de amostra.

A factStatstabela tem FKs para o DevID, CustID, BillDateTimeID, e ParentID. A solicitação que temos é calcular ou armazenar o valor Top 10 Parentsde cada um com BillDateTimeIDbase no TotalActive AND e incluir tudo que não estiver entre os 10 principais em uma categoria acumulada semelhante à seguinte:

+----------------+------------+------+
| BillDateTimeID |   Parent   | Rank |
+----------------+------------+------+
|       20140801 | Jim        |    1 |
|       20140801 | Bob        |    2 |
|       20140801 | All Others |    3 |
+----------------+------------+------+

Posso facilmente fazer isso no SQL usando funções de janelas, mas tentar reproduzir isso no SSAS tem sido difícil. No SQL, obteríamos o resultado usando:

;with Total as
(
  select 
    ParentID,
    BillDateTimeID,
    sum(case when DeactDate is null then 1 else 0 end) TotalActive
  from factStats
  group by ParentID, BillDateTimeID
),
PRank as
(
  select 
    ParentID,
    BillDateTimeID,
    TotalActive,
    row_number() over(partition by BillDateTimeID 
                      order by TotalActive desc) pr
  from total
)
select 
  parentid,
  BillDateTimeID,
  TotalActive,
  pr
from prank
where pr <= 2
union all
select 
  0,
  BillDateTimeID,
  sum(TotalActive) TotalActive,
  3
from prank
where pr > 2
group by BillDateTimeID
order by BillDateTimeID desc, pr;

Demonstração do SQL Fiddle .

Eu tentei várias maneiras diferentes de obter o resultado, mas cada uma delas teve um problema. Minhas tentativas estão abaixo.

Inicialmente, consegui obter os dados usando uma consulta MDX, mas não sabia como incorporá-los ao nosso modelo de tabela. A consulta MDX para referência é:

with 
set [Top10Parent] AS
(
    (TOPCOUNT({ORDER(({[Parent].[Parent Name].[Parent Name]}),
        ([Measures].[Total Count]), BDESC)}, 10))
)
MEMBER [Parent].[Parent Name].[Others] AS
(
    AGGREGATE(EXCEPT([Parent].[Parent Name].[Parent Name], [Top10Parent]))
)
select 
    [Measures].[Total Count] on columns,
    {[Top10Parent]}+ {[Parent].[Parent Name].[Others]} on Rows
from [OurModel]
where {[Date and Time].[Month and Year].[Month and Year].[Jul 2014]};

Claro, isso também me deu o resultado por um único mês, não todos os meses.

Quando percebi que a consulta MDX não funcionaria, comecei alterando nossa factStatstabela para incluir uma nova coluna para sinalizar os itens nas 10 principais e no valor acumulado.

alter table factStats
    add Top10ParentID INT NOT NULL
    constraint DF_factStats default (0);

A restrição padrão faz referência ao nosso valor "Agrupado" para os 10 principais.

Tentativa 1: Criei uma nova tabela das 10 principais para armazenar o ParentID, o nome e o Rank:

create table dimTop10Parent
(
    Top10ParentID INT NOT NULL PRIMARY KEY,
    ParentName varchar(100) NOT NULL,
    Parent_Rank INT NOT NULL
);

Essa tabela será preenchida sempre que atualizarmos nosso modelo com os novos 10 principais pais com base no total de itens ativos que eles possuem. A Parent_Rankcoluna é ocultada em nosso modelo de tabela e usada exclusivamente para classificação. Isso funciona muito bem, exceto que não temos a capacidade de obter historicamente o Top 10, pois ele não se baseia mês a mês.

Tentativa 2: Crie uma nova tabela para armazenar os 10 principais, mas a PRIMARY KEY incluirá o Top10ParentID e um BillingDateTimeID.

create table dimTop10Parent
(
    Top10ParentID INT NOT NULL,
    ParentName varchar(100) NOT NULL,
    Parent_Rank INT NOT NULL,
    BillDateTimeID BIGINT NOT NULL
);

O problema é que não podemos criar um relacionamento entre o FK único factStats e o PK de duas partes no dimTop10Parent no modelo tabular.

Tentativa 3: crie a nova tabela, mas use uma identidade como PK.

create table dimTop10Parent
(
    Top10ID INT IDENTITY NOT NULL PRIMARY KEY,
    Top10ParentID INT NOT NULL,
    ParentName varchar(100) NOT NULL,
    Parent_Rank INT NOT NULL,
    BillDateTimeID BIGINT NOT NULL
);

A factStatstabela armazenará o Top10IDvalor que será exclusivo para cada linha. Eu pensei que isso resolveria o meu problema, mas, não foi porque não podemos mais classificar pelo Parent_Rankmodelo, gera um erro:

Não é possível classificar ParentName por Parent_Rank porque pelo menos um valor em ParentName possui vários valores distintos em Parent_Rank. Por exemplo, você pode classificar [Cidade] por [Região] porque existe apenas uma região para cada cidade, mas não pode classificar [Região] por [Cidade] porque existem várias cidades para cada região.

Usando os dados da amostra, o resultado final deve ser semelhante ao (mostrando os 2 principais com um terceiro acumulado):

| PARENTNAME | BILLDATETIMEID | TOTALACTIVE | PR |
|------------|----------------|-------------|----|
|     FDN    |   201408010000 |          11 |  1 |
|     FDO    |   201408010000 |           3 |  2 |
| All Others |   201408010000 |           5 |  3 |
|     FDN    |   201407010000 |          12 |  1 |
|     EVOD   |   201407010000 |           2 |  2 |
| All Others |   201407010000 |           5 |  3 |

Neste ponto, estou sem saber como obter esse resultado final. Posso alterar as tabelas conforme necessário para obtê-lo, posso alterar o modelo usando uma fórmula, medida, etc. Li sobre classificação usando as fórmulas DAX 1 , 2 , 3 , mas não consigo entender eles o suficiente para obter o resultado com precisão.

Como posso calcular / armazenar este Top 10 para qualquer mês e ainda assim poder juntar os dados conforme necessário em nosso modelo de tabela?

Taryn
fonte

Respostas:

1

Eu tive um cenário semelhante e usei a seguinte consulta DAX ...

Primeiro, para simplificar, defini uma medida a ser usada dentro do DAX para não precisar repetir a fórmula. Então eu usei o generate para iterar na fórmula TOPN:

define measure TableInTabular[NameOfTheMeasure] = COUNTAX(FILTER('Stats', ISBLANK([DeactDate]) = TRUE), 1)
evaluate
 (
  addcolumns
   (  
    filter
     (  
      generate
        (  
         VALUES(DatesTableName[Month]),  
         TOPN (10, VALUES(TableInTabular[ParentID]),TableInTabular[NameOfTheMeasure],0)
        ),
        TableInTabular[NameOfTheMeasure]>0
      ),
      "ActiveCount (or how you want to call this Column)",
      TableInTabular[NameOfTheMeasure]  
    )  
 )  
order by DatesTableName[Month] asc, 
TableInTabular[NameOfTheMeasure] desc

Com o exposto acima, você deve ter um dos 10 principais pais e a medida por mês. basta substituir o "TableInTabular" pelo nome da tabela tabular em que você possui os dados e o "DatesTableName" pelo nome da tabela de datas.

Informe-me se eu entendi mal sua pergunta e espero que ajude ...

Alejandro Pelc
fonte
1
Obrigado pela resposta, ainda existem alguns problemas com isso. Primeiro, eu posso usar isso dentro do SSMS, mas isso está sendo implantado em nosso modelo de tabela para que nossos usuários possam acessá-lo via PowerView - eles não farão nenhuma consulta - isso só precisa estar disponível. Segundo, a menos que eu esteja fazendo algo errado, não há avaliação ou ordem permitida no modelo tabular via Visual Studio - nenhuma opção para isso como uma função. Terceiro, essa consulta retorna apenas os 10 principais, também preciso dos dados acumulados ou de alguma maneira de obtê-los. Vou continuar a brincar com isso embora.
Taryn