嵌套在 OUTER JOIN 中的 INNER JOIN 的語法與查詢結果
TLDR;如果您查看 2 個執行計劃,是否有一個簡單的答案來確定哪個更好?我故意沒有創建索引,所以更容易看到發生了什麼。
跟進我之前的問題,我們發現不同連接樣式(即嵌套與傳統)之間的查詢性能差異,我意識到嵌套語法也會修改查詢的行為。考慮以下 2 個查詢。
SELECT a.*, m.*, n.* FROM dbo.Autos a LEFT JOIN dbo.Models m JOIN dbo.Manufacturers n -- <-- Nested INNER JOIN ON n.ManufacturerID = m.ManufacturerID ON m.ModelID = a.ModelID
這不必使製造商加入以包含具有不在Models 表中的ModelID的自動行。
使用傳統語法,我們必須將連接到 Manufactures 更改為外部連接,就像這樣……但這會改變查詢計劃。
SELECT a.*, m.*, n.* FROM dbo.Autos a LEFT JOIN dbo.Models m ON m.ModelID = a.ModelID LEFT JOIN dbo.Manufacturers n -- <-- Now LEFT OUTER JOIN ON n.ManufacturerID = m.ManufacturerID
如果您查看 2 個執行計劃,是否有一個簡單的答案來確定哪個更好?我故意沒有創建索引,所以更容易看到發生了什麼。
第二個計劃的估計成本較低,因此在有限的意義上它“更好”。
數據集非常小,優化器沒有花太多時間尋找替代方案。查詢的第一種形式恰好在早期使用散列連接和表假離線找到了一個計劃。該計劃的估計成本是如此之低,以至於優化器不會費心尋找更好的東西。
第二種形式的查詢恰好在搜尋過程的早期僅使用嵌套循環外連接找到一個計劃,並且優化器再次決定該計劃足夠好。碰巧這個計劃估計更便宜。
也就是說(如問題評論中所述)這兩個查詢在語義上並不相同。如果您可以保證對於數據庫的所有可能的未來狀態,結果總是相同的,那麼這對您來說可能並不重要,但優化器不能做出這樣的假設。它只生成保證在所有情況下都能生成 SQL 指定的相同結果的計劃。
我意識到嵌套語法也會修改查詢的行為。
“嵌套語法”只是整個 ANSI 連接語法規範的一個方面。為了為更複雜的連接模式啟用完整的邏輯規範,規範允許(可選)括號和
FROM
子句子查詢。查詢可以使用括號中的相同 ANSI 語法編寫:
SELECT A.*, M.*, N.* FROM dbo.Autos AS A LEFT JOIN ( dbo.Manufacturers AS N JOIN dbo.Models AS M ON M.ManufacturerID = N.ManufacturerID ) ON M.ModelID = A.ModelID;
這種形式清楚地表明,邏輯要求是左連接 from
Autos
到內連接to的結果。省略可選的括號會給出您稱為“嵌套”的形式:Manufacturers``Models
SELECT A.*, M.*, N.* FROM dbo.Autos AS A LEFT JOIN dbo.Manufacturers AS N JOIN dbo.Models AS M ON M.ManufacturerID = N.ManufacturerID ON M.ModelID = A.ModelID;
這不是一種不同的語法——它只是省略了可選的括號並重新格式化了一下。
正如 Martin 所提到的,在這種情況下,也可以使用內部聯接和右外部聯接來表達邏輯要求:
SELECT A.*, M.*, N.* FROM dbo.Manufacturers AS N JOIN dbo.Models AS M ON M.ManufacturerID = N.ManufacturerID RIGHT JOIN dbo.Autos AS A ON A.ModelID = M.ModelID;
上述所有三種查詢形式都使用相同的 ANSI 連接語法。所有這三個也恰好使用提供的數據集生成相同的物理執行計劃:
正如我在對上一個問題的回答中提到的,表達完全相同邏輯要求的查詢並不總是會產生相同的執行計劃。您更喜歡使用哪種邏輯查詢形式主要是風格問題。一般而言,一種特定樣式與“更好”的查詢計劃之間沒有關聯。如果新查詢在邏輯上與原始查詢不完全相同,我通常建議不要重寫查詢以獲得特定計劃。
SQL 標準還允許
FROM
子句查詢,因此編寫相同查詢規範的另一種方法是:SELECT * FROM dbo.Autos AS A LEFT JOIN ( SELECT N.ManufacturerID, ManufacturerName = N.Name, M.ModelID, ModelName = M.Name FROM dbo.Manufacturers AS N JOIN dbo.Models AS M ON M.ManufacturerID = N.ManufacturerID ) AS R1 ON R1.ModelID = A.ModelID;
使用傳統語法,我們必須將連接更改為 `Manufacturers 到外部連接,就像這樣……但這會改變查詢計劃。
這可能會改變查詢的含義,在這種情況下,它在技術上不是一個有效的替代方案(但請參閱ypercube對您的問題的 評論)。
ANSI 連接語法中的(可選)括號正好用於像這樣更複雜的連接要求,因此您不必害怕在必要時使用它們。