知识库✳

教程与手册

全部教程
  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 装帧

07
  1. 首页
  2. 课程目录
  3. 多表连接

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

多表连接

内连接、外连接、自连接与多表连接:把分散在多个表里的数据拼成一张完整报表。

学完这一章,你应该能

  • 区分内连接与外连接,知道什么时候该用左连接
  • 会写自连接解决层级问题
  • 能读懂老式 (+) 外连接语法
  • 避免笛卡尔积这类事故

7.1内连接:只保留匹配上的行

  • 连接条件写在 ON,筛选条件写在 WHERE,不要混在一起
  • JOIN 默认就是 INNER JOIN,只返回两边都匹配的行
  • 忘了写连接条件会产生笛卡尔积(行数 = 两表行数之积),这是危险事故
ANSI 写法与老式写法对照
-- ANSI 标准写法(推荐)
SELECT e.employee_id, e.last_name, d.department_name
FROM   employees e
JOIN   departments d ON d.department_id = e.department_id
ORDER  BY e.employee_id;

-- Oracle 老式写法(等价)
SELECT e.employee_id, e.last_name, d.department_name
FROM   employees e, departments d
WHERE  e.department_id = d.department_id
ORDER  BY e.employee_id;
在你的专用沙箱里执行打开演练场

7.2外连接:保留一边的全部行

当某个部门还没有员工,或者某个员工还没分配部门时,内连接会把这些行丢掉。需要「以某张表为主」的场景必须用外连接。

找出没有员工的部门
-- 左连接:保留 departments 的所有行,员工侧为 NULL 表示没人
SELECT d.department_id, d.department_name, e.employee_id, e.last_name
FROM   departments d
LEFT   JOIN employees e ON e.department_id = d.department_id
WHERE  e.employee_id IS NULL      -- 关键:用 IS NULL 反查出「没有」
ORDER  BY d.department_id;
在你的专用沙箱里执行打开演练场
老式 (+) 语法对照
-- (+) 放在「可能为空」的那一侧
SELECT d.department_id, d.department_name, e.last_name
FROM   departments d, employees e
WHERE  d.department_id = e.department_id(+)
ORDER  BY d.department_id;
在你的专用沙箱里执行打开演练场

提示 很多人以为把条件从 ON 挪到 WHERE 没区别,但在外连接里区别很大:对「可为空一侧」的列加不容忍 NULL 的过滤条件(如 WHERE e.salary > 5000),会把外连接退化成内连接,未匹配的行就此消失。反过来,像 WHERE e.employee_id IS NULL 这种反查「没有匹配」的写法,本来就该放 WHERE。判断标准只有一条:你要不要保留未匹配的行。

7.3自连接与多表连接

自连接:查员工和他的经理
SELECT e.employee_id   AS emp_id,
       e.last_name     AS employee,
       m.employee_id   AS mgr_id,
       m.last_name     AS manager
FROM   employees e
LEFT   JOIN employees m ON m.employee_id = e.manager_id
ORDER  BY e.employee_id;
在你的专用沙箱里执行打开演练场
四表连接:员工 → 部门 → 地点 → 国家
SELECT e.last_name,
       d.department_name,
       l.city,
       c.country_name
FROM   employees   e
JOIN   departments d ON d.department_id = e.department_id
JOIN   locations   l ON l.location_id   = d.location_id
JOIN   countries   c ON c.country_id    = l.country_id
WHERE  c.region_id = 2
ORDER  BY e.last_name;
在你的专用沙箱里执行打开演练场

7.4非等值连接与交叉连接

非等值连接:按工资区间匹配等级
-- 用 WITH 现造一张工资等级表,避免依赖 SCOTT 方案
WITH salgrade AS (
  SELECT 1 AS grade,     0 AS losal,  2999 AS hisal FROM dual
  UNION ALL SELECT 2,  3000,  5999 FROM dual
  UNION ALL SELECT 3,  6000,  9999 FROM dual
  UNION ALL SELECT 4, 10000, 14999 FROM dual
  UNION ALL SELECT 5, 15000, 99999 FROM dual
)
SELECT e.last_name, e.salary, s.grade, s.losal, s.hisal
FROM   employees e
JOIN   salgrade s
       ON e.salary BETWEEN s.losal AND s.hisal
ORDER  BY s.grade, e.salary;

非等值连接的条件不是 =,而是 BETWEEN、<、> 等区间判断。这里用 WITH 临时造出等级表,所以在只装了 HR 方案的库里也能直接跑。

在你的专用沙箱里执行打开演练场
交叉连接(笛卡尔积)—— 慎用
SELECT e.last_name, d.department_name
FROM   employees e
CROSS  JOIN departments d;
-- 107 × 27 = 2889 行,几乎总是写错了
在你的专用沙箱里执行打开演练场

提示 发现结果行数远超预期,先数一数表里有多少行、有没有漏写连接条件。CROSS JOIN 只在生成组合数据(如日期 × 门店)时才是有意为之。

练习

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

提示 一种是外连接加 IS NULL,另一种用 NOT EXISTS。

参考答案

-- 写法一:左连接后反查
SELECT d.department_id, d.department_name
FROM   departments d
LEFT   JOIN employees e ON e.department_id = d.department_id
WHERE  e.employee_id IS NULL
ORDER  BY 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
       )
ORDER  BY d.department_id;
在你的专用沙箱里执行打开演练场

自评要点

  • 外连接后用 IS NULL 反查,不是 = NULL
  • NOT EXISTS 不受 NULL 影响,比 NOT IN 安全

参考答案

因为过滤条件写在了 WHERE,而「没有员工」的部门在连接后 e.salary 是 NULL,
NULL > 5000 不成立(结果为 UNKNOWN),这些行被 WHERE 过滤掉了,
外连接就此退化成内连接。

若确实要「保留所有部门,但只看工资大于 5000 的员工」,
应把条件放进 ON:
... LEFT JOIN employees e
    ON e.department_id = d.department_id AND e.salary > 5000

判断标准只有一条:你要不要保留未匹配的行。

自评要点

  • 指出 NULL > 5000 不成立
  • 指出应把条件移到 ON
  • 说清 ON 与 WHERE 的语义差别

参考答案

SELECT e.employee_id,
       e.last_name AS employee,
       m.last_name AS manager
FROM   employees e
LEFT   JOIN employees m ON m.employee_id = e.manager_id
ORDER  BY e.employee_id;
在你的专用沙箱里执行打开演练场

自评要点

  • 自连接要起两个不同的别名
  • 用 LEFT JOIN 保留没有经理的员工
← 上一章

单行函数

下一章 →

分组聚合与 HAVING