1. 程式人生 > >SQL學習十、聯結表

SQL學習十、聯結表

SQL最強大的功能之一就是能在資料查詢的執行中聯結(join)表。

相關概念

1、關係表

理解關係表,最好可以通過一個例子。

比如我們有這樣一張水果訂單表oderlist,表中每一行是一個水果訂單,裡面包含(主鍵id、水果名、數量、單價、訂單號、使用者id、使用者名稱稱、訂單時間、水果供應商、供應商地址、供應商電話、供應商郵箱、供應商聯絡人)。

水果訂單表oderlist
通過表中的資料我們可以看出同一個供應商供應了多個訂單,這樣儲存會產生資料冗餘,且不易儲存和管理,需要思考將訂單資訊和供應商資訊分開儲存。

不足:

  • 同一供應商對應的每個訂單,其供應商資訊都是相同的,對每個訂單重複此資訊既浪費時間又浪費儲存空間
  • 如果供應商資訊發生變化,例如供應商遷址或電話號碼變動,需要對該供應商對應的每個訂單都修改一次
  • 每個訂單都儲存供應商資訊,則很難保證每次輸入該資料的方式都相同,不一致的資料在報表中就很難利用

這裡就涉及到一個關係資料庫設計的基礎 ---- 相同的資料出現多次決不是一件好事。
關係表的設計就是要把資訊分解成多個表,一類資料一個表。各表通過某些共同的值互相關聯(所以才叫關係資料庫)。

所以上表的資料我們可以這樣儲存:
建立兩個表,一個儲存供應商資訊,一個儲存訂單資訊。

供應商表 supplier_new 中儲存供應商資訊,包含(主鍵id、水果供應商、供應商地址、供應商電話、供應商郵箱、供應商聯絡人)

新的水果訂單表 orderlist_new 中只儲存訂單資訊,包含(主鍵id、水果名、數量、單價、訂單號、使用者id、使用者名稱稱、訂單時間、水果供應商ID),除了儲存水果供應商 ID(supplier_new 表的主鍵) 外,它不儲存其他有關供應商的資訊,supplier_new 表的主鍵將 supplier_new 表 與 orderlist_new 表關聯,利用供應商 ID能從 supplier_new 表中找出相應供應 商的詳細資訊。

新的水果訂單表orderlist_new

供應商表supplier_new

這樣做的好處:

  • 供應商資訊不重複,不會浪費時間和空間
  • 如果供應商資訊變動,可以只更新 supplier_new 表中的單個記錄,相關表 中的資料不用改動
  • 由於資料不重複,資料顯然是一致的,使得處理資料和生成報表更簡單

總之,關係資料可以有效地儲存,方便地處理資料。

2、可伸縮

能夠適應不斷增加的工作量而不失敗。

設計良好的資料庫或應用程式可以根據需求的變化不斷的調整,且不影響查詢或使用效率,不造成資料/程式碼冗餘,稱為可伸縮性好。

3、聯結

就像上面提到的,在關係資料庫中我們將資料儲存到不同的表中,那麼我們怎麼查詢多張表中的資料呢?
答案就是使用聯結。
簡單說,聯結是一種機制,用來在一條 SELECT 語句 中關聯表,因此稱為聯結。
使用特殊的語法,可以聯結多個表返回一組輸出。
聯結不是物理實體,它只在查詢執行期間存在。

4、 笛卡兒積

由沒有聯結條件的表關係返回的結果為笛卡兒積。檢索出的行的數目 將是第一個表中的行數乘以第二個表中的行數。

5、叉聯結

有時,返回笛卡兒積的聯結,也稱叉聯結(cross join)。

建立聯結

1、where子句聯結

例如,我們想查詢每個訂單對應的供應商,對應欄位分別在兩個表中 ,我們可以這樣寫:

select orderNo, supplier
from supplier_new , oderlist_new
where oderlist_new.supplierId = supplier_new.id

或者這樣寫:

select o.orderNO, s.supplier
from supplier_new as s , oderlist_new as o
where s.id = o.supplierId

查詢結果

SQL分析

  • SELECT語句指定要檢索的列,orderNo–訂單編號,supplier–供應商,與之前不同的是這次檢索的兩列分別位於兩張表中,orderNo 在oderlist_new表中,supplier 在supplier_new 表中;
  • FROM 子句,與之前不同的是這次的檢索是兩張表,所以FROM後面列出了兩個表supplier_new , oderlist_new,它們就是這條 SELECT 語句 聯結的兩個表的名字;
  • WHERE 子句,這兩個表用 WHERE 子句正確地聯結,WHERE 子句 指示DBMS將 oderlist_new表中的 supplierId 與 supplier_new表中的 id匹配起來。

