[英]How can a sub-query refer to a table outside it?
我試圖了解 JOIN 中的子查詢如何引用上層查詢中的字段。
車輛表存儲了公司使用的車輛的當前信息; 所有車輛歷史都存儲在一個名為 vehicle_aud 的表中,其結構與車輛表完全相同,但還包括對另一個表的引用,稱為修訂,該表存儲有關誰、何時、為什么等對車輛進行更改的信息主表。
為了完成對車輛的最后一個操作,使用了一個非常簡單的 JOIN,如下所示:
SELECT *
FROM vehicles v
JOIN vehicles_aud vu ON vu.id=v.id AND vu.revision_id=(
SELECT max(revision_id)
from vehicles_aud
WHERE id=v.id
)
JOIN revisions r ON r.id=vu.revision_id
請不要介意 SELECT 部分中的星號:我確定在此處指定任何實際字段對於我下面的問題沒有多大意義。 准確地說,這個查詢也可以通過以下方式重寫以便更好地理解:
SELECT *
FROM vehicles v
CROSS APPLY (
SELECT TOP 1 *
FROM vehicles_aud
WHERE id=v.id
ORDER BY id DESC
) vu
JOIN revisions r ON r.id=vu.revision_id
在第二個示例中,JOIN 不適用。
我假設第一個示例中的子查詢應該與 CROSS APPLY 運算符一起使用,因為它指的是子查詢之外的車輛表中的 id 字段,但是 IRL 使用上述 JOIN 的查詢效果很好。 我懷疑如果沒有 CROSS APPLY,這怎么可能? 我的意思是,在什么情況下子查詢可以引用子查詢之外的表的字段?
使用分析函數是一種方法:
SELECT TOP 1 WITH TIES *
FROM vehicles v
INNER JOIN vehicles_aud vu ON vu.id = v.id
INNER JOIN revisions r ON r.id = vu.revision_id
ORDER BY ROW_NUMBER() OVER (PARTITION BY v.id ORDER BY vu.revision_id DESC);
上述查詢將返回每組共享相同vehicles.id
revision_id
值的具有最大revision_id
值的所有記錄。
我不確定,這是否真的能回答你的問題......
簡而言之:任何一種 JOIN 都會創建兩個結果集並將它們與給定的條件匹配,而任何一種 APPLY 都會逐行調用操作。 如果 APPLY 返回多行,則添加結果集(類似於 JOIN),而對於單行結果,引擎僅添加列。
實際情況會復雜得多。
該引擎非常智能,會在檢查統計信息、索引、現有計划等后決定最佳計划。 您得到的真正計划很可能不是您所期望的。 對於看似不同的查詢,您獲得的計划很可能是相同的。
在打開“包括實際計划”的情況下嘗試以下操作:
USE master;
GO
CREATE DATABASE testPlan;
GO
USE testPlan;
GO
CREATE TABLE t1 (ID INT IDENTITY CONSTRAINT pk PRIMARY KEY, SomeValue VARCHAR(100));
INSERT INTO t1 VALUES('MaxVal will be 100'),('MaxVal will be 200'),('MaxVal will be 300');
GO
CREATE TABLE t2(fkID INT CONSTRAINT fk FOREIGN KEY REFERENCES t1(ID),TheValue INT);
INSERT INTO t2 VALUES(1,1),(1,2),(1,100)
,(2,1),(2,2),(2,200)
,(3,1),(3,2),(3,300);
GO
--a scalar computation using MAX()
SELECT *
,(SELECT MAX(t2.TheValue) FROM t2 WHERE t1.ID=t2.fkID) AS MaxVal
FROM t1
--the same as above, but with APPLY
SELECT *
FROM t1
CROSS APPLY(SELECT MAX(t2.TheValue) FROM t2 WHERE t1.ID=t2.fkID) A(MaxVal)
--Now we pick the TOP 1 after an ORDER BY
SELECT *
,(SELECT TOP 1 t2.TheValue FROM t2 WHERE t1.ID=t2.fkID ORDER BY t2.TheValue DESC) AS MaxVal
FROM t1
--and again the same with APPLY
SELECT *
FROM t1
CROSS APPLY(SELECT TOP 1 t2.TheValue FROM t2 WHERE t1.ID=t2.fkID ORDER BY t2.TheValue DESC) A(MaxVal)
--Tim's approach using the very slick TOP 1 WITH TIES approach
SELECT TOP 1 WITH TIES *
FROM t1 INNER JOIN t2 ON t1.ID=t2.fkID
ORDER BY ROW_NUMBER() OVER(PARTITION BY t1.ID ORDER BY t2.TheValue DESC);
GO
USE master;
GO
--carefull with real data!
--DROP DATABASE testPlan;
GO
“標量 MAX”的計划在 27(!) 行上使用表掃描,減少到 9。與 APPLY 相同的方法具有相同的計划。 引擎足夠聰明,可以看到這不需要完全成熟的結果集。 附帶說明:您可以將 MaxVal 用作查詢中的變量,非常有幫助...
在這個小測試中,子查詢中TOP 1
的計划是最昂貴的。 開頭和上面一樣(表掃描27行,減少到9行),但是要加一個排序操作。 APPLY 的變化大致相同。
TOP 1 WITH TIES
需要 9 行 t2 並對它們進行排序。 以下操作是針對 9 行進行的。 再進行一次排序並減少到 TOP 行。
在這種情況下,第一個是最快的 - 到目前為止。
但是在(您的)現實中,實際行為將取決於現有索引、統計信息和實際行數。 此外,您還有一個額外的級別(多一張桌子)。 查詢越復雜,優化器就越難找到最佳計划。
如果性能很重要,那么就與您的馬匹賽跑並進行測量。 如果性能不是那么重要,請使用更易於閱讀、理解和維護的查詢。
這是您的第一個查詢:
SELECT *
FROM vehicles v JOIN
vehicles_aud va
ON va.id = v.id AND
va.revision_id = (SELECT MAX(va2.revision_id)
FROM vehicles_aud va2
WHERE va2.id = v.id
--------------------------------^
) JOIN
revisions r
ON r.id = va.revision_id;
我想你的問題是關於這個條款的。 這是在相關子查詢的相關性子句。 表別名的使用闡明了正在發生的事情。
從邏輯上講,發生的情況是,對於外部查詢中的每一行,內部查詢使用va.id
的單獨值運行。正如您似乎知道的那樣,它提取了revision_id
最新值。
有些人對相關子查詢有一種不自然的偏見,認為數據庫實際上循環遍歷所有行。 請記住,SQL 是一種描述性語言。 盡管這描述了處理正在做什么,但這並不是一般實際發生的情況。 特別是,在某些情況下,相關子查詢可能是最有效的機制。
編寫查詢的更“口語化”的方法是使用窗口函數:
SELECT *
FROM vehicles v JOIN
(SELECT va.*,
ROW_NUMBER() OVER (PARTITION BY va.id ORDER BY va2.revision_id DESC) as seqnum
FROM vehicles_aud va
) va
ON va.id = v.id AND
va.seqnum = 1 JOIN
revisions r
ON r.id = va.revision_id;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.