天天看點

資料蔣堂 | 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

增加一個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次元取值。

不過,維函數在資料結構設計時就已經定義好了,在明确知道對齊次元時,可以根據用來對齊的字段自動尋找一個合适的維函數來用,這樣上面的句子簡化成不寫維函數也不會有歧義:

想改變統計次元的層次時,隻要改寫ON的部分即可,GROUP BY部分可以不動。

我們知道,在多元分析時為了提高性能常常會做預先彙總,也就是根據分析中可能出現的次元組合事先把測度的統計值計算好儲存起來,需要時直接引用而不必再從頭周遊計算。而把所有組合情況都預先彙總是不大現實的(因為存儲空間過大),一般隻能選擇最常用的次元組合。

這樣有兩個問題:

若幹套彙總資料和一個基礎資料是如何對應的?

怎麼知道哪些組合是最常用的?

在SQL體系下,如果是針對沒有關聯運算的單表,那麼這兩個問題都不是很難處理。基礎資料就是一個單表,彙總資料和這個表的某些次元組合對應;将曆史分析過程記錄下來之後,就可以統計出哪些次元組合最常用,進而指導彙總資料的建設。

但是,如果允許關聯運算,多元分析過程中會拼出帶JOIN的SQL來,這個問題就複雜多了。基于關系代數的JOIN定義,很難描述彙總資料與基礎資料的對應關系,而次元組合也隐藏在SQL的JOIN文法中,很難拆出來目前的查詢到底在針對哪些次元進行彙總。

而使用DQL就簡單多了。彙總資料和基礎資料的對應問題,僅僅是把普通字段推廣到廣義字段,邏輯上看仍然是個單表。而DQL語句能很清晰明了地看出每句查詢是在針對哪些次元(廣義字段)彙總,這樣就很容易統計最常用的次元組合。

原文釋出時間為:2018-01-06

本文作者:蔣步星