近來在工作上遇到一件事情。我有一張用戶訂單表,這個訂單表有一個order_id,是唯一約束。同時有一張訂單流程表,和訂單表以ser_id關聯,一個ser_id至少對應一條訂單流程記錄。現在我要將兩個表匯總,成為一張表,以ser_id為唯一約束,其中一個字段來自流程表,這個字段是ser_id對應的幾條工作流程記錄中work_id最大的。
大致上訂單表示這樣的:
order_id ser_id ......
112333 100001
122112 100001
122882 100211
......
而工作流程表是這樣的:
work_id ser_id ......
91188 100001
91198 100001
91108 100001
91223 100221
......
最開始我的想法是使用rank() over,於是我寫了這樣一段:
SELECT
A.*,
B.I_NAME
FROM ORDER_T A,
(SELECT X.*,
RANK() OVER(PARTITION BY ser_id ORDER BY X.ser_id) RK) B
WHERE A.SER_ID = B.SER_ID(+)
AND B.RK = 1;
最開始的時候我以為這個是對的,但是后來發現這是個很白痴的SQL。為什么白痴下面慢慢講。
下面是舉例說明,有兩張表,i_test作為訂單表,duibibiao作為工作項表,直接上圖:
圖1
圖2
我理想的查詢結果是這樣的:
圖3
因為我的目的是做一張中間表,這個中間表是我最后用來統計我們這個月接了多少單子的。按照我上面的SQL,執行出來的結果是這樣的:
當時我還有點懵懂,不知道為什么會出現這種情況,不僅僅是100001對應的項多了那么多,而且還有幾條記錄不見了。后來我才發現,B.RK=1這一條就很白痴,因為之前做的乘積中,做了連接,這樣的話形成的臨時表中就會有一列的RK有的有數有的沒有數,這樣的話會把需要的數據過濾掉。我做連接查詢的目的就是保留所有的訂單表記錄,因為最后我是要count這個表,然后告訴老板這個月我們接收了多少訂單的。 order by的那個地方也很白痴,因為會有很多ser_id是重復的,因此很多個RK都是等於1的,這樣下來挑不出來單個的一條記錄,你怎么排序都不行。
於是我把SQL改成了這樣:
SELECT A.SER_ID, B.I_NAME
FROM I_TEST A,
(SELECT *
FROM (SELECT X.WORK_ID,
X.SER_ID,
X.I_NAME,
RANK() OVER(PARTITION BY X.SER_ID ORDER BY X.ROWID) RK
FROM DUIBIBIAO X) Y
WHERE Y.RK = 1) B
WHERE A.SER_ID = B.SER_ID(+)
坑爹的正確結果終於出來了。這里為了解決上面的那個問題,我用到了偽列ROWID,這個可沒有重復的,不管正序倒序,都能找到一條記錄。
后來我上網的時候發現了一個有意思的東西,ROW_NUMBER() OVER。這個的好處就是不會產生序列號相同的情況。SQL如下:
SELECT A.SER_ID, B.I_NAME
FROM I_TEST A,
(SELECT *
FROM (SELECT X.WORK_ID,
X.SER_ID,
X.I_NAME,
row_number() OVER(PARTITION BY X.SER_ID ORDER BY X.ser_id) RK
FROM DUIBIBIAO X) Y
WHERE Y.RK = 1) B
WHERE A.SER_ID = B.SER_ID(+)
得到的結果也完全正確。現在我就想比比誰的效率更高點。因為RANK() OVER的速度實在是......
祭出一個非常大的表,叫做test,有10000000+數據。這個表有兩個字段,ser_id和area_id,語句如下:
SELECT A.*,
ROW_NUMBER() OVER(PARTITION BY A.AREA_ID ORDER BY A.SER_ID DESC) RK
FROM TEST A;
SELECT a.*,
RANK() OVER(PARTITION BY a.area_id ORDER BY a.ser_id DESC) rk FROM TEST a;
對比一下效率:
沒有看到任何區別,都是一樣的慢。
總結一下,不管是RANK還是ROW_NUMBER,都是非常好用的分析函數。我這里只是用到了很簡單的一部分,並且學習到了兩個函數的通用之處。但是不管怎么樣,只要你的表夠大,做一次分析是不可避免的會出現超級慢的情況,這種情況下除了拼機器的性能之外,如果邏輯正確,可以對要開窗分析的表進行一些限制,這樣的話會降低很大的成本。比如說我上面的SQL,如果我在業務中只是需要西安的數據,那么我可以在其中添加where t.area_id = 290。我試驗了一下,ROWS變成了5660K,BYTES變成了140M,TEMPSPC變成了195M,CPU COST變成了49918。
歡迎大家批評指正。歡迎留言。