Kodokon kodokon.com

子查询:WHERE、FROM、IN 和 EXISTS

把一条查询嵌套在另一条之中,以便与聚合值比较、按集合过滤或检测是否存在。

9 分钟 · 3 题

在 Kodokon 中打开本课

一个子查询是放在括号里、嵌套于另一条查询内部的查询。在单条语句中,它就能回答诸如“金额高于平均值的订单”或“至少有一个订单的客户”这样的问题。开始之前,请在 sqliteonline.com 或 sqlite3 中运行这段脚本。

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  amount REAL NOT NULL
);

INSERT INTO customers (id, name) VALUES
  (1, 'Alice'), (2, 'Karim'),
  (3, 'Mina'), (4, 'Paulo');

INSERT INTO orders (id, customer_id, amount)
VALUES
  (1, 1, 49.90), (2, 1, 15.00),
  (3, 2, 120.50), (4, 2, 80.00),
  (5, 3, 9.90), (6, 1, 200.00);
数据集:四位客户,六个订单。

放在 WHERE 中的标量子查询必须返回单个值:一行一列。引擎会先计算它,再像使用任何其他常量那样使用它。你无法用简单的 WHERE amount > AVG(amount) 来做到这一点,这在 SQL 中是被禁止的。

SQL
SELECT id, customer_id, amount
FROM orders
WHERE amount > (
  SELECT AVG(amount) FROM orders
);
金额高于平均客单价(79.22)的订单。

IN 会把某一列与子查询返回的值集合进行比较。EXISTS 则检测相关子查询(它引用了 c.id,即外层查询的一列)是否至少返回一行:一旦找到第一个匹配,引擎就会停下。

SQL
SELECT name FROM customers
WHERE id IN (
  SELECT customer_id FROM orders
  WHERE amount > 100
);

SELECT c.name FROM customers AS c
WHERE EXISTS (
  SELECT 1 FROM orders AS o
  WHERE o.customer_id = c.id
);
IN:消费大户。EXISTS:活跃的客户。

FROM 中,子查询表现得像一张临时表,称为派生表,并且必须被赋予一个别名。它是分两步聚合的经典工具:先算出每位客户的总额,再算出这些总额的平均值。

SQL
SELECT ROUND(AVG(t.total), 2) AS avg_basket
FROM (
  SELECT customer_id, SUM(amount) AS total
  FROM orders
  GROUP BY customer_id
) AS t;
每位活跃客户的平均总客单价:158.43。

知识检测

确认你已牢记本课的重点内容。

  1. 用在 amount > (...) 里的子查询必须返回多少个值?
    • 恰好一个
    • 外层表每一行对应一个
    • 想返回多少都行
  2. 相比 IN,EXISTS 的主要优势是什么?
    • 它可以返回多列
    • 它在找到第一行时就停下,不必把整个集合实体化
    • 它无需子查询就能工作
  3. 放在 FROM 中的子查询必须始终带上什么,才能在各处都可移植?
    • 一个别名,例如 AS t
    • 一个 ORDER BY 子句
    • 一个内部的分号