文档首页
MySQL 9.0 参考手册
相关文档 下载此手册

MySQL 9.0 参考手册  /  ...  /  侧向派生表

15.2.15.9 侧向派生表

派生表通常不能引用(依赖)相同 FROM 子句中前面表的列。派生表可以定义为侧向派生表以指定允许此类引用。

非侧向派生表使用 第 15.2.15.8 节,“派生表” 中讨论的语法指定。侧向派生表的语法与非侧向派生表相同,只是在派生表规范之前指定了关键字 LATERAL。必须在每个用作侧向派生表的表之前添加 LATERAL 关键字。

侧向派生表受以下限制

  • 侧向派生表只能出现在 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 是解决上述两种方法中所有缺点的有效解决方案。