知识库✳

教程与手册

全部教程
  1. 01Oracle 函数手册
  2. 02性能优化手册
  3. 03Linux Shell
  4. 04Oracle 学习与演练
  5. 05网络基础学习
  6. 06数据治理与管理
返回个人网站
甲Oracle 学习教程从 SQL 到 PL/SQL 与运维
首页课程目录搜索演练场
知识库

已完成 0 / 22 章

模块一 · 入门与准备

  • 01Oracle 数据库体系结构
  • 02环境安装与连接方式
  • 03SQL*Plus 与常用工具

模块二 · SQL 查询基础

  • 04SELECT 查询基础
  • 05过滤与排序
  • 06单行函数
  • 07多表连接
  • 08分组聚合与 HAVING
  • 09子查询

模块三 · 数据与对象

  • 10DML 与事务控制
  • 11表与数据类型
  • 12约束
  • 13索引与执行计划
  • 14视图、序列与同义词

模块四 · PL/SQL 编程

  • 15PL/SQL 块与变量
  • 16流程控制与游标
  • 17存储过程与函数
  • 18触发器
  • 19异常处理

模块五 · 运维与进阶

  • 20用户与权限管理
  • 21表空间与存储管理
  • 22备份与恢复
❖

Oracle 学习教程

返回个人网站知识库

示例代码基于 Oracle AI Database 26ai / 23ai,查询示例使用官方 HR 示例方案。

以 Next.js 16 · Tailwind CSS v4 · HeroUI v3 装帧

09
  1. 首页
  2. 课程目录
  3. 子查询

第 9 章◆进阶◆建议 50 分钟◆模块二 · SQL 查询基础

子查询

把一个查询的结果作为另一个查询的输入:单行、多行、相关子查询、WITH 子句与 EXISTS。

学完这一章,你应该能

  • 区分单行子查询与多行子查询的运算符要求
  • 会用 IN / ANY / ALL / EXISTS 解决不同语义
  • 能用 WITH 子句拆解复杂查询
  • 理解相关子查询的执行方式与性能特征

9.1单行子查询

子查询返回一行一列时,可以用 = < > 等标量运算符。如果它意外返回了多行,会报 ORA-01427。

找出工资高于全公司平均值的员工
SELECT employee_id, last_name, salary
FROM   employees
WHERE  salary > (SELECT AVG(salary) FROM employees)
ORDER  BY salary DESC;
在你的专用沙箱里执行打开演练场
在 SELECT 和 HAVING 中嵌入子查询
SELECT d.department_name,
       COUNT(*)                    AS headcount,
       ROUND(AVG(e.salary), 2)     AS avg_salary
FROM   employees   e
JOIN   departments d ON d.department_id = e.department_id
GROUP  BY d.department_name
HAVING AVG(e.salary) > (SELECT AVG(salary) FROM employees)
ORDER  BY avg_salary DESC;
在你的专用沙箱里执行打开演练场

9.2多行子查询:IN / ANY / ALL

  • = ANY 等价于 IN;<> ALL 等价于 NOT IN
  • NOT IN 列表里若含 NULL,整个条件永远不成立(返回空结果)
  • 这个 NULL 陷阱是实战中最隐蔽的坑之一,宁可改用 NOT EXISTS
三种运算符的语义差别
-- IN:等于列表中的任意一个
SELECT last_name, salary FROM employees
WHERE  department_id IN (SELECT department_id FROM departments WHERE location_id = 1700);

-- ANY:只需满足其中一个比较(> ANY 意为「大于最小值」)
SELECT last_name, salary FROM employees
WHERE  salary > ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG');

-- ALL:必须满足所有比较(> ALL 意为「大于最大值」)
SELECT last_name, salary FROM employees
WHERE  salary > ALL (SELECT salary FROM employees WHERE job_id = 'IT_PROG');
在你的专用沙箱里执行打开演练场

9.3EXISTS 与相关子查询

相关子查询会引用外层的列,因此对每一行都要执行一次。EXISTS 只关心「有没有匹配行」,找到一条就可以停止,通常比 IN 更高效且不受 NULL 影响。

EXISTS 与 NOT EXISTS
-- 查出至少有一名员工工资超过 15000 的部门
SELECT d.department_id, d.department_name
FROM   departments d
WHERE  EXISTS (
         SELECT 1
         FROM   employees e
         WHERE  e.department_id = d.department_id
           AND  e.salary > 15000
       )
ORDER  BY d.department_id;

-- 反查:没有任何员工的部门(等价于上一章的 LEFT JOIN ... IS NULL)
SELECT d.department_id, d.department_name
FROM   departments d
WHERE  NOT EXISTS (
         SELECT 1 FROM employees e WHERE e.department_id = d.department_id
       );
在你的专用沙箱里执行打开演练场

提示 SELECT 1 还是 SELECT * 在 EXISTS 里没有区别,数据库不会真的取列。写成 SELECT 1 更明确地表达「我只关心存在性」。

9.4WITH 子句(CTE):把复杂查询拆开

  • WITH 只是把子查询提到前面,逻辑上等价于内联视图,但可读性大幅提升
  • 同一个 CTE 可以被引用多次;优化器可能把它并入外层查询,也可能物化成临时表,可用 MATERIALIZE / INLINE 提示干预
  • 11gR2 起支持递归 WITH(WITH ... AS (… UNION ALL …)),用于树形结构;12.2 起递归成员可并行
先算部门统计,再筛出高于公司平均的部门
WITH dept_stats AS (
  SELECT department_id,
         COUNT(*)        AS headcount,
         ROUND(AVG(salary), 2) AS avg_salary
  FROM   employees
  WHERE  department_id IS NOT NULL
  GROUP  BY department_id
),
company_avg AS (
  SELECT ROUND(AVG(salary), 2) AS avg_salary FROM employees
)
SELECT d.department_name,
       s.headcount,
       s.avg_salary,
       c.avg_salary AS company_avg
FROM   dept_stats  s
JOIN   departments d ON d.department_id = s.department_id
CROSS  JOIN company_avg c
WHERE  s.avg_salary > c.avg_salary
ORDER  BY s.avg_salary DESC;
在你的专用沙箱里执行打开演练场

练习

3 题 · 先自己写,再展开对照

参考答案

NOT IN 会被展开成一系列不等比较。只要列表里有 NULL,
任何 x <> NULL 的结果都是 UNKNOWN,而不是 TRUE,
整个 NOT IN 条件就无法成立,于是查不到任何行。

改用 NOT EXISTS(或给子查询加 WHERE col IS NOT NULL):
SELECT d.department_id
FROM   departments d
WHERE  NOT EXISTS (
         SELECT 1 FROM employees e WHERE e.department_id = d.department_id
       );

自评要点

  • 指出 x <> NULL 是 UNKNOWN 而非 TRUE
  • 给出 NOT EXISTS 作为替代

参考答案

SELECT e.employee_id, e.department_id, e.salary
FROM   employees e
WHERE  e.salary > (
         SELECT AVG(e2.salary)
         FROM   employees e2
         WHERE  e2.department_id = e.department_id
       )
ORDER  BY e.department_id, e.salary DESC;
在你的专用沙箱里执行打开演练场

自评要点

  • 写出相关子查询(内层引用外层的 e.department_id)
  • 理解外层每行都要执行一次内层查询

参考答案

WITH dept_stats AS (
  SELECT department_id, ROUND(AVG(salary), 2) AS avg_salary
  FROM   employees
  WHERE  department_id IS NOT NULL
  GROUP  BY department_id
),
company_avg AS (
  SELECT ROUND(AVG(salary), 2) AS avg_salary FROM employees
)
SELECT d.department_name, s.avg_salary, c.avg_salary AS company_avg
FROM   dept_stats  s
JOIN   departments d ON d.department_id = s.department_id
CROSS  JOIN company_avg c
WHERE  s.avg_salary > c.avg_salary
ORDER  BY s.avg_salary DESC;
在你的专用沙箱里执行打开演练场

自评要点

  • WITH 把子查询提到前面,提升可读性
  • 知道 CROSS JOIN 在这里是刻意的:把单行公司均值接到每一行上
← 上一章

分组聚合与 HAVING

下一章 →

DML 与事务控制