繼 PowerBI DAX MVC 設(shè)計(jì)模式 導(dǎo)論 引發(fā)了很多會(huì)員伙伴的詢(xún)問(wèn)团滥,希望羅叔給出一個(gè)相對(duì)完整和復(fù)雜的案例來(lái)體會(huì) MVC 架構(gòu)和設(shè)計(jì)模式的作用。
本文將結(jié)合設(shè)計(jì)模式與 MVC 架構(gòu)設(shè)計(jì)演示一個(gè)真實(shí)的案例:競(jìng)爭(zhēng)交叉分析各淀。用戶(hù)任選兩個(gè)對(duì)比實(shí)體,來(lái)看兩個(gè)參與對(duì)比實(shí)體的某種度量值表現(xiàn)诡挂。例如:
- 對(duì)于辦公用品大類(lèi)碎浇,其中的紙張和裝訂機(jī)同時(shí)出現(xiàn)在不同類(lèi)型客戶(hù)的訂單中的概率是怎樣的?
- 對(duì)于辦公用品大類(lèi)璃俗,其中的紙張和裝訂機(jī)出現(xiàn)在不同地區(qū)的銷(xiāo)售是怎樣的奴璃?
- …
效果
為了更加清楚的理解這種對(duì)比,羅叔先和大家一起看看效果:
如上圖所示城豁,其功能包括:
- 分為兩個(gè)對(duì)比項(xiàng)切片器苟穆,且該切片器按照頂部切片器(類(lèi)別)進(jìn)行聯(lián)動(dòng);
- 交叉訂單數(shù),用于顯示同時(shí)滿(mǎn)足左右對(duì)比項(xiàng)交叉(同時(shí)包括)時(shí)的訂單數(shù)雳旅;
- 交叉銷(xiāo)售額按地域跟磨,用于顯示按地域且同時(shí)考慮兩個(gè)對(duì)比項(xiàng)的四種課程模式:
- 僅包括左邊的選擇,不包括右邊的選擇的訂單銷(xiāo)售額攒盈;
- 僅包括右邊的選擇抵拘,不包括左邊的選擇的訂單銷(xiāo)售額;
- 同時(shí)包括左右兩邊的選擇的訂單銷(xiāo)售額沦童;
- 不包括任何一邊的訂單銷(xiāo)售額。
不難看出叹话,本案例是購(gòu)物籃分析的深度增強(qiáng)版偷遗。處于教學(xué)目的,羅叔故意增加了分析的靈活性和動(dòng)態(tài)性驼壶,問(wèn)題是如何實(shí)現(xiàn)上述的分析氏豌?
難點(diǎn)分析
在羅叔給出正確設(shè)計(jì)方案前,我們先一起來(lái)看看其中的難點(diǎn)以及你是否已經(jīng)想到這些:
- 如何構(gòu)建兩個(gè)對(duì)比切片器热凹?雖然數(shù)據(jù)都是產(chǎn)品子類(lèi)別泵喘,但應(yīng)該如何構(gòu)建?
- 構(gòu)建的兩個(gè)切片器是否應(yīng)該與原有模型建立關(guān)系般妙?
- 如果構(gòu)建的兩個(gè)切片器與原有模型沒(méi)有關(guān)系纪铺,那類(lèi)別切片器如何影響這兩個(gè)切片器聯(lián)動(dòng)?
- 如何實(shí)現(xiàn)交叉分析的計(jì)算碟渺?
- 如何實(shí)現(xiàn)四種模式下交叉銷(xiāo)售額的計(jì)算鲜锚?
對(duì)于初學(xué)者,為了讓可視化效果產(chǎn)生聯(lián)動(dòng)苫拍,會(huì)構(gòu)建子類(lèi)別并與數(shù)據(jù)模型進(jìn)行關(guān)聯(lián)芜繁,這是很自然的想法,雖然這個(gè)思路確實(shí)可以實(shí)現(xiàn)最終效果绒极,但這個(gè)思路是錯(cuò)誤的骏令。在真正的復(fù)雜項(xiàng)目中,這種類(lèi)似交叉分析的分析主題可能會(huì)非常多垄提,多到幾十個(gè)頁(yè)面甚至需要上百個(gè)度量值榔袋,如果使用這個(gè)思路,必然會(huì)使得模型變得非常復(fù)雜铡俐。
下面羅叔基于 MVC 架構(gòu)設(shè)計(jì)給出標(biāo)準(zhǔn)的實(shí)現(xiàn)并指出我們應(yīng)該遵守的設(shè)計(jì)思想和設(shè)計(jì)模式摘昌。
非侵入式設(shè)計(jì)
這里正式提出重要的設(shè)計(jì)思想:非侵入式設(shè)計(jì)。羅叔并不記得這個(gè)思路來(lái)自哪里高蜂,在 PowerBI DAX 領(lǐng)域聪黎,該思想由我們首次提出,其內(nèi)涵為:不應(yīng)該為了展現(xiàn)而破壞業(yè)務(wù)數(shù)據(jù)模型。
由于我們整體采用了 MVC 架構(gòu)設(shè)計(jì)稿饰,在導(dǎo)論中我們指出數(shù)據(jù)模型包括:數(shù)據(jù)模型和視圖模型锦秒,由于這里是以分析和展現(xiàn)為目的的崭孤,并沒(méi)有引入任何新的業(yè)務(wù)邏輯歌逢,因此,我們?cè)谕耆挥绊憯?shù)據(jù)模型的前提下完成所有設(shè)計(jì)预麸。
視圖模型
首先給出滿(mǎn)足非侵入式設(shè)計(jì)的視圖模型:
可以看出侣姆,這由三個(gè)游離的表構(gòu)成生真,它們均由 DAX 構(gòu)造,如下:
View.Competitor.LeftItem = VALUES( Model_Product[子類(lèi)別] )
View.Competitor.RightItem = VALUES( Model_Product[子類(lèi)別] )
View.Competitor.Legend =
VAR X = {
( "L1R0" , "LeftOnly" , 1 ),
( "L0R1" , "RightOnly" , 2 ),
( "L1R1" , "Both" , 3 ),
( "L0R0" , "None" , 4 )
}
RETURN SELECTCOLUMNS( X , "ID" , [Value1] , "Name" , [Value2] , "OrderBy" , [Value3] )
這三個(gè)表(或者稱(chēng)列表)與主數(shù)據(jù)模型(或者稱(chēng)業(yè)務(wù)數(shù)據(jù)模型)沒(méi)有任何篩選關(guān)系捺宗,也就不會(huì)影響業(yè)務(wù)模型的計(jì)算或變更柱蟀。
展現(xiàn)邏輯 - 交叉訂單數(shù)計(jì)算
在進(jìn)行圖表展現(xiàn)時(shí),一個(gè)最佳實(shí)踐是:
- 第一步蚜厉,將你希望呈現(xiàn)的最終效果用維度和度量值來(lái)表示长已,其中度量值可以是占位符;
- 第二步昼牛,實(shí)現(xiàn)這個(gè)度量值术瓮。
可視化大概的效果為:
現(xiàn)在給出這個(gè)度量值的 DAX 表達(dá)式:
View.Competior.SharedOrderNumber = // 共同出現(xiàn)的訂單數(shù)
VAR vOrdersFromLeft =
CALCULATETABLE(
VALUES( Model_Order[訂單ID] ) , TREATAS( { SELECTEDVALUE( 'View.Competitor.LeftItem'[子類(lèi)別] ) } , Model_Product[子類(lèi)別] ) )
VAR vOrdersFromRight =
CALCULATETABLE(
VALUES( Model_Order[訂單ID] ) , TREATAS( { SELECTEDVALUE( 'View.Competitor.RightItem'[子類(lèi)別] ) } , Model_Product[子類(lèi)別] ) )
RETURN COUNTROWS( INTERSECT( vOrdersFromLeft , vOrdersFromRight ) )
其思路如下:
- vOrdersFromLeft - 將左側(cè)切片器所選內(nèi)容動(dòng)態(tài)掛載到數(shù)據(jù)模型,以篩選出相應(yīng)的訂單集合贰健;
- vOrdersFromRight - 將右側(cè)切片器所選內(nèi)容動(dòng)態(tài)掛載到數(shù)據(jù)模型胞四,以篩選出相應(yīng)的訂單集合;
- 求上述兩個(gè)集合的交集的行數(shù)即可伶椿;
- 注意撬讽,在這個(gè)過(guò)程數(shù)據(jù)模型始終保持被細(xì)分或行業(yè)篩選。
展現(xiàn)邏輯 - 交叉銷(xiāo)售額的計(jì)算
類(lèi)似地悬垃,不同類(lèi)型的交叉銷(xiāo)售額也需要得到展現(xiàn)時(shí)的計(jì)算游昼,最終效果:
按照展現(xiàn)的最佳實(shí)踐:
- 第一步,將你希望呈現(xiàn)的最終效果用維度和度量值來(lái)表示尝蠕,其中度量值可以是占位符烘豌;
- 第二步,實(shí)現(xiàn)這個(gè)度量值看彼。
這里涉及一個(gè)圖例維度廊佩,如下:
View.Competitor.Legend =
VAR X = {
( "L1R0" , "LeftOnly" , 1 ),
( "L0R1" , "RightOnly" , 2 ),
( "L1R1" , "Both" , 3 ),
( "L0R0" , "None" , 4 )
}
RETURN SELECTCOLUMNS( X , "ID" , [Value1] , "Name" , [Value2] , "OrderBy" , [Value3] )
該圖例給出了四種可能的交叉情況,進(jìn)而繼續(xù)實(shí)現(xiàn)度量值靖榕,如下:
View.Competior.SalesByLegend =
VAR vLeftItem = SELECTEDVALUE( 'View.Competitor.LeftItem'[子類(lèi)別] )
VAR vRightItem = SELECTEDVALUE( 'View.Competitor.RightItem'[子類(lèi)別] )
// 計(jì)算當(dāng)前圖例
VAR vLegendItem = SELECTEDVALUE( 'View.Competitor.Legend'[ID] )
// 左右元素對(duì)應(yīng)的訂單集合
VAR vOrdersFromLeft = CALCULATETABLE( VALUES( Model_Order[訂單ID] ) , TREATAS( { vLeftItem } , Model_Product[子類(lèi)別] ) )
VAR vOrdersFromRight = CALCULATETABLE( VALUES( Model_Order[訂單ID] ) , TREATAS( { vRightItem } , Model_Product[子類(lèi)別] ) )
// 四種交叉的集合可能
VAR vSetL1R0 = EXCEPT( vOrdersFromLeft , vOrdersFromRight )
VAR vSetL0R1 = EXCEPT( vOrdersFromRight , vOrdersFromLeft )
VAR vSetL1R1 = INTERSECT( vOrdersFromLeft , vOrdersFromRight )
VAR vSetL0R0 = EXCEPT( ALL( Model_Order[訂單ID] ) , UNION( vOrdersFromLeft , vOrdersFromRight ) )
// 對(duì)應(yīng)不同圖例标锄,計(jì)算與該圖例一致的交叉銷(xiāo)售額
RETURN SWITCH( TRUE() ,
vLegendItem = "L1R0" , CALCULATE( [KPI.Sales] , vSetL1R0 ) ,
vLegendItem = "L0R1" , CALCULATE( [KPI.Sales] , vSetL0R1 ) ,
vLegendItem = "L1R1" , CALCULATE( [KPI.Sales] , vSetL1R1 ) ,
vLegendItem = "L0R0" , CALCULATE( [KPI.Sales] , vSetL0R0 ) ,
BLANK()
)
對(duì)該 DAX 表達(dá)式的注解參見(jiàn)上述表達(dá)式注釋。
MVC 架構(gòu)設(shè)計(jì)
上述設(shè)計(jì)按照非侵入式設(shè)計(jì)思想構(gòu)建茁计,在構(gòu)建的過(guò)程中料皇,我們始終是在 MVC 框架下進(jìn)行的,我們整理這個(gè)框架,視圖如下:
視圖的展現(xiàn)邏輯:
視圖模型:
我們?cè)倩仡櫼幌?MVC 架構(gòu)的模型如下:
不難看出這里的設(shè)計(jì)完全嚴(yán)格遵守了 MVC 架構(gòu)設(shè)計(jì)践剂,具體說(shuō)來(lái):
- 視圖鬼譬,依賴(lài)于視圖模型與展現(xiàn)度量值;
- 視圖模型逊脯,是從數(shù)據(jù)模型導(dǎo)出的优质,在展現(xiàn)度量值計(jì)算時(shí),動(dòng)態(tài)掛載到數(shù)據(jù)模型以產(chǎn)生篩選效應(yīng)军洼;
- 展現(xiàn)度量值巩螃,完全按照展現(xiàn)效果設(shè)計(jì),將視圖模型與數(shù)據(jù)模型實(shí)現(xiàn)動(dòng)態(tài)掛載匕争。
可以看出避乏,這樣的 MVC 架構(gòu)設(shè)計(jì)與非侵入式設(shè)計(jì)思想融為一體。要實(shí)現(xiàn)非侵入式設(shè)計(jì)汗捡,采用 MVC 架構(gòu)設(shè)計(jì)是通用的思路淑际;而采用 MVC 架構(gòu)設(shè)計(jì)便實(shí)現(xiàn)了非侵入式設(shè)計(jì)畏纲。
數(shù)據(jù)模型與視圖模型的聯(lián)動(dòng)
至此扇住,我們?nèi)匀挥幸粋€(gè)問(wèn)題沒(méi)有給出答案,那就是:
- 子類(lèi)別來(lái)自于孤立的視圖模型表盗胀;
- 類(lèi)別來(lái)自于數(shù)據(jù)模型艘蹋;
- 它們之間沒(méi)有任何關(guān)系是如何實(shí)現(xiàn)聯(lián)動(dòng)的?
這要得益于 PowerBI 最近幾個(gè)月更新所支持的用度量值控制切片器的元素票灰,這樣就具有了動(dòng)態(tài)性女阀。我們構(gòu)造了一個(gè)度量值如下:
View.Competitor.LeftItem.Check =
IF(
CALCULATE(
SELECTEDVALUE( Model_Product[類(lèi)別] ) ,
TREATAS( { SELECTEDVALUE( 'View.Competitor.LeftItem'[子類(lèi)別] ) } , Model_Product[子類(lèi)別] )
) = SELECTEDVALUE( Model_Product[類(lèi)別] ) ,
"有效"
)
并將其置于切片器的篩選中,如下:
這個(gè)有效性由度量值給出屑迂,而該度量值是與數(shù)據(jù)模型動(dòng)態(tài)計(jì)算關(guān)聯(lián)的“橋梁”浸策。
總結(jié)
羅叔正式提出 MVC 架構(gòu)設(shè)計(jì)以及非侵入式設(shè)計(jì)其實(shí)已經(jīng)等候多時(shí),它需要幾個(gè) PowerBI 的構(gòu)件做支撐惹盼,具體包括:
- 度量值可以用文件夾組織庸汗,用于分類(lèi);
- 切片器可以被度量值篩選手报,以實(shí)現(xiàn)視圖模型與數(shù)據(jù)模型的橋接聯(lián)動(dòng)效應(yīng)蚯舱;
- 可視化元素可以被編組以實(shí)現(xiàn)視圖級(jí)可視化元素與展現(xiàn)度量值的對(duì)應(yīng)關(guān)系;
- 模型可以創(chuàng)建新的布局以區(qū)分?jǐn)?shù)據(jù)模型和視圖模型掩蛤;
- DAX 可以驅(qū)動(dòng)更多視覺(jué)元素的可視化以便形成強(qiáng)大的展現(xiàn)計(jì)算能力枉昏。
在 2019 年幾個(gè)月來(lái) PowerBI 的更新后,我們終于迎來(lái)了正式推出這套思想揍鸟,并給出案例以明顯體會(huì)到這種模式的優(yōu)越性兄裂。
本文給出了一個(gè)基于 MVC 架構(gòu)的典型案例,該案例要求復(fù)雜的展現(xiàn)分析,而我們的設(shè)計(jì)不但可以實(shí)現(xiàn)目的懦窘,還完全不影響數(shù)據(jù)模型本身前翎,這便是我們需要的。
值得注意的是:我們?cè)谠O(shè)計(jì)視圖模型時(shí)畅涂,對(duì)維度的命名為:View.Competitor.RightItem港华,這個(gè)命名根本沒(méi)有提及子類(lèi)別,而子類(lèi)別是蘊(yùn)含在其中的午衰,也就是說(shuō)這個(gè)命名是抽象的立宜,我們完全可以繼續(xù)擴(kuò)展這種設(shè)計(jì),以實(shí)現(xiàn)按產(chǎn)品子類(lèi)別分析或者其他實(shí)體(如:產(chǎn)品)來(lái)分析臊岸。也就是這是依賴(lài)于抽象橙数,而不依賴(lài)于具體的,這使得我們的設(shè)計(jì)有最大化的可復(fù)用潛力帅戒。這在設(shè)計(jì)模式中叫做面向接口的設(shè)計(jì)灯帮。我們真正打開(kāi)了 PowerBI DAX 通用設(shè)計(jì)模式的大門(mén),我們會(huì)在后續(xù)的文章中不斷給出通用設(shè)計(jì)模式逻住,以使得我們的 PowerBI 設(shè)計(jì)更加完美钟哥,無(wú)懈可擊。