今天,我重新檢查了一個困擾已久的問題,在經過仔細的考慮,算是找到了一個比較合理的解決辦法。
問題是這樣的,表StoreProductSales記錄一系列的商場以及它們每天的營業額。商場與商場之間有從屬關係,記錄在Store_WithAcestor表裏。當用戶查詢某個商場的對某個產品的銷售額時,除了返回自己的營業額,還需要包含所有子商場的營業額。
示例結構和數據如下:
StoreProductSales
(
DayId int,
StoreId nvarchar(8),
ProductId nvarchar(8),
Sales int
)
Insert into StoreProductSales values(1,'BJ','Cloth',100)
Insert into StoreProductSales values(1,'BJ','Shoe',100)
Insert into StoreProductSales values(1,'BJ-HaiDian','Cloth',50)
表Store_WithAncestor存放有
Parent, Child
BJ, BJ
BJ, BJ-HaiDian
BJ_HaiDian, BJ_Haidian
用戶輸入某個Store的ID,開始DayId和結束DayId,輸出該Store在這段時間裏所有賣出的Product及其銷售量。
最簡單的方法是從Store_WithAncestor中選出需要store所有子store,如果用戶選擇BJ,那麼就把BJ,BJ-HaiDian都選出來,然後把所有這些商場上成交的產品銷量疊加起來;
這樣做是很慢的。不妨簡單的計算一下,如果某個商場有100個子商場,共計10000個商品,那麼爲了求出該商場的每個產品的日銷量,需要做1百萬次加法運算。同時應該考慮到數據庫的內存掃描工作。假設數據按照store做聚集索引,那麼對於每個商場,需要額外讀取1百萬的頁面。
怎樣才能使它快一點呢?
很容易想到的是如果我們能生成一個聚合表,在該表中存放的sales就是商場本身的銷售量加上所有孩子的銷售量。這樣查詢的時候,既不需要做online的計算,也不需要執行額外的IO操作。
但是這樣做有一個問題。
那就是您不能保證每個商品在每個商場每天都有銷量。
假設這樣的情形。在某天,cloth在BJ_haidian銷售了100,而在BJ 沒有銷售。那麼在上述的聚合表裏是不會出現BJ的記錄的,因爲它只存放自己和孩子的銷售量之和。當用戶查詢那天BJ的銷售額時,由於不查詢它的孩子,cloth的銷量是0。
顯然這是一個錯誤。
當然如果原始表中有一條記錄,BJ, cloth,0,那一切都很好。但是你不可能那麼做。假如有1000個store,10000個商品,在表中每天加入1千萬行只是爲了佔位置,看起來是不可理喻的。
那麼還有沒有別的辦法呢?
我的方法是在源表的基礎上加一個列withParent,它表明在那一天的銷售記錄中,對於本產品是否有父輩的商場也出售了。例如,在20090601這一天,BJ_haidian和bj都賣出了cloth,那麼這一行的withParent對應爲0,如果BJ沒有賣出,則爲1.
當用戶查詢BJ的銷售記錄時,我任然對它進行擴展,但我並不需要加載所有子節點對應的行,因爲withParent就在索引裏,我們只需要掃描索引節點就可以判斷是否需要加載這一行。因此我們不但可以減少加載所需的IO操作,還減少了額外的加法操作。
實際證明,該方法使得執行時間縮短了十分之一。