第37期:JOIN延伸 - 維度查詢語法
有了前面文章里的維度定義后,我們就可以來梳理前面講過的簡化JOIN語法了。
先定義字段維度:
- 維度字段的維度為其本身;
- 外鍵字段的維度為相應外鍵表中關聯字段的維度;
- 測度字段沒有維度;
這是個遞歸定義。
一
然后再嚴格定義同維表和主子表:
同維表:兩個表的主鍵字段維度集合對應相同,則稱兩個表同維;
主子表:某個表的主鍵字段維度集合是另一個表的主鍵字段維度集合的真子集,則前者稱為后者的主表,后者為前者的子表;
按這個定義,容易得到這些結論:
- 同維表的同維表是同維表,同維表是等價關系;
- 主表的同維表是主表,子表的同維表是子表;子表的子表是子表;
二
還要定義表的廣義字段:
- 本表的字段是其廣義字段;
- 廣義字段作為普通字段的所在表的同維表的字段是廣義字段;
- 某廣義字段是外鍵字段時,那么它對應的外鍵表的字段是廣義字段;
- 廣義字段的維函數是廣義字段;
這還是個遞歸定義。
回顧前面的例子來理解:
- SELECT * FROM employee WHERE nationality='美國' AND department.manager.nationality='中國'
- SELECT id,name,salary+allowance FROM employee
其中department.manager.nationality,salary,allowance都是表employee的廣義字段。
三
有了廣義字段概念后,前面所說的消除關聯的語法就是很自然的事了。在SQL語法中允許將表的廣義字段當作普通字段引用,就可以實現外鍵屬性化和同維表等同化,再允許將子表的廣義字段作為集合字段在本表運算時進行聚合運算,就實現了主子表一體化。結合前面文章中的例子很容易理解。
這種改進的語法以維度概念作為核心 ,為和SQL區別,我們把它稱為DQL(D是Dimension)。
四
我們再來解決維度對齊中的那個小漏洞,考查前面文章中的例子:
- SELECT Contract.SUM(price), Payment.SUM(amount), Invoice.SUM(amount)
- FROM Contract GROUP BY date FULL JOIN Payment GROUP BY date FULL JOIN Invoice GROUP BY date
這個查詢是想按日期分別統計合同額、回款額及發票額,但選出的字段(表達式)中并沒有作為關鍵字段的日期,而只有一些合計數,這會得到一個讓人看不懂的結果集。
這里參與JOIN的三個表中都有date字段,選任何一個放在SELECT中都是不合適的,因為任何一個表都可能有日期不全的情況,而且這三個表是完全對稱的關系,也沒有道理讓其中任何一個特殊化。這種情況時,在SQL中大概要寫成
coalesce(Contract.date,Payment.date,Invoice.date)的形式(Oracle語法),有點繁瑣。
當我們從數據庫結構中已經抽取出維度之后,就可以較方便地解決這個問題了。顯然,這幾個date都是有維度的字段,我們把這個維度命名為DATE,那么上面語句可以寫成這樣:
- SELECT Contract.SUM(price), Payment.SUM(amount), Invoice.SUM(amount) ON DATE
- FROM Contract GROUP BY date FULL JOIN Payment GROUP BY date FULL JOIN Invoice GROUP BY date
增加一個ON子句來指明用于對齊的目標維度,這些維度會自動被選出到結果集,并處理空值的情況。
類似地,后一個例子應當寫成:
- SELECT Sales.COUNT(1), Contract.SUM(price) ON AREA
- FROM Sales GROUP BY area FULL JOIN Contract GROUP BY customer.area
用于向維度對齊的字段還可以是廣義字段。
五
另外,在有了維函數概念后,還可以進一步簡化某些查詢。
比如前面那個三表對齊的例子中,我們希望按月而不是按日期統計,當然可以寫成:
- SELECT Contract.SUM(price), Payment.SUM(amount), Invoice.SUM(amount) ON MONTH
- FROM Contract GROUP BY month(date) FULL JOIN Payment GROUP BY month(date) FULL JOIN Invoice GROUP BY month(date)
其中month是一個維函數,以日期為參數,返回MONTH維度取值。
不過,維函數在數據結構設計時就已經定義好了,在明確知道對齊維度時,可以根據用來對齊的字段自動尋找一個合適的維函數來用,這樣上面的句子簡化成不寫維函數也不會有歧義:
- SELECT Contract.SUM(price), Payment.SUM(amount), Invoice.SUM(amount) ON MONTH
- FROM Contract GROUP BY date FULL JOIN Payment GROUP BY date FULL JOIN Invoice GROUP BY date
想改變統計維度的層次時,只要改寫ON的部分即可,GROUP BY部分可以不動。
六
我們知道,在多維分析時為了提高性能常常會做預先匯總,也就是根據分析中可能出現的維度組合事先把測度的統計值計算好保存起來,需要時直接引用而不必再從頭遍歷計算。而把所有組合情況都預先匯總是不大現實的(因為存儲空間過大),一般只能選擇最常用的維度組合。
這樣有兩個問題:
- 若干套匯總數據和一個基礎數據是如何對應的?
- 怎么知道哪些組合是最常用的?
在SQL體系下,如果是針對沒有關聯運算的單表,那么這兩個問題都不是很難處理。基礎數據就是一個單表,匯總數據和這個表的某些維度組合對應;將歷史分析過程記錄下來之后,就可以統計出哪些維度組合最常用,從而指導匯總數據的建設。
但是,如果允許關聯運算,多維分析過程中會拼出帶JOIN的SQL來,這個問題就復雜多了。基于關系代數的JOIN定義,很難描述匯總數據與基礎數據的對應關系,而維度組合也隱藏在SQL的JOIN語法句,很難拆出來當前的查詢到底在針對那些維度進行匯總。
而使用DQL就簡單多了。匯總數據和基礎數據的對應問題,僅僅是把普通字段推廣到廣義字段,邏輯上看仍然是個單表。而DQL語句中能很清晰明了地看出每句查詢是是在針對哪些維度(廣義字段)匯總,這樣就就很容易統計最常用的維度組合。