簡體   English   中英

在SQL中評估相關子查詢

[英]Evaluating a correlated subquery in SQL

我在評估相關子查詢時遇到了麻煩。 一個例子是在SELECT中使用相關子查詢,因此不需要GROUP BY:

考慮關系:

Movies : Title, Director Length
Schedule : Theatre, Title

我有以下查詢

SELECT S.Theater, MAX(M.Length)
FROM Movies M JOIN Schedule S ON M.Title=S.Title
GROUP BY S.Theater

這是每個劇院播放的最長的電影。 這是不使用GROUP BY的相同查詢:

SELECT DISTINCT S.theater,
    (SELECT MAX(M.Length)
    FROM Movies M
    WHERE M.Title=S.Title)
FROM Schedule S

但我不明白它是如何工作的。

如果有人能給我一個關於如何評估相關子查詢的例子,我將不勝感激。

謝謝 :)

從概念的角度來看,假設數據庫在沒有子查詢的情況下遍歷結果的每一行:

SELECT DISTINCT S.Theater, S.Title
FROM Schedule S

然后,對於每一個,為您運行子查詢:

SELECT MAX(M.Length)
FROM Movies M
WHERE M.Title = (whatever S.Title was)

並把它作為價值。 真的,它(概念上)與使用函數不同:

SELECT DISTINCT S.Theater, SUBSTRING(S.Title, 1, 5)
FROM Schedule S

這只是該函數對另一個表執行查詢。

不過,我確實在概念上說。 數據庫可能正在將相關查詢優化為更像連接的東西。 無論它在內部對性能有何影響,但對於理解這個概念並不重要。

但是,它可能無法返回您期望的結果。 考慮以下數據(對不起sqlfiddle似乎是錯誤的atm):

CREATE TABLE Movies (
  Title varchar(255),
  Length int(10) unsigned,
  PRIMARY KEY (Title)
);

CREATE TABLE Schedule (
  Title varchar(255),
  Theater varchar(255),
  PRIMARY KEY (Theater, Title)
);

INSERT INTO Movies
VALUES ('Star Wars', 121);
INSERT INTO Movies
VALUES ('Minions', 91);
INSERT INTO Movies
VALUES ('Up', 96);

INSERT INTO Schedule
VALUES ('Star Wars', 'Cinema 8');
INSERT INTO Schedule
VALUES ('Minions', 'Cinema 8');
INSERT INTO Schedule
VALUES ('Up', 'Cinema 8');
INSERT INTO Schedule
VALUES ('Star Wars', 'Cinema 6');

然后這個查詢:

SELECT DISTINCT
  S.Theater,
  (
    SELECT MAX(M.Length)
    FROM Movies M
    WHERE M.Title = S.Title
  ) AS MaxLength
FROM Schedule S;

你會得到這個結果:

+----------+-----------+
| Theater  | MaxLength |
+----------+-----------+
| Cinema 6 |       121 |
| Cinema 8 |        91 |
| Cinema 8 |       121 |
| Cinema 8 |        96 |
+----------+-----------+

正如您所看到的,它不是GROUP BY的替代品(您仍然可以使用GROUP BY),它只是為每一行運行子查詢。 DISTINCT只會從結果中刪除重復項。 它不再為每個劇院提供“最大長度”,它只是給出與劇院名稱相關的每個獨特電影長度。

PS:你可能會使用某種ID列來識別電影,而不是使用連接中的標題。 這樣一來,如果必須修改電影的名稱,它只需要在一個地方改變,而不是整個時間表。 另外,加入ID號而不是字符串更快。

概念...

要理解這一點,首先忽略關於相關子查詢的位。

考慮這樣的語句的操作順序:

SELECT t.foo FROM mytable t

MySQL准備一個空的結果集。 結果集中的行將包含一列,因為SELECT列表中有一個表達式。 從mytable中檢索一行。 MySQL在結果集中添加一行,使用mytable行中foo列的值,將其分配給結果集中的foo列。 獲取下一行,重復相同的過程,直到沒有更多行要從表中獲取。

很簡單的東西。 但請忍受我。

請考慮以下聲明:

SELECT t.foo AS fi, 'bar' AS fo FROM mytable t

MySQL進程的方式相同。 准備一個空的結果集。 結果集中的行這次將有列。 第一列的名稱為fi(因為我們為其指定了名稱為fi的別名)。 結果集行中的第二列將命名為fo,因為(再次)我們分配了一個別名。

現在我們從mytable中蝕刻一行,並在結果集中插入一行。 foo列的值進入列名fi,文字字符串'bar'進入名為fo的列。 繼續獲取行並在結果集中插入行,直到不再需要獲取行。

不是太難。

接下來,考慮這個聲明,看起來有點棘手:

