文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (美式 Letter) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 參考手冊  /  ...  /  橫向衍生表

15.2.15.9 橫向衍生表

衍生表通常無法參照(依賴)同一個 FROM 子句中前面表格的欄位。衍生表可以定義為橫向衍生表,以指定允許這種參照。

非橫向衍生表格使用第 15.2.15.8 節,「衍生表格」中討論的語法指定。橫向衍生表格的語法與非橫向衍生表格相同,只是在衍生表格規格之前指定了關鍵字 LATERALLATERAL 關鍵字必須放在每個要用作橫向衍生表格的表格之前。

橫向衍生表格受以下限制:

  • 橫向衍生表格只能出現在 FROM 子句中,可以是逗號分隔的表格列表,或是聯結規格 (JOININNER JOINCROSS JOINLEFT [OUTER] JOINRIGHT [OUTER] JOIN)。

  • 如果橫向衍生表格在聯結子句的右運算元中,並且包含對左運算元的參照,則聯結操作必須是 INNER JOINCROSS JOINLEFT [OUTER] JOIN

    如果表格在左運算元中,並且包含對右運算元的參照,則聯結操作必須是 INNER JOINCROSS JOINRIGHT [OUTER] JOIN

  • 如果橫向衍生表格參照聚合函數,則該函數的聚合查詢不能是擁有橫向衍生表格所在的 FROM 子句的查詢。

  • 依照 SQL 標準,MySQL 始終將與表格函數(例如 JSON_TABLE())的聯結視為已使用 LATERAL。由於 LATERAL 關鍵字是隱含的,因此在 JSON_TABLE() 之前不允許使用;這也符合 SQL 標準。

以下討論顯示橫向衍生表格如何實現某些無法使用非橫向衍生表格完成,或需要效率較低的替代方案的 SQL 操作。

假設我們要解決這個問題:給定一個銷售人員表格(其中每一行描述一個銷售人員),以及一個所有銷售額的表格(其中每一行描述一個銷售額:銷售人員、客戶、金額、日期),確定每個銷售人員的最大銷售額及其客戶。這個問題可以透過兩種方式解決。

解決問題的第一種方法:對於每個銷售人員,計算最大銷售額,並找出提供此最大銷售額的客戶。在 MySQL 中,可以這樣做:

SELECT
  salesperson.name,
  -- find maximum sale size for this salesperson
  (SELECT MAX(amount) AS amount
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id)
  AS amount,
  -- find customer for this maximum size
  (SELECT customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    AND all_sales.amount =
         -- find maximum size, again
         (SELECT MAX(amount) AS amount
           FROM all_sales
           WHERE all_sales.salesperson_id = salesperson.id))
  AS customer_name
FROM
  salesperson;

這個查詢效率不高,因為它每個銷售人員計算最大銷售額兩次(在第一個子查詢中一次,在第二個子查詢中一次)。

我們可以嘗試通過每個銷售人員計算一次最大值,並在衍生表格中 快取 它來提高效率,如下面的修改查詢所示:

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale_customer.customer_name
FROM
  salesperson,
  -- calculate maximum size, cache it in transient derived table max_sale
  (SELECT MAX(amount) AS amount
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id)
  AS max_sale,
  -- find customer, reusing cached maximum size
  (SELECT customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    AND all_sales.amount =
        -- the cached maximum size
        max_sale.amount)
  AS max_sale_customer;

但是,該查詢在 SQL-92 中是非法的,因為衍生表格不能依賴同一個 FROM 子句中的其他表格。衍生表格在查詢期間必須是常數,不能包含對其他 FROM 子句表格的欄位的參照。按照撰寫方式,該查詢會產生以下錯誤:

ERROR 1054 (42S22): Unknown column 'salesperson.id' in 'where clause'

在 SQL:1999 中,如果在衍生表格之前加上 LATERAL 關鍵字(表示 此衍生表格依賴於其左側的前一個表格),則該查詢將變為合法。

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale_customer.customer_name
FROM
  salesperson,
  -- calculate maximum size, cache it in transient derived table max_sale
  LATERAL
  (SELECT MAX(amount) AS amount
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id)
  AS max_sale,
  -- find customer, reusing cached maximum size
  LATERAL
  (SELECT customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    AND all_sales.amount =
        -- the cached maximum size
        max_sale.amount)
  AS max_sale_customer;

橫向衍生表格不必是常數,並且每次頂層查詢處理其所依賴的前一個表格中的新行時都會更新。

解決問題的第二種方法:如果 SELECT 列表中的子查詢可以傳回多個欄位,則可以使用不同的解決方案。

SELECT
  salesperson.name,
  -- find maximum size and customer at same time
  (SELECT amount, customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    ORDER BY amount DESC LIMIT 1)
FROM
  salesperson;

這是有效率的,但非法。它不起作用,因為此類子查詢只能傳回單個欄位。

ERROR 1241 (21000): Operand should contain 1 column(s)

重新撰寫查詢的一種嘗試是從衍生表格中選取多個欄位:

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale.customer_name
FROM
  salesperson,
  -- find maximum size and customer at same time
  (SELECT amount, customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    ORDER BY amount DESC LIMIT 1)
  AS max_sale;

但是,這也不起作用。衍生表格依賴於 salesperson 表格,因此在沒有 LATERAL 的情況下會失敗。

ERROR 1054 (42S22): Unknown column 'salesperson.id' in 'where clause'

新增 LATERAL 關鍵字使查詢合法。

SELECT
  salesperson.name,
  max_sale.amount,
  max_sale.customer_name
FROM
  salesperson,
  -- find maximum size and customer at same time
  LATERAL
  (SELECT amount, customer_name
    FROM all_sales
    WHERE all_sales.salesperson_id = salesperson.id
    ORDER BY amount DESC LIMIT 1)
  AS max_sale;

簡而言之,LATERAL 是解決剛剛討論的兩種方法中所有缺點的有效解決方案。