PostgreSQL JOIN實踐及原理

                                           PostgreSQL JOIN實踐及原理

最近項目使用了PostgreSQL 簡單學習join語法以及原理,以後有時間搞一下SQLite源碼。

PostgreSQL  JOIN 子句用於把來自兩個或多個表的行結合起來,基於這些表之間的共同字段。

在 PostgreSQL 中,JOIN 有五種連接類型:

CROSS JOIN :交叉連接

INNER JOIN:內連接

LEFT OUTER JOIN:左外連接

RIGHT OUTER JOIN:右外連接

FULL OUTER JOIN:全外連接

1.數據準備

創建company表和department表 其中company表存儲員工基本信息 department表存儲部門信息。

company表定義以及初始化數據如下:

DROP TABLE COMPANY;

CREATE TABLE COMPANY(

   ID INT PRIMARY KEY     NOT NULL,

   NAME           TEXT    NOT NULL,

   AGE            INT     NOT NULL,

   ADDRESS        CHAR(50),

   SALARY         REAL

);

INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1, 'Paul', 32, 'California', 20000.00 );

INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2, 'Allen', 25, 'Texas', 15000.00 );

INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );

INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );

INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (5, 'David', 27, 'Texas', 85000.00 );

INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (6, 'Kim', 22, 'South-Hall', 45000.00 );

INSERT INTO COMPANY VALUES (7, 'James', 24, 'Houston', 10000.00 );

INSERT INTO COMPANY VALUES (8, 'Paul', 24, 'Houston', 20000.00);

INSERT INTO COMPANY VALUES (9, 'James', 44, 'Norway', 5000.00);

INSERT INTO COMPANY VALUES (10, 'James', 45, 'Texas', 5000.00);

department表定義以及初始化如下:

CREATE TABLE DEPARTMENT(

   ID INT PRIMARY KEY      NOT NULL,

   DEPT           CHAR(50) NOT NULL,

   EMP_ID         INT      NOT NULL

);

INSERT INTO DEPARTMENT (ID, DEPT, EMP_ID) VALUES (1, 'IT Billing', 1 );

INSERT INTO DEPARTMENT (ID, DEPT, EMP_ID) VALUES (2, 'Engineering', 2 );

INSERT INTO DEPARTMENT (ID, DEPT, EMP_ID) VALUES (3, 'Finance', 7 );

2.連接操作

2.1 交叉連接

交叉連接(CROSS JOIN)把第一個表的每一行與第二個表的每一行進行匹配。如果兩個輸入表分別有 x y 行,則結果表有 x*y 行。

SELECT EMP_ID, NAME, DEPT FROM COMPANY CROSS JOIN DEPARTMENT;

其相應查詢計劃如下所示:

2.2 內連接

內連接(INNER JOIN)根據連接謂詞結合兩個表(table1 和 table2)的列值來創建一個新的結果表。查詢會把 table1 中的每一行與 table2 中的每一行進行比較,找到所有滿足連接謂詞的行的匹配對。當滿足連接謂詞時,A 和 B 行的每個匹配對的列值會合併成一個結果行。內連接(INNER JOIN)是最常見的連接類型,是默認的連接類型。INNER 關鍵字是可選的。

SELECT EMP_ID, NAME, DEPT FROM COMPANY INNER JOIN DEPARTMENT ON COMPANY.ID = DEPARTMENT.EMP_ID;

2.3 左外連接

對於左外連接,首先執行一個內連接。然後,對於表 T1 中不滿足表 T2 中連接條件的每一行,其中 T2 的列中有 null 值也會添加一個連接行。因此,連接的表在 T1 中每一行至少有一行。

     

2.4 右外連接

首先,執行內部連接。然後,對於表T2中不滿足表T1中連接條件的每一行,其中T1列中的值爲空也會添加一個連接行。這與左聯接相反;對於T2中的每一行,結果表總是有一行。

  

2.5 外連接

首先,執行內部連接。然後,對於表 T1 中不滿足表 T2 中任何行連接條件的每一行,如果 T2 的列中有 null 值也會添加一個到結果中。此外,對於 T2 中不滿足與 T1 中的任何行連接條件的每一行,將會添加 T1 列中包含 null 值的到結果中。

由以上操作可知  大多數連接會使用Hash Join算法來實現。postgreSQL中join算法有三種nested loop join merge join 以及hash join

3.連接原理

3.1 nested loop join

nested loop join: The right relation is scanned once for every row found in the left relation. This strategy is easy to implement but can be very time consuming. (However, if the right relation can be scanned with an index scan, this can be a good strategy. It is possible to use values from the current row of the left relation as keys for the index scan of the right.)

EXPLAIN SELECT * FROM COMPANY JOIN DEPARTMENT ON DEPARTMENT.EMP_ID = COMPANY.ID WHERE company."id" = 1;

1.     表company按照id來過濾得到結果

2.     對於過濾後結果每一行,利用id從department表中進行匹配

3.2 merge join

merge join: Each relation is sorted on the join attributes before the join starts. Then the two relations are scanned in parallel, and matching rows are combined to form join rows. This kind of join is more attractive because each relation has to be scanned only once. The required sorting might be achieved either by an explicit sort step, or by scanning the relation in the proper order using an index on the join key.

set enable_hashjoin=off; 

EXPLAIN SELECT * FROM COMPANY JOIN DEPARTMENT ON DEPARTMENT.emp_id = COMPANY.ID;

  1. 首先company表關聯字段id是有序的 直接索引掃描
  2. Deparpment 首先按照emp_id排序  然後執行merge join

3.3 hash join

hash join: the right relation is first scanned and loaded into a hash table, using its join attributes as hash keys. Next the left relation is scanned and the appropriate values of every row found are used as hash keys to locate the matching rows in the table.

set enable_hashjoin=on; 

EXPLAIN SELECT * FROM COMPANY JOIN DEPARTMENT ON DEPARTMENT.emp_id = COMPANY.ID WHERE company."id" = 1;

EXPLAIN SELECT * FROM COMPANY JOIN DEPARTMENT ON DEPARTMENT.emp_id = COMPANY.ID WHERE company.age = 32;

首先順序掃描department表 構建hash表 key=department.emp_id 即關聯字段,

然後順序掃描company表  用company表Id來匹配hash表key 如果匹配成功 則輸出。

hash join和merge join被關聯的兩個表都只掃描一次, nested loop join則被關聯的表其中一個掃描一次, (如果前一個表的掃描結果有多行輸出)另一個掃描多次.

HASH JOIN原理

參考一下hash join實現源碼:

將主驅動表的關聯字段作爲key,主驅動表需要的字段作爲value來構建hash表。

遍歷被驅動表的每一行 計算該行是否與hash表中key相同 如果key相同則將被驅動表相應字段和命中hash表key對應的value一起輸出,作爲結果中的一行。由於hash表的使用,被驅動表的每一行查找時間複雜度爲常數。

for (j = 0; j < length(inner); j++)

  hash_key = hash(inner[j]);

  append(hash_store[hash_key], inner[j]);

for (i = 0; i < length(outer); i++)

  hash_key = hash(outer[i]);

  for (j = 0; j < length(hash_store[hash_key]); j++)

    if (outer[i] == hash_store[hash_key][j])

      output(outer[i], inner[j]);

解釋如下:

//利用 inner 表, 來構造 hash 表(放在內存裏)           

for (j = 0; j < length(inner); j++)           

{           

    hash_key = hash(inner[j]);       

    append(hash_store[hash_key], inner[j]);       

}                      

//對 outer 表的每一個元素, 進行遍歷           

for (i = 0; i < length(outer); i++)           

{           

    //拿到 outer 表中的  某個元素, 進行 hash運算, 得到其 hash_key 值       

    hash_key = hash(outer[i]);          

    //用上面剛得到的 hash_key值, 來 對 hash 表進行 探測(假定hash表中有此key 值)       

    //採用 length (hash_store[hash_Key])  是因爲,hash算法構造完hash 表後,有可能出現一個key值處有多個元素的情況。           

    //對 擁有相同 的 (此處是上面剛運算的,特定的)hash_key 值的各個元素的遍歷       

    for (j = 0; j < length(hash_store[hash_key]); j++)       

    {       

        //如果找到了匹配值,則輸出一行結果   

        if (outer[i] == hash_store[hash_key][j])   

            output(outer[i], inner[j]);

    }       

}

參考鏈接:

https://www.runoob.com/postgresql/postgresql-join.html

https://www.cnblogs.com/gaojian/archive/2012/11/08/2759874.html

https://github.com/digoal/blog/blob/master/201205/20120521_02.md?spm=a2c4e.10696291.0.0.70fe19a4Qlj7as&file=20120521_02.md

https://www.postgresql.org/docs/devel/planner-optimizer.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章