SELECT t.foo AS fi, (SELECT 'bar') AS fo FROM mytable t

同樣的事情再次發生。 空結果集。 行有兩列,名稱為fi和fo。

從mytable中獲取一行,並在結果集中插入一行。 foo的值進入列fi(就像之前一樣。)這就是它變得棘手的地方......對於結果集中的第二列,MySQL在parens中執行查詢。 在這種情況下,這是一個非常簡單的查詢,我們可以很容易地測試它,看看它返回什么。 從該查詢中獲取結果並將其分配給fo列,並將該行插入結果集。

還在我這兒?

SELECT t.foo AS fi, (SELECT q.tip FROM bartab q LIMIT 1) AS fo FROM mytable 

這開始看起來更復雜。 但它並沒有那么多不同。 同樣的事情再次發生。 准備空結果集。 行將有兩列,一個名稱為fi,另一列名為fo。 從mytable中獲取一行。 從foo列獲取值,並將其分配給結果行中的fi列。 對於fo列,執行查詢,並將查詢結果分配給fo列。 將結果行插入結果集。 從mytable中獲取另一行,重復該過程。

在這里我們應該停下來注意一些事情 MySQL在SELECT列表中對該查詢很挑剔。 真的很挑剔。 MySQL對此有限制。 查詢必須只返回一列。 並且它不能返回多行。

在最后一個示例中,對於插入結果集的行,MySQL正在查找要分配給fo列的單個值。 當我們以這種方式思考時,有意義的是查詢不能返回多個列...... MySQL會對第二列的值做什么? 而且我們不想返回多行是有意義的...... MySQL會對多行做什么?

MySQL將允許查詢返回零行。 當發生這種情況時,MySQL會為fo列分配NULL。

如果您對此有所了解,那么95%的方式可以理解相關的子查詢。

讓我們看另一個例子。 我們的單行SQL有點不合適,所以我們只需添加一些換行符和空格,以便我們更容易使用。 額外的空格和換行符不會改變我們陳述的含義。

SELECT t.foo AS fi
     , ( SELECT q.tip
           FROM bartab q
          WHERE q.col = t.foo
          ORDER BY q.tip DESC
          LIMIT 1
        ) AS fo
   FROM mytable t

好的,這看起來要復雜得多。 但它真的嗎? 又到了同樣的事情。 准備一個空的結果集。 行將有兩列,fi和fo。 從mytable中獲取一行,並准備好一行插入結果集。 復制foo列中的值,將其指定給fi列。 對於fo列,執行查詢,將查詢返回的單個值帶到fo列,然后將行推入結果集。 從mytable中獲取下一行,然后重復。

解釋(finall!)關於“相關”的部分。

我們將運行該查詢以獲取fo列的結果。 它包含對外部表中列的引用 t.foo 在此示例中出現在WHERE子句中; 它沒有必要,它可以出現在聲明的任何地方。

MySQL使用它做什么,當它運行該子查詢時,它將foo列的值傳遞給查詢。 如果我們剛從mytable獲取的行在foo列中的值為42 ...該子查詢等效於

         SELECT q.tip
           FROM bartab q
          WHERE q.col =   42
          ORDER BY q.tip DESC
          LIMIT 1

但是因為我們沒有傳入42的字面值,我們傳入的是外部查詢中的行的值,我們的子查詢返回的結果與我們在外部處理的行“相關”查詢。

我們的子查詢中可能要復雜得多,只要我們記住SELECT列表中有關子查詢的規則......它必須返回一列,最多只返回一行。 它最多返回一個值。

相關子查詢可以出現在SELECT列表以外的語句的某些部分中,例如WHERE子句。 同樣的一般概念適用。 對於外部查詢處理的每一行 ,該中的列的值將傳遞到子查詢。 從子查詢返回的結果外部查詢中正在處理的行相關


討論省略了實際執行之前的所有步驟...將statament解析為令牌,執行語法檢查(關鍵字和標識符在正確的位置)。 然后執行語義檢查(mytable是否存在,用戶是否具有select權限,mytable中是否存在列foo)。 然后確定訪問計划。 並在執行中,獲取所需的鎖,等等。 我們執行的每個語句都會發生這種情況。)

我們不打算討論我們可以使用相關子查詢創建的可怕性能問題。 雖然前面的討論應該提供一個線索。 由於子查詢是針對我們放入結果集的每一行執行的(如果它在我們的外部查詢的SELECT列表中),或者正在為外部查詢訪問的每一行執行...如果外部查詢是返回40,000行,這意味着我們的相關子查詢將被執行40,000次。 所以我們最好確保子查詢快速執行。 即使它執行得很快,我們仍然要執行它40,000次。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM