多表关联查询是在关系型数据库中,通过使用多个表之间的关联条件来查询相关联的数据。这种查询通常用于解决复杂的数据关系问题,涉及多个表之间的关联和数据整合。这里写自定义目录标题)
在多表关联查询时,如果多张表之间存在同名的列,则必须使用表名来限定列的引用。与列别名类似,可以给表指定一个别名,使用简短的表别名可以替代原有较长的表名称,可以大大缩减语句的长度;
--指定emp表别名为e,dept表别名为d,查询岗位MANAGER的员工信息及部门
select e.empno as 员工编号, e.ename as 员工名称, d.dname as 部门名称
from emp e,dept d
where e.deptno=d.deptno and e.job='MANAGER';
使用表的别名的注意事项如下:
表的别名在FROM子句中定义,别名放在表名之后,它们之间用空格隔开。
别名一经定义,在整个查询语句中就只能使用表的别名而不能再使用表名。
表的别名只在所定义的查询语句中有效。
应该选择有意义的别名,表的别名最长为30个字符,但越短越好。
2.2. 内连接
内连接是一种常用的多表关联查询方式,一般使用关键字INNER JOIN来实现。INNER关键字可以省略,当只使用JOIN关键字时,语句只表示内连接操作。内连接使用JOIN指定用于连接的两张表,使用ON指定连接表的连接条件。内连接只显示与连接条件匹配的行,语法格式如下:
SELECT columns_list
FROM table_name1 INNER JOIN table_name2 ON join_condition;
columns_list:字段列表。
table_name1和table_name2:两张要实现内连接的表。
join_condition:实现内连接的条件表达式。
--通过deptno字段来内连接emp表和dept表,并检索这两张表中相关字段的信息
select e.empno as 员工编号, e.ename as 员工名称, d.dname as 部门
from emp e inner join dept d on e.deptno=d.deptno;
2.3. 外连接
外连接分为以下3类:
左外连接:关键字为LEFT OUTER JOIN或LEFT JOIN。
右外连接:关键字为RIGHT OUTER JOIN或RIGHT JOIN。
完全外连接:关键字为FULL OUTER JOIN或FULL JOIN。
外连接不只列出与连接条件匹配的行,还能够列出左表(左外连接时)、右表(右外连接时)或两张表(全部外连接时)中所有符合搜索条件的数据行。
2.3.1. 左外连接
左外连接的查询结果中不仅包含满足连接条件的数据行,还包含左表中不满足连接条件的数据行。左外连接以左边的表为主表,显示主表所有数据。
--向emp表中插入新记录(没有为deptno和dname列插入值,即它们的值为NULL)
insert into emp(empno,ename,job) values(9527,'EAST','SALESMAN');
--通过deptno进行emp表和dept表的左外连接
select e.empno,e.ename,e.job,d.deptno,d.dname
from emp e left join dept d on e.deptno=d.deptno;
可以看到deptno为空的数据也被查询出来,左外连接就是以左边的表emp为主表,显示emp主表的所有记录,以条件e.deptno=d.deptno匹配dept表中的数据,即使EMPNO=9527的记录没有匹配上dept表的记录也会显示出来;
2.3.2. 右外连接
右外连接的查询结果中不仅包含满足连接条件的数据行,还包含右表中不满足连接条件的数据行。右外连接以右边的表为主表,显示主表所有数据。
--通过deptno进行emp表和dept表的右外连接
select e.empno,e.ename,e.job,d.deptno,d.dname
from emp e right join dept d on e.deptno=d.deptno;
在外连接中也可以使用外连接的连接运算符,外连接的连接运算符为"(+)",该连接运算符可以放在等号的左边,也可以放在等号的右边,但一定要放在显示较少行(完全满足连接条件行)的一端;
上面的右外连接查询语句还可以这么写,代码如下:
select e.empno,e.ename,e.job,d.deptno,d.dname
from emp e , dept d
where e.deptno(+)=d.deptno;
(+)在哪个表的列名后面,则另一个表为主表,e.deptno(+)=d.deptno则dept为主表,即语句为右连接,e.deptno=d.deptno(+)则emp为主表,即语句为左连接;
使用"(+)"操作符时应注意以下3点:
当使用"(+)"操作符执行外连接时,如果在WHERE子句中包含多个条件,则必须在所有条件中都包含"(+)"操作符。
"(+)"操作符只适用于列,而不能用在表达式上。
"(+)"操作符不能与ON和IN操作符一起使用。
2.3.3. 完全外连接
在执行完全外连接时,Oracle会执行一个完整的左外连接和右外连接查询,然后将查询结果合并,并消除重复的记录行。
select e.empno,e.ename,e.job,d.deptno,d.dname
from emp e full join dept d
on e.deptno=d.deptno;
2.3.4. 自然连接
自然连接是指在检索多张表时,Oracle会将第一张表中的列与第二张表中具有相同名称的列进行自动连接。在自然连接中,用户不需要明确指定进行连接的列,这个任务由Oracle系统自动完成,自然连接使用NATURAL JOIN关键字。
--查询工资(sal字段)高于1000的记录,并实现emp表与dept表的自然连接
select empno,ename,job,dname
from emp natural join dept
where sal > 1000;
自然连接强制要求表之间具有相同的列名称,容易在设计表时出现不可预知的错误,所以在实际应用系统开发中很少用到自然连接。
2.3.5. 自连接
自连接主要用在自参照表上,显示上下级关系或者层次关系。自参照表是指在同一张表的不同列之间具有参照关系或主从关系的表。自连接是在同一张表之间的连接查询,必须定义表别名。
--查询所有管理者所管理的下属员工信息
select em2.ename 上层管理者,em1.ename as 下属员工
from emp em1 left join emp em2
on em1.mgr=em2.empno
order by em1.mgr;
2.3.6. 交叉连接
交叉连接实际上就是不需要任何连接条件的连接,它使用CROSS JOIN关键字来实现,其语法格式如下:
SELECT colums_list
FROM table_name1 CROSS JOIN table_name2
colums_list:字段列表。
table_name1和table_name2:两张实现交叉连接的表。
交叉连接的执行结果是一个笛卡尔积,这种查询结果是非常冗余的,但可以通过WHERE子句来过滤出有用的记录信息。
笛卡尔积(Cartesian product)是指在没有明确指定连接条件的情况下,将两个或多个表中的所有行进行组合。这种组合操作不考虑表之间的关联关系,只是简单地将每一行与其他表中的每一行进行组合。
--计算dept表与emp表的记录两两组合的行数
select count(*) from dept cross join emp;
可以看到dept表中有4条数据,emp表中有15条数据,交叉连接将dept表中的每一条数据都与emp表中数据进行组合,最终结果为4 * 15=60条数据;
Oracle 多表关联:主表选择,业务优先 + 数据量优化为辅
一、先分清两个概念:SQL 逻辑主表(业务层面)、执行计划驱动表(Oracle 优化器层面)
- 逻辑主表(写 SQL 时自己定义,业务决定)
指你的查询核心业务对象,是这次 SQL 要查询的主体数据,所有其他表都是用来补充、过滤这个主体的信息。
判断规则(业务优先):
你最终要展示 / 统计的核心实体 = 逻辑主表
例 1:查「所有订单,附带下单用户、商品名称」
核心是订单,订单表为主表,用户表、商品表只是辅助补全字段。
例 2:查「所有用户,统计每个用户的下单总数」
核心是用户,用户表为主表,订单表做统计关联。
左连接场景:LEFT JOIN 左边表天然是业务主表
sql
-- 要查出全部客户,没订单也要显示,客户是业务主表
select * from customer c
left join orders o on c.cust_id = o.cust_id;
如果你把 orders 放左边、customer 右连接,业务语义直接错乱,哪怕性能再好也不能这么写。
业务过滤条件主要落在哪张表,那张就是主表
where 条件大量筛选 A 表,只拿 B 表的字段展示,A 是主表。
核心结论:业务语义决定你 SQL 里谁是逻辑主表,不能为了数据量随便调换左右表、连接类型,否则查询结果错误。
二、执行计划驱动表(Oracle CBO 优化器自动选,数据量 / 索引决定性能)
写完 SQL 后,Oracle 优化器会根据统计信息、数据量、索引,自动选一张驱动表(嵌套循环、哈希连接都会有驱动表),这是物理执行层面的表,和你 SQL 写的主表不一定一致。
优化器选驱动表的核心规则(数据量、索引主导):
嵌套循环 NESTED LOOPS
驱动表:数据量小、过滤后行数少 的表。
原理:小表循环遍历,去大表索引匹配。
适合:驱动表过滤后几百 / 几千行,被驱动表关联字段有索引。
哈希连接 HASH JOIN(大表关联默认)
驱动表(建哈希表的表):数据量更小 的表。
原理:小表构建哈希内存表,大表逐行匹配哈希;小表越小,内存占用越低、速度越快。
排序合并 SORT MERGE JOIN
两张表都会排序,数据量差距影响较小,一般不推荐。
数据量对性能的影响(实操经验)
小表 1 万行,大表 1000 万行:优化器必然选小表做驱动表,性能差距几十上百倍。
两张表都是超大表(千万级):哈希连接为主,尽量让过滤后行数少的表当驱动。
大表关联字段无索引:嵌套循环失效,强制走哈希 / 合并,性能暴跌。
三、业务 vs 数据量 冲突时怎么处理?(最重要实操)
场景 1:业务必须 A 当逻辑主表(LEFT JOIN A LEFT JOIN B),但 A 数据量巨大,B 很小
不能改连接左右(改了会丢 A 表数据),通过两种方式优化性能:
给 B 表关联字段建索引,优化器会自动调整执行逻辑,用小表 B 辅助匹配大表 A;
在 where 里给 A 表加精准过滤条件,缩小 A 表扫描行数,减少驱动成本;
加 hint 强制执行方式(不推荐优先用,临时调优可用):/*+ LEADING(B) */ 引导优化器以 B 为驱动表,不改变业务查询结果。
场景 2:内连接 INNER JOIN A JOIN B(无左右之分,结果等价)
内连接没有业务左右限制,直接让数据量小、过滤性好的表作为逻辑前置表,优化器更容易生成高效执行计划。
sql
-- 内连接两种写法结果完全一致,推荐小表放前面
select * from small_table s
inner join big_table b on s.id = b.s_id;
四、快速总结
写 SQL 定义逻辑主表:业务第一
看查询核心实体、连接类型(LEFT 左边固定为主表)、业务过滤条件;
业务需求决定结果正确性,正确性优先级 > 性能。
Oracle 执行时的驱动表:数据量、索引、统计信息决定
优化器自动选择,目的提升查询速度,不改变最终查询结果。
取舍方案:
LEFT/RIGHT 外连接:严格按业务写死主表位置,靠索引、过滤条件优化性能;
INNER JOIN 内连接:无结果差异,优先把数据量小的表写在前,方便优化器。
补充实操案例
需求:查询全部会员(必须全部展示,无订单也要出来),带出订单信息
业务规则:会员表为主表,必须member left join order,不能调换。
数据情况:会员表 500 万行,订单表 200 万行。
优化手段:给订单表 member_id 建索引,where 中过滤会员注册时间缩小扫描范围,不需要修改表顺序。
数据量大小:
较小的表:通常选择较小的表作为主表(或驱动表),因为较小的表可以更快地加载到内存中,从而减少查询的整体开销。
过滤条件:
有过滤条件的表:如果某个表在查询条件中有WHERE子句的过滤条件,那么这个表可以作为主表,因为过滤后的数据量会减少,从而减少关联操作的复杂度。
索引使用:
有索引的表:通常选择有索引的表作为主表,因为索引可以显著提高查询速度。特别是在使用JOIN时,有索引的列可以加速连接操作。