自連線
到目前為止,我們講解的連線都是在不同的資料表之間進行的,其實參與連線的表完全可以是同一樣表,也就是表與其自身相連線,這樣連線就被稱為自連線。自連線並不是獨立於交叉連線、內連線、外連線等這些連線方式之外的另外一種連線方式,而只是這些連線方式的一種特例,也就是交叉連線、內連線、外連線等連線方式中只要參與連線的表是同一張表,那麼它們就可以被稱為自連線。
雖然大部分時間使用連線都是在連線不同的表,但是有的時候表也需要與自身連線,其主要用途就是檢索一張表內部的匹配情況。下面就通過一個例項來演示自連線的使用。假設需要檢索與另外一個訂單的訂單型別一樣的所有訂單的列表。有的開發人員可能會寫出下面的SQL語句:
SELECT FNumber,FPrice,FTypeId FROM T_Order WHERE FTypeId= FTypeId
執行以後我們在輸出結果中看到下面的執行結果:
FNumber FPrice FTypeId
K001 100.00 1
K002 200.00 1
T003 300.00 2
N002 100.00 2
N003 500.00 4
T001 300.00 3
T002 100.00 1
這裡顯示出了T_Order表中的所有資料,而不是想像中的結果。因為這裡的WHERE語句條件永遠為真,因為向同行的相同列總是等於它自己,因此結果集中包含了表中的所有記錄。
如果要實現要求的功能,可以假象存在另外一個與T_Order表完全相同的表,這樣我們就可以在這兩個表之間進行任意的連線操作了。我們嘗試套用INNER JOIN的寫法,只是將參與連線的兩個表名都設定為T_Order,SQL語句如下:
SELECT FNumber,FPrice,FTypeId FROM T_Order INNER JOIN T_Order ON T_Order.FTypeId=T_Order.FTypeId
這句SQL語句執行以後資料庫系統會報出如下的錯誤資訊:
- FROM 子句中的物件"T_Order" 和"T_Order" 具有相同的表現名稱。請使用相關名稱來區分它們。
很顯然,因為這裡兩次使用了T_Order表,但是資料庫系統無法區分這兩個T_Order表,因此必須為它們指定不同的別名,修改後的SQL語句如下:
SELECT o1.FNumber,o1.FPrice,o1.FTypeId,o2.FNumber,o2.FPrice,o2.FTypeId FROM T_Order o1 INNER JOIN T_Order o2 ON o1.FTypeId=o2.FTypeId
這裡為T_Order表取了兩個別名o1和o2,並且在引用表中列的時候也明確的指定了列屬於那個表下的,這樣資料庫系統就能區分這兩個別名代表的表了。使用別名以後我們可以將這兩個別名看作結構相同、資料相同的兩個不同的表,這樣就可以避免思維上的障礙。
這個SQL語句執行成功,沒有語法錯誤,它是一個有效的自連線,不過它執行所產生的結果卻不是正確的。比如第一行中“訂單號為K001的訂單與訂單號為K001的訂單的訂單型別相同”,自己的訂單型別當然與自己相同,這當然是正確的,可是這樣的結果對我們來說是沒有意義的。ON子句中指定兩個表的FTypeId欄位必須相同,當然對於同一個訂單來說,它們肯定是相同的,而這裡真正要查詢的是具有相同的FTypeId欄位值的兩個不同的訂單,因此需要在連線條件中新增一個新的條件,修改後的SQL語句如下:
SELECT o1.FNumber,o1.FPrice,o1.FTypeId,o2.FNumber,o2.FPrice,o2.FTypeId FROM T_Order o1 INNER JOIN T_Order o2 ON o1.FTypeId=o2.FTypeId and o1.FId<>o2.FId
ON子句末端新增的新條件“and o1.FId<>o2.FId”檢查了別名為o1的表的主鍵不等於名為o2的表的主鍵,因為主鍵是唯一的,所以這樣就可以確保得到的是一個不同的訂單,從而
不包含同一張訂單。這個SQL語句執行以後我們在輸出結果中看到下面的執行結果:
FNUMBER FPRICE FTYPEID FNUMBER FPRICE FTYPEID
T002 100 1 K001 100 1
K002 200 1 K001 100 1
T002 100 1 K002 200 1
K001 100 1 K002 200 1
N002 100 2 T003 300 2
T003 300 2 N002 100 2
K002 200 1 T002 100 1
K001 100 1 T002 100 1
可以看到執行結果中已經去掉了相同訂單的匹配,但是仔細觀察仍然會發現存在重複的行。比如第一行的最後一行。o1表中的T002訂單與o2表中的K001訂單匹配,然後o2表中的K001訂單與o1表中的T002訂單匹配,也就是說資料庫系統把“A匹配B”與“B匹配A”看成了兩個不同的匹配,而實質上它們只是方向不同的相同的匹配,因此需要防止出現這樣相同的匹配結果。因為出現上面這種問題的原因是因為存在“A匹配B”與“B匹配A”這兩個方向的匹配,那麼我們只要破壞這種雙向匹配就可以了,最簡單的方式就是要求o1的表的主鍵值於o2的表的主鍵值。修改後的SQL語句如下:
SELECT o1.FNumber,o1.FPrice,o1.FTypeId,o2.FNumber,o2.FPrice,o2.FTypeId FROM T_Order o1 INNER JOIN T_Order o2 ON o1.FTypeId=o2.FTypeId and o1.FId<o2.FId
這裡僅有的改變是ON子句中連線條件的最後面部分,其原來的形式是:
o1.FId<>o2.FId
這個ON子句僅僅應用於兩個表中FId欄位值不同的記錄。只要o1表與o2表中的FId欄位值不同,則記錄就會被包含字結果集中,因此將導致重複,所以這裡將ON子句的這個SQL片段替換為:
o1.FId<o2.FId
現在o1表的一個記錄行僅僅在它的FId欄位值小於o2表的一個記錄行僅僅在它的FId欄位值的時候,才出現在結果集中。這確保了一行資料僅出現在結果集中一次。
這個SQL語句執行以後我們在輸出結果中看到下面的執行結果:
FNUMBER FPRICE FTYPEID FNUMBER FPRICE FTYPEID
K001 100 1 K002 200 1
T003 300 2 N002 100 2
K002 200 1 T002 100 1
K001 100 1 T002 100 1