Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  Lateral Derived Tables

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;因此,不允许在 JSON_TABLE() 前面使用 LATERAL 关键字;这也是根据 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 是解决这两个方法中所有缺陷的高效解决方案。