Oracle子查询 #

一、子查询概述 #

1.1 什么是子查询 #

子查询(Subquery)是嵌套在另一个查询中的SELECT语句。子查询可以出现在SELECT、FROM、WHERE、HAVING等子句中。

text
子查询分类
├── 按返回结果
│   ├── 单行子查询:返回一行
│   ├── 多行子查询:返回多行
│   └── 多列子查询:返回多列
├── 按与外查询关系
│   ├── 非相关子查询:独立执行
│   └── 相关子查询:依赖外查询
└── 按位置
    ├── WHERE子句
    ├── FROM子句(内联视图)
    ├── SELECT子句
    └── HAVING子句

1.2 示例表结构 #

sql
-- 创建示例表
CREATE TABLE employees (
    employee_id NUMBER(6) PRIMARY KEY,
    first_name VARCHAR2(50),
    last_name VARCHAR2(50),
    department_id NUMBER(4),
    salary NUMBER(8,2),
    manager_id NUMBER(6),
    hire_date DATE
);

CREATE TABLE departments (
    department_id NUMBER(4) PRIMARY KEY,
    department_name VARCHAR2(30),
    location_id NUMBER(4)
);

-- 插入测试数据
INSERT INTO employees VALUES (1, 'John', 'Doe', 10, 8000, NULL, TO_DATE('2020-01-15', 'YYYY-MM-DD'));
INSERT INTO employees VALUES (2, 'Jane', 'Smith', 10, 9000, 1, TO_DATE('2019-03-20', 'YYYY-MM-DD'));
INSERT INTO employees VALUES (3, 'Bob', 'Johnson', 20, 7000, 1, TO_DATE('2021-06-10', 'YYYY-MM-DD'));
INSERT INTO employees VALUES (4, 'Alice', 'Williams', 20, 12000, 1, TO_DATE('2018-11-05', 'YYYY-MM-DD'));
INSERT INTO employees VALUES (5, 'Charlie', 'Brown', 30, 6500, 2, TO_DATE('2022-02-28', 'YYYY-MM-DD'));

INSERT INTO departments VALUES (10, 'IT', 1);
INSERT INTO departments VALUES (20, 'Sales', 2);
INSERT INTO departments VALUES (30, 'HR', 3);

二、单行子查询 #

2.1 基本用法 #

sql
-- 单行子查询:返回一行结果
-- 使用单行比较运算符:=, >, <, >=, <=, <>

-- 查询薪资高于平均薪资的员工
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > (
    SELECT AVG(salary) FROM employees
);

-- 查询与John同部门的员工
SELECT employee_id, first_name, department_id
FROM employees
WHERE department_id = (
    SELECT department_id 
    FROM employees 
    WHERE first_name = 'John'
);

-- 查询薪资最高的员工
SELECT employee_id, first_name, salary
FROM employees
WHERE salary = (
    SELECT MAX(salary) FROM employees
);

2.2 在HAVING中使用 #

sql
-- 在HAVING中使用单行子查询
-- 查询平均薪资高于公司平均薪资的部门
SELECT 
    department_id,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > (
    SELECT AVG(salary) FROM employees
);

-- 查询员工数最多的部门
SELECT 
    department_id,
    COUNT(*) AS employee_count
FROM employees
GROUP BY department_id
HAVING COUNT(*) = (
    SELECT MAX(cnt) 
    FROM (
        SELECT COUNT(*) AS cnt
        FROM employees
        GROUP BY department_id
    )
);

2.3 在SELECT中使用 #

sql
-- 在SELECT中使用单行子查询
SELECT 
    employee_id,
    first_name,
    salary,
    (SELECT AVG(salary) FROM employees) AS avg_salary,
    salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;

-- 查询部门名称
SELECT 
    e.employee_id,
    e.first_name,
    (SELECT d.department_name 
     FROM departments d 
     WHERE d.department_id = e.department_id) AS department_name
FROM employees e;

三、多行子查询 #

3.1 IN运算符 #

sql
-- 多行子查询:返回多行结果
-- 使用多行运算符:IN, ANY, ALL

-- IN:匹配列表中的任一值
SELECT employee_id, first_name, department_id
FROM employees
WHERE department_id IN (
    SELECT department_id 
    FROM departments 
    WHERE location_id IN (1, 2)
);

-- NOT IN:不匹配列表中的任何值
SELECT employee_id, first_name
FROM employees
WHERE department_id NOT IN (
    SELECT department_id 
    FROM departments 
    WHERE location_id = 1
);

-- 注意:NOT IN与NULL
-- 如果子查询返回NULL,NOT IN不会返回任何结果
SELECT employee_id, first_name
FROM employees
WHERE department_id NOT IN (
    SELECT department_id 
    FROM departments 
    WHERE department_id IS NOT NULL  -- 过滤NULL
);

3.2 ANY运算符 #

sql
-- ANY:与子查询返回的任一值比较
-- > ANY:大于最小值
-- < ANY:小于最大值
-- = ANY:等价于IN

-- 查询薪资高于IT部门任一员工的员工
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > ANY (
    SELECT salary 
    FROM employees 
    WHERE department_id = 10
);

-- 等价于
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > (
    SELECT MIN(salary) 
    FROM employees 
    WHERE department_id = 10
);

-- = ANY 等价于 IN
SELECT employee_id, first_name
FROM employees
WHERE department_id = ANY (
    SELECT department_id FROM departments WHERE location_id = 1
);

3.3 ALL运算符 #

sql
-- ALL:与子查询返回的所有值比较
-- > ALL:大于最大值
-- < ALL:小于最小值

-- 查询薪资高于IT部门所有员工的员工
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > ALL (
    SELECT salary 
    FROM employees 
    WHERE department_id = 10
);

-- 等价于
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > (
    SELECT MAX(salary) 
    FROM employees 
    WHERE department_id = 10
);

-- 查询薪资低于IT部门所有员工的员工
SELECT employee_id, first_name, salary
FROM employees
WHERE salary < ALL (
    SELECT salary 
    FROM employees 
    WHERE department_id = 10
);

四、多列子查询 #

4.1 成对比较 #

sql
-- 多列子查询:返回多列结果

-- 成对比较:多列同时匹配
SELECT employee_id, first_name, department_id, salary
FROM employees
WHERE (department_id, salary) IN (
    SELECT department_id, MAX(salary)
    FROM employees
    GROUP BY department_id
);

-- 查询与John同部门且薪资相同的员工
SELECT employee_id, first_name, department_id, salary
FROM employees
WHERE (department_id, salary) = (
    SELECT department_id, salary
    FROM employees
    WHERE first_name = 'John'
);

4.2 非成对比较 #

sql
-- 非成对比较:各列独立比较
SELECT employee_id, first_name, department_id, salary
FROM employees
WHERE department_id IN (
    SELECT department_id FROM employees WHERE first_name = 'John'
)
AND salary IN (
    SELECT salary FROM employees WHERE first_name = 'John'
);

五、相关子查询 #

5.1 基本相关子查询 #

sql
-- 相关子查询:引用外查询的列

-- 查询薪资高于本部门平均薪资的员工
SELECT e.employee_id, e.first_name, 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
);

-- 查询每个部门薪资最高的员工
SELECT e.employee_id, e.first_name, e.department_id, e.salary
FROM employees e
WHERE e.salary = (
    SELECT MAX(e2.salary)
    FROM employees e2
    WHERE e2.department_id = e.department_id
);

5.2 EXISTS运算符 #

sql
-- EXISTS:检查子查询是否返回行
-- 只要子查询返回至少一行,EXISTS就返回TRUE

-- 查询有员工的部门
SELECT d.department_id, d.department_name
FROM departments d
WHERE EXISTS (
    SELECT 1
    FROM employees e
    WHERE e.department_id = d.department_id
);

-- NOT EXISTS:检查子查询是否不返回行
-- 查询没有员工的部门
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
);

-- EXISTS vs IN
-- EXISTS:适合子查询结果集大的情况
-- IN:适合子查询结果集小的情况

-- 使用EXISTS查询有下属的员工
SELECT e.employee_id, e.first_name
FROM employees e
WHERE EXISTS (
    SELECT 1
    FROM employees e2
    WHERE e2.manager_id = e.employee_id
);

5.3 相关更新和删除 #

sql
-- 相关更新
UPDATE employees e
SET salary = (
    SELECT AVG(e2.salary)
    FROM employees e2
    WHERE e2.department_id = e.department_id
)
WHERE EXISTS (
    SELECT 1
    FROM departments d
    WHERE d.department_id = e.department_id
    AND d.department_name = 'IT'
);

-- 相关删除
DELETE FROM employees e
WHERE NOT EXISTS (
    SELECT 1
    FROM departments d
    WHERE d.department_id = e.department_id
);

六、FROM子句子查询 #

6.1 内联视图 #

sql
-- FROM子句中的子查询称为内联视图

-- 查询各部门平均薪资
SELECT e.department_id, e.avg_salary, d.department_name
FROM (
    SELECT 
        department_id,
        AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
) e
JOIN departments d ON e.department_id = d.department_id;

-- 分页查询
SELECT *
FROM (
    SELECT 
        e.*,
        ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
    FROM employees e
)
WHERE rn BETWEEN 3 AND 5;

6.2 复杂内联视图 #

sql
-- 复杂的内联视图
SELECT 
    dept_stats.department_id,
    dept_stats.employee_count,
    dept_stats.avg_salary,
    d.department_name
FROM (
    SELECT 
        department_id,
        COUNT(*) AS employee_count,
        AVG(salary) AS avg_salary,
        MAX(salary) AS max_salary,
        MIN(salary) AS min_salary
    FROM employees
    GROUP BY department_id
) dept_stats
JOIN departments d ON dept_stats.department_id = d.department_id
WHERE dept_stats.avg_salary > 8000;

七、WITH子句 #

7.1 公用表表达式 #

sql
-- WITH子句:定义公用表表达式(CTE)

-- 基本用法
WITH dept_avg AS (
    SELECT 
        department_id,
        AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
)
SELECT 
    e.employee_id,
    e.first_name,
    e.salary,
    d.avg_salary
FROM employees e
JOIN dept_avg d ON e.department_id = d.department_id
WHERE e.salary > d.avg_salary;

-- 多个CTE
WITH 
dept_avg AS (
    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
),
high_salary AS (
    SELECT employee_id, first_name, salary, department_id
    FROM employees
    WHERE salary > 10000
)
SELECT 
    h.employee_id,
    h.first_name,
    h.salary,
    d.avg_salary
FROM high_salary h
JOIN dept_avg d ON h.department_id = d.department_id;

7.2 递归CTE #

sql
-- 递归CTE(Oracle 11g R2+)

-- 查询员工层级结构
WITH emp_hierarchy AS (
    -- 基础查询:顶级员工
    SELECT 
        employee_id,
        first_name,
        manager_id,
        1 AS level
    FROM employees
    WHERE manager_id IS NULL
    
    UNION ALL
    
    -- 递归查询:下属员工
    SELECT 
        e.employee_id,
        e.first_name,
        e.manager_id,
        h.level + 1 AS level
    FROM employees e
    JOIN emp_hierarchy h ON e.manager_id = h.employee_id
)
SELECT 
    employee_id,
    first_name,
    manager_id,
    level,
    LPAD(' ', (level - 1) * 2, ' ') || first_name AS hierarchy
FROM emp_hierarchy
ORDER BY level, employee_id;

-- 生成数字序列
WITH nums AS (
    SELECT 1 AS n FROM DUAL
    UNION ALL
    SELECT n + 1 FROM nums WHERE n < 10
)
SELECT n FROM nums;

八、标量子查询 #

8.1 标量子查询特点 #

sql
-- 标量子查询:返回单个值(一行一列)

-- 在SELECT中使用
SELECT 
    employee_id,
    first_name,
    salary,
    (SELECT department_name 
     FROM departments 
     WHERE department_id = e.department_id) AS department_name
FROM employees e;

-- 在ORDER BY中使用
SELECT employee_id, first_name, salary
FROM employees e
ORDER BY (SELECT department_name 
          FROM departments 
          WHERE department_id = e.department_id);

-- 在CASE中使用
SELECT 
    employee_id,
    first_name,
    salary,
    CASE 
        WHEN salary > (SELECT AVG(salary) FROM employees) THEN 'High'
        ELSE 'Low'
    END AS salary_level
FROM employees;

8.2 标量子查询优化 #

sql
-- 标量子查询可能导致性能问题
-- 每行都会执行一次子查询

-- 优化:使用JOIN替代
-- 原查询
SELECT 
    e.employee_id,
    e.first_name,
    (SELECT d.department_name 
     FROM departments d 
     WHERE d.department_id = e.department_id) AS department_name
FROM employees e;

-- 优化后
SELECT 
    e.employee_id,
    e.first_name,
    d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;

九、子查询优化 #

9.1 子查询展开 #

sql
-- Oracle优化器会尝试展开子查询

-- 原查询
SELECT * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1);

-- 优化器可能转换为JOIN
SELECT e.* 
FROM employees e, departments d
WHERE e.department_id = d.department_id
AND d.location_id = 1;

-- 查看执行计划
EXPLAIN PLAN FOR
SELECT * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1);

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

9.2 子查询优化提示 #

sql
-- 使用提示控制子查询处理

-- NO_UNNEST:禁止子查询展开
SELECT /*+ NO_UNNEST */ * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1);

-- UNNEST:鼓励子查询展开
SELECT /*+ UNNEST */ * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1);

-- PUSH_SUBQ:尽早执行子查询
SELECT /*+ PUSH_SUBQ */ * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1);

十、子查询常见问题 #

10.1 单行子查询返回多行 #

sql
-- 错误:单行子查询返回多行
-- ORA-01427: single-row subquery returns more than one row

SELECT employee_id, first_name
FROM employees
WHERE department_id = (
    SELECT department_id FROM employees  -- 返回多行
);

-- 解决:使用IN或限制返回一行
SELECT employee_id, first_name
FROM employees
WHERE department_id IN (
    SELECT department_id FROM employees
);

-- 或
SELECT employee_id, first_name
FROM employees
WHERE department_id = (
    SELECT department_id FROM employees WHERE ROWNUM = 1
);

10.2 子查询返回NULL #

sql
-- 子查询返回NULL时的问题

-- NOT IN与NULL
SELECT employee_id, first_name
FROM employees
WHERE department_id NOT IN (SELECT NULL FROM DUAL);
-- 结果:无数据返回

-- 解决:过滤NULL
SELECT employee_id, first_name
FROM employees
WHERE department_id NOT IN (
    SELECT department_id FROM departments WHERE department_id IS NOT NULL
);

-- 或使用NOT EXISTS
SELECT e.employee_id, e.first_name
FROM employees e
WHERE NOT EXISTS (
    SELECT 1 FROM departments d WHERE d.department_id = e.department_id
);

10.3 相关子查询性能 #

sql
-- 相关子查询可能导致性能问题
-- 每行都执行一次子查询

-- 原查询
SELECT e.employee_id, e.first_name
FROM employees e
WHERE EXISTS (
    SELECT 1 FROM departments d 
    WHERE d.department_id = e.department_id 
    AND d.location_id = 1
);

-- 优化:使用JOIN
SELECT DISTINCT e.employee_id, e.first_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1;

十一、总结 #

子查询要点:

类型 特点 运算符
单行子查询 返回一行 =, >, <, >=, <=, <>
多行子查询 返回多行 IN, ANY, ALL
相关子查询 引用外查询 EXISTS
多列子查询 返回多列 成对比较

最佳实践:

  1. 使用WITH子句提高可读性
  2. 注意NOT IN与NULL的问题
  3. 相关子查询考虑使用JOIN优化
  4. 使用EXISTS替代IN处理大数据集
  5. 查看执行计划优化子查询

下一步,让我们学习高级函数!

最后更新:2026-03-27