Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


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

15.2.15.9 横向派生表

一个派生表通常不能引用(依赖)前面的表中的列。可以使用横向派生表来指定允许这种引用。

非横向派生表使用在第15.2.15.8节,“Derived Tables”中讨论的语法。横向派生表的语法与非横向派生表相同,除了在派生表specification前添加关键字LATERAL。每个要用作横向派生表的表都必须在LATERAL关键字之前。

横向派生表受以下限制:

  • 横向派生表只能出现在FROM子句中,或者在一个由逗号分隔的表列表中,或者在连接specification(JOININNER JOINCROSS JOINLEFT [OUTER] JOINRIGHT [OUTER] JOIN)中。

  • 如果横向派生表在连接子句的右操作数中,并且包含对左操作数的引用,那么连接操作必须是INNER JOINCROSS JOINLEFT [OUTER] JOIN

    如果表在左操作数中,并且包含对右操作数的引用,那么连接操作必须是INNER JOINCROSS JOINRIGHT [OUTER] JOIN

  • 如果一个 Derived 表引用了聚合函数,那么该函数的聚合查询不能是包含 Derived 表出现的 FROM 子句所拥有的那个。

  • 根据 SQL 标准,MySQL 总是将使用表函数,如JSON_TABLE() 的连接视为隐式使用了 LATERAL 关键字。由于 LATERAL 关键字是隐式的,因此不能在JSON_TABLE() 之前使用它,这也是 SQL 标准规定的。

以下讨论展示了如何使用 Derived 表来实现某些 SQL 操作,这些操作不能使用非 Derived 表或需要更少效率的 workaround。

假设我们想解决这个问题:给定销售队伍中每个人的表(每行描述一个销售队伍成员),和所有销售的表(每行描述一笔销售:销售员、客户、金额、日期),确定每个销售员的最大销售额和客户。这类问题可以有两种方法来解决。

解决问题的第一种方法:对于每个销售员,计算最大销售额,并找到提供了这个最大销售额的客户。在 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;

该查询效率较低,因为它对每个销售员计算最大销售额两次(一次在第一个子查询中,一次在第二个子查询中)。

我们可以尝试通过将最大销售额计算一次 per 销售员,并将其“缓存”在 Derived 表中,像这样修改查询:

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是解决前面讨论的两个方法中所有缺点的高效解决方案。