在一條 SELECT 語句中聯結幾個表時,相應的關係是在執行中構造的。在資料庫表的定義中沒有指示 DBMS如何對錶進行聯結的內容。

在聯結兩個表時,實際要做的是將 第一個表中的每一行與第二個表中的每一行配對,WHERE 子句作為過濾 條件,只包含那些匹配給定條件(這裡是聯結條件)的行。
沒有 WHERE 子句,第一個表中的每一行將與第二個表中的每一行配對,而不管它們 邏輯上是否能配在一起(上述查詢去掉WHERE 子句會查出36條資料,笛卡兒積。)。

去掉WHERE 子句,笛卡兒積

重要:

  • 要保證所有聯結都有 WHERE 子句,否則 DBMS將返回比想要的資料多得多的資料
  • 要保證 WHERE 子句的正確性。不正確的過濾條件 會導致 DBMS返回不正確的資料

2、內聯結(inner join)

上面使用where 子句進行的聯結稱為等值聯結(equijoin),它基於兩個表之間的相等測試,也稱為內聯結(inner join)

示意圖

上面使用where 子句進行表的聯結,使用的是簡單的等值語法,標準格式應該是使用 **INNER JOIN (內聯結)**語法,如下:

select orderNo, supplier
from supplier_new inner join oderlist_new
on oderlist_new.supplierId = supplier_new.id

不同:

  • FROM 子句,兩個表之間的關係是以 INNER JOIN 指定的部分 FROM 子句(聯結的表)
  • ON 子句,聯結條件用特定的 ON 子句而不是 WHERE 子句給出

注意:不要聯結過多的表
SQL 不限制一條 SELECT 語句中可以聯結的表的數目,但實際上許多 DBMS 都有限制,而且不要聯結不必要的表。聯結的表越多,效能下降越厲害。 因為 DBMS 在執行時要關聯指定的每個表,以處理聯結,而這種處理可能非常耗費資源。

3、自聯結(self-join)

例如,我們需要查詢和訂單號20180831013同一供應商的所有訂單資訊 (兩次使用到同一張表),我們可以這樣寫:

  • 使用子查詢
    我們先在訂單表oderlist_new中查出orderNo = '20180831013'訂單的供應商Id(supplierId),
    然後在訂單表oderlist_new中查出相同供應商Id(supplierId)的所有訂單。
select * 
from oderlist_new 
where supplierId = (
	select supplierId 
	from oderlist_new
	where orderNo = '20180831013'
)

查詢結果

  • 使用聯結(自聯結)
    下面的SQL中使用了o1.*,如果直接查詢*的話,會將oderlist_new 查詢兩遍,即每行的欄位從id到supplierId都會展示兩遍,因為直接查詢*,表示查詢from後面關聯的所有表的所有欄位,所以下面的SQL會展示o1,o2兩個表的所有欄位,但其實我們o1,o2都是查詢的oderlist_new 這個表,所以每行的資料都會重複一遍。
    故,我們在使用聯結的時候需要明確指定返回的資料
select o1.* 
from oderlist_new o1, oderlist_new o2
where o2.orderNo = '20180831013' and o1.supplierId = o2.supplierId

查詢結果

select * 
from oderlist_new o1, oderlist_new o2
where o2.orderNo = '20180831013' and o1.supplierId = o2.supplierId

查詢結果

4、外聯結(OUTER JOIN)

在表聯結中,包含了那些在相關表(聯結的表)中除關聯行(關聯關係沒有關聯到的行)之外的行,這種聯結 稱為外聯結。
外聯接可以是左外聯結(left join)、右外聯結(right join)或 全外聯結(full join)

5、左外聯結(left join)

LEFT JOIN 從左表(table1)返回所有的行,即使右表(table2)中沒有匹配。如果右表中沒有匹配,則結果為 NULL。

白話解釋:左外聯結就是取左表的所有行,去匹配右表中符合條件的資料,有就取出來完善資料,沒有就置為NULL

例如,我們用左外聯結(left join)來查詢訂單表中每個訂單對應的供應商

首先,我們來看下訂單表(oderlist_new )供應商表(supplier_new )中的 資料

訂單表(oderlist_new )
訂單表

供應商表(supplier_new )
供應商

使用SQL查詢:

  • oderlist_newleft joinsupplier_new
select o.orderNO, s.supplier
from oderlist_new o left join supplier_new s 
on s.id = o.supplierId

查詢結果

  • supplier_newleft joinoderlist_new
select o.orderNO, s.supplier
from supplier_new s left join oderlist_new o
on s.id = o.supplierId

查詢結果

SQL分析
可以看到在from 子句中調換 **左外聯結(left join)**左右表之後查詢到的結果是不一樣的,
這樣我們就可以理解上面對 左外聯結(left join) 的解釋了。

  • oderlist_newleft joinsupplier_new訂單表(oderlist_new )供應商表(supplier_new )進行左外聯結,訂單表(oderlist_new )在左邊,所以從訂單表(oderlist_new )中取全部記錄(如上,共18條),然後去供應商表(supplier_new )中找符合on條件的對應行,故查詢出18條資料

  • supplier_newleft joinoderlist_new供應商表(supplier_new )訂單表(oderlist_new )進行左外聯結,供應商表(supplier_new )在左邊,所以從供應商表(supplier_new )中取全部記錄(如上,共3條),然後去訂單表(oderlist_new )中找符合on條件的對應行,故查詢出了19條資料,且最後一條資料是沒有訂單號的(訂單表(oderlist_new )中沒有杭州有機蔬菜專供經銷商對應的訂單)

所以,我們就知道了 左外聯結(left join),就是取左邊表的全部資料,然後右邊表中找符合條件的資料。

6、右外聯結(right join)

RIGHT JOIN 關鍵字從右表(table2)返回所有的行,即使左表(table1)中沒有匹配。如果左表中沒有匹配,則結果為 NULL。

改變聯結表的順序, 左外聯結(left join)右外聯結(right join)可以等效替換。

7、全外聯結(full join)

FULL OUTER JOIN 關鍵字只要左表(table1)和右表(table2)其中一個表中存在匹配,則返回行。

與左外聯結或右外聯結包含一個表 的不關聯的行不同,全外聯結包含兩個表的不關聯的行。

注意:

  • 1、Access、MariaDB、MySQL、Open Office Base和 SQLite都不支援 FULL OUTER JOIN 語法。

  • 2、雖然終的結果是相同的,但許多 DBMS處理聯結遠比處理子查詢快得多。至於應該選擇哪種方法,可以試一下兩種方法,以確定哪一種的效能更好。

聯結 和 聚集函式一起使用

我們在聯結中使用聚合函式對多個表進行聚合運算

上一篇中(講子查詢的那一節)我們對比了查詢相同結果,在不同地方使用子查詢的SQL執行效率對比,這裡我們就使用聯結查詢來和子查詢對比一下,看看那個效率更快一點。

和上篇的子查詢的SQL功能一樣,我們同樣是獲取國家內業圖斑中圖斑面積大於10畝的的圖斑分別擁有的附件數量

  • 子查詢
    SQL[0]
//執行1次子查詢
select W.TBBH, count(*) As FJ_AMOUNT
From WYHCFJ As W
where W.TBBH in (
	select SR.TBBH 
	From SURVEY_RECORD AS SR
	Where SR.TBMJ > 10 and SR.TBLX = 'GJNYTB'
) and W.TCBM = 'GJNYTB'
group by W.TBBH

查詢結果

  • 內聯結
    使用內聯結在這份資料中查詢的效率和子查詢有偏差,這也就回到了上面提到的注意事項了,聯結和子查詢的效率問題最好自己進行一下驗證。

SQL[1]WYHCFJ W inner join SURVEY_RECORD SR

select W.TBBH, count(*) As FJ_AMOUNT
from WYHCFJ W inner join SURVEY_RECORD SR
on SR.TBMJ > 10 and SR.TBLX = 'GJNYTB' and W.TCBM = 'GJNYTB' and W.TBBH = SR.TBBH 
group by W.TBBH

查詢結果

SQL[2]SURVEY_RECORD SR inner join WYHCFJ W

可以看到使用內聯結時調換聯結表的位置對查詢出的結果並沒有影響,因為內聯結取的是“交集”

select SR.TBBH, count(*) As FJ_AMOUNT
from SURVEY_RECORD SR inner join WYHCFJ W 
on SR.TBMJ > 10 and SR.TBLX = 'GJNYTB' and W.TCBM = 'GJNYTB' and W.TBBH = SR.TBBH 
group by SR.TBBH

查詢結果

內聯結是查詢兩張表中關聯的資料行,所以查出來的結果是一樣的,但是用外聯結查詢的結果就會有些不同

  • 左外聯結

我們將上面的內聯結改為左外聯結

SQL[3]

select SR.TBBH, count(W.F_ID) As FJ_AMOUNT
from SURVEY_RECORD SR left join WYHCFJ W 
on SR.TBMJ > 10 and SR.TBLX = 'GJNYTB' and W.TCBM = 'GJNYTB' and W.TBBH = SR.TBBH 
group by SR.TBBH

查詢結果

SQL分析:
我們發現直接將內聯結更換為左外聯結查詢出來的結果不是正確結果,為什麼呢?
我們先分析一下上面這個SQL,上面這個左聯結的SQL,會先取SURVEY_RECORD表中的所有資料(167行),然後用這167行去關聯WYHCFJ表進行查詢符合on子句中條件的相關資料,所以很多TBBH 的符合條件的附件數量都是0。

SQL[4] 我們調換表聯結的順序

select SR.TBBH, count(W.F_ID) As FJ_AMOUNT
from WYHCFJ W left join SURVEY_RECORD SR 
on SR.TBMJ > 10 and SR.TBLX = 'GJNYTB' and W.TCBM = 'GJNYTB' and W.TBBH = SR.TBBH 
group by SR.TBBH

查詢結果

SQL分析:
我們看這次的查詢結果,和正確的結果多了一條資料,而且TBBH為NULL,數量為580。
這個資料是怎麼來的,我們調換表聯結的順序之後,上述SQL的意思就是,先去WYHCFJ表中的所以資料(1074條),然後用這1074條資料去關聯SURVEY_RECORD中符合on條件的相關資料,沒有符合條件的就置為NULL,所以有79條(獲取79組,用TBBH進行的分組)是符合之前條件的資料,得到的結果也是正確的,但是剩下的WYHCFJ表中的資料在SURVEY_RECORD中沒有符合條件的資料進行對應,所以查詢的SR.TBBH就被置為NULL了,後面的count(W.F_ID) As FJ_AMOUNT統計的就是WYHCFJ表中所有發附件數量減去符合條件的494條資料得到的580條資料。

我們查詢一下符合條件的資料有多少條,SQL:

select count(*) As FJ_AMOUNT
From WYHCFJ As W
where W.TBBH in (
    select SR.TBBH 
    From SURVEY_RECORD AS SR
    Where SR.TBMJ > 10 and SR.TBLX = 'GJNYTB'
) and W.TCBM = 'GJNYTB'

查詢結果

那麼我們怎麼利用左外聯結查詢出正確的結果呢?

SQL[5]– on 和 where 在聯結中的區別

select SR.TBBH, count(W.F_ID) As FJ_AMOUNT
from WYHCFJ W left join SURVEY_RECORD SR 
on SR.TBMJ > 10 and SR.TBLX = 'GJNYTB' 
where W.TCBM = 'GJNYTB' and W.TBBH = SR.TBBH 
group by SR.TBBH

查詢結果

SQL分析:
on 條件是在生成臨時表時(-ing)使用的條件,它不管 on 中的條件是否為真,都會返回左邊表中的記錄;而where 條件是在臨時表生成好之後(-ed),再對臨時表進行過濾的條件,條件不為真的就全部過濾掉。

也就是說**SQL[4]會查出80條資料,是因為我們把條件都寫在on條件下,那麼這些條件的限制只會在生成(-ing)臨時表的時候起作用,並且它不管 on 中的條件是否為真,都會返回左邊表中的記錄,所以就查詢出了80條資料;而SQL[5]我們把W.TCBM = 'GJNYTB' and W.TBBH = SR.TBBH條件寫在where條件下,雖然SQL[5]**在on條件(SR.TBMJ > 10 and SR.TBLX = 'GJNYTB')下生成的臨時表中有不符合正確結果的其他資料,但是在where條件中我們是將其過濾掉了的,所以可以返回正確的查詢結果。

上面的查詢時間,我取到的是反應平均水平的眾值,相對於端值應該更能反應相關SQL的查詢效率。
至於上述SQL的查詢效率的區別,主要是表中資料行數的多少,以及條件篩選出來的資料範圍。

筆記

1、在使用 jion 時,on 和 where 條件的區別如下:

1、 on 條件是在生成臨時表時使用的條件,它不管 on 中的條件是否為真,都會返回左邊表中的記錄。
2、where 條件是在臨時表生成好後,再對臨時表進行過濾的條件。這時已經沒有 left join 的含義(必須返回左邊表的記錄)了,條件不為真的就全部過濾掉。

2、不同的 SQL JOIN

內聯結(INNER JOIN):如果表中有至少一個匹配,則返回行
左聯結(LEFT JOIN):即使右表中沒有匹配,也從左表返回所有的行
右聯結(RIGHT JOIN):即使左表中沒有匹配,也從右表返回所有的行
全聯結(FULL JOIN):只要其中一個表中存在匹配,則返回行

3、SQLite 外聯結

SQLite支援 LEFT OUTER JOIN,但不支援 RIGHT OUTER JOIN。
**所以當我們需要使用RIGHT OUTER JOIN時,我們可以調整SQL中表的順序用LEFT OUTER JOIN替代。