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 TotalActiveItems
uso 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 factStats
tabela tem FKs para o DevID
, CustID
, BillDateTimeID
, e ParentID
. A solicitação que temos é calcular ou armazenar o valor Top 10 Parents
de cada um com BillDateTimeID
base 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;
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 factStats
tabela 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_Rank
coluna é 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 factStats
tabela armazenará o Top10ID
valor 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_Rank
modelo, 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?