python實現簡易數據庫之三——join多表連接和group by分組


  上一篇里面我們實現了單表查詢和top N查詢,這一篇我們來講述如何實現多表連接和group by分組。

一、多表連接

  多表連接的時間是數據庫一個非常耗時的操作,因為連接的時間復雜度是M*N(M,N是要連接的表的記錄數),如果不對進行優化,連接的產生的臨時表可能非常大,需要寫入磁盤,分多趟進行處理。

  1、雙表等值join

  我們看這樣一個連接sql:

select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PARTSUPP
where PS_SUPPKEY = S_SUPPKEY and PS_AVAILQTY > 2000and S_NATIONKEY = 1;

  可以把這個sql理解為在SUPPLIER表的S_SUPPKEY屬性和PARTSUPP表的PS_SUPPKEY屬性上作等值連接,並塞選出滿足PS_AVAILQTY > 2000和 S_NATIONKEY = 1的記錄,輸入滿足條件記錄的PS_AVAILQTY,PS_SUPPLYCOST,S_NAME屬性。這樣的理解對我們人來說是很明了的,但數據庫不能照這樣的方式執行,上面的PS_SUPPKEY其實是PARTSUPP的外鍵,兩個表進行等值連接,得到的連接結果是很大的。所以我們應該先從單表查詢條件入手,在單表查詢過濾之后再進行等值連接,這樣需要連接的記錄數會少很多。

  首先根據PS_AVAILQTY > 2000找出滿足條件的PARTSUPP表的記錄行號集A,然后根據S_NATIONKEY = 1找出SUPPLIER表找出相應的記錄行號集B,在記錄集A、B上進行等值連接,看圖很簡單:

  

  依次掃描的時間復雜度為max(m,n),加上折半查找,總的時間復雜度為max(m,n)*(log(m1)+log(n1)),其中m1、n1表示where條件塞選出的記錄數。

  來看一下執行的結果:

Input SQL:
select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PARTSUPP
where PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and S_NATIONKEY = 1;
{'FROM': ['SUPPLIER', 'PARTSUPP'],
 'GROUP': None,
 'ORDER': None,
 'SELECT': [['PARTSUPP.PS_AVAILQTY', None, None],
            ['PARTSUPP.PS_SUPPLYCOST', None, None],
            ['SUPPLIER.S_NAME', None, None]],
 'WHERE': [['PARTSUPP.PS_AVAILQTY', '>', '2000'],
           ['SUPPLIER.S_NATIONKEY', '=', '1'],
           ['PARTSUPP.PS_SUPPKEY', '=', 'SUPPLIER.S_SUPPKEY']]}
Quering: PARTSUPP.PS_AVAILQTY > 2000
Quering: SUPPLIER.S_NATIONKEY = 1
Quering: PARTSUPP.PS_SUPPKEY = SUPPLIER.S_SUPPKEY

Output:
The result hava 26322 rows, here is the fisrt 10 rows:
-------------------------------------------------
rows     PARTSUPP.PS_AVAILQTY PARTSUPP.PS_SUPPLYCOST SUPPLIER.S_NAME
-------------------------------------------------
1             8895       378.49 Supplier#000000003
2             4286       502.00 Supplier#000000003
3             6996       739.71 Supplier#000000003
4             4436       377.80 Supplier#000000003
5             6728       529.58 Supplier#000000003
6             8646       722.34 Supplier#000000003
7             9975       841.19 Supplier#000000003
8             5401       139.06 Supplier#000000003
9             6858       786.94 Supplier#000000003
10             8268       444.21 Supplier#000000003
-------------------------------------------------
Take 26.58 seconds.

  從Quering后面的信息可以看到我們處理where子條件的順序,先處理單表查詢,再處理多表連接。

  2、多表join

  處理完雙表join后,我們看一下怎么實現三個的join,示例sql:

select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PART,PARTSUPP
where PS_PARTKEY = P_PARTKEY
and PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and P_BRAND = 'Brand#12'
and S_NATIONKEY = 1;

  這里進行三個表的連接,三個表連接得到的應該是三個表的記錄合並的結果,那根據where條件選出的記錄行號應當包含三列,每一列是一個表的行號:  

  三個表的連接事實上建立在兩個表連接的基礎上的,先進行兩個表的連接后,得到兩組行號表,再將這兩組行號表合並:

  

  主要代碼如下:

 1 sortJoin(joina,cloumi)#cloumi表示公共表在joina的列號    
 2 sortJoin(joinb,cloumj)#cloumj表示公共表在joinb的列號
 3 i = j = 0#左右指針初試為0
 4 while i < len(joina) and j < len(joinb):
 5     if joina[i][cloumi] < joinb[j][cloumj]:
 6         i += 1
 7     elif joina[i][cloumi] > joinb[j][cloumj]:
 8         j += 1
 9     else:#相等,進行連接
10         lastj = j
11         while j < len(joinb) and joina[i][cloumi] == joinb[j][cloumj]:
12             temp = joina[i] + joinb[j]
13             temp.remove(joina[i][cloumi])#刪掉重復的元素                
14             mergeResult.append(temp)
15             j += 1
16         j = lastj#右指針回滾
17         i += 1

  我們分析一下這個算法的時間復雜度,首先要對兩個表排序,復雜度為O(m1log(m1)),在掃描的過程中,右邊指針會回溯,所以不再是O(max(m1,n1)),我們可以認為是k*O(m1*n1),這個系數k應該是很小的,因為一般右指針不會回溯太遠,總的時間復雜度是O(m1log(m1))+k*O(m1*n1),應該是小於N方的復雜度。

  看一下執行的結果:

Input SQL:
select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PART,PARTSUPP
where PS_PARTKEY = P_PARTKEY
and PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and P_BRAND = 'Brand#12'
and S_NATIONKEY = 1;
{'FROM': ['SUPPLIER', 'PART', 'PARTSUPP'],
 'GROUP': None,
 'ORDER': None,
 'SELECT': [['PARTSUPP.PS_AVAILQTY', None, None],
            ['PARTSUPP.PS_SUPPLYCOST', None, None],
            ['SUPPLIER.S_NAME', None, None]],
 'WHERE': [['PARTSUPP.PS_AVAILQTY', '>', '2000'],
           ['PART.P_BRAND', '=', 'Brand#12'],
           ['SUPPLIER.S_NATIONKEY', '=', '1'],
           ['PARTSUPP.PS_PARTKEY', '=', 'PART.P_PARTKEY'],
           ['PARTSUPP.PS_SUPPKEY', '=', 'SUPPLIER.S_SUPPKEY']]}
Quering: PARTSUPP.PS_AVAILQTY > 2000
Quering: PART.P_BRAND = Brand#12
Quering: SUPPLIER.S_NATIONKEY = 1
Quering: PARTSUPP.PS_PARTKEY = PART.P_PARTKEY
Quering: PARTSUPP.PS_SUPPKEY = SUPPLIER.S_SUPPKEY

Output:
The result hava 1022 rows, here is the fisrt 10 rows:
-------------------------------------------------
rows     PARTSUPP.PS_AVAILQTY PARTSUPP.PS_SUPPLYCOST SUPPLIER.S_NAME
-------------------------------------------------
1             4925       854.19 Supplier#000002515
2             4588       455.04 Supplier#000005202
3             8830       852.13 Supplier#000007814
4             8948       689.89 Supplier#000002821
5             3870       488.38 Supplier#000005059
6             6968       579.03 Supplier#000005660
7             9269       228.31 Supplier#000000950
8             8818       180.32 Supplier#000003453
9             9343       785.01 Supplier#000003495
10             3364       545.25 Supplier#000006030
-------------------------------------------------
Take 50.42 seconds.

  這個查詢的時間比Mysql快了很多,在mysql上運行這個查詢需要10分鍾(建立了索引),想想也是合理的,我們的設計已經大大簡化了,完全不考慮表的修改,犧牲這么的實用性必然能提升在查詢上的效率。

二、group by分組

  在執行完where條件后,讀取原始記錄,然后可以按group by的屬性分組,分組的屬性可能有多條,比如這樣一個查詢:

select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME,COUNT(*)
from SUPPLIER,PART,PARTSUPP
where PS_PARTKEY = P_PARTKEY
and PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and P_BRAND = 'Brand#12'
and S_NATIONKEY = 1;
group by PS_AVAILQTY,PS_SUPPLYCOST,S_NAME;

  按 PS_AVAILQTY,PS_SUPPLYCOST,S_NAME這三個屬性分組,我們實現時使用了一個技巧,將每個候選記錄的這三個字段按字符串格式拼接成一個新的屬性,拼接的示例如下:

"4925" "854.19" "Supplier#000002515" -->> "4925+854.19+Supplier#000002515"

  注意中間加了一個加號“+”,這個加號是必須的,如果沒有加號,"105","201"與"10","5201"的拼接結果都是"105201",這樣得到的group by結果將會出錯,而添加一個加號它們兩的拼接結果是不同的。

  拼接后,我們只需要按新的屬性進行分組,可以使用map來實現,map的key為新的屬性值,value為新屬性值key的后續記錄。再在組上進行聚集函數的運算。

 

  這個小項目就寫到這里了,或許這壓根只是一個數據處理,談不上數據庫實現,不過通過這個小項目我對數據庫底層的實現還是了解了很多,以后做數據庫優化理解起來也容易一些。

  謝謝關注,歡迎評論。   


作者:MyDetail
出處:http://www.cnblogs.com/fengfenggirl/
本文版權歸作者MyDetail和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM