派生表通常不能引用(依赖于)同一个 FROM
子句中前面表的列。派生表可以定义为侧向派生表,以指定允许此类引用。
非侧向派生表使用 第 15.2.15.8 节“派生表” 中讨论的语法指定。侧向派生表的语法与非侧向派生表的语法相同,只是在派生表规范之前指定了关键字 LATERAL
。LATERAL
关键字必须位于要用作侧向派生表的每个表之前。
侧向派生表受以下限制
侧向派生表只能出现在
FROM
子句中,可以出现在用逗号分隔的表列表中,也可以出现在连接规范(JOIN
、INNER JOIN
、CROSS JOIN
、LEFT [OUTER] JOIN
或RIGHT [OUTER] JOIN
)中。如果侧向派生表位于连接子句的右侧操作数中,并且包含对左侧操作数的引用,则连接操作必须是
INNER JOIN
、CROSS JOIN
或LEFT [OUTER] JOIN
。如果该表位于左侧操作数中,并且包含对右侧操作数的引用,则连接操作必须是
INNER JOIN
、CROSS JOIN
或RIGHT [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
是解决上述两种方法中所有缺点的有效解决方案。