0. 使用说明
- 用法:先记每节「含义」,再看「例子」。
- 目标:面试能手写左右连接、分组、子查询、窗口函数、分页;能说清执行计划和索引。
1. 基础:增删改查 + 事务
| 语句 | 含义 | 例子 |
|---|---|---|
| SELECT | 查询 | SELECT * FROM emp WHERE deptno=10; |
| INSERT | 插入 | INSERT INTO dept(deptno,dname) VALUES(50,'测试'); |
| UPDATE | 更新 | UPDATE emp SET sal=sal*1.1 WHERE deptno=20; |
| DELETE | 删除 | DELETE FROM emp WHERE empno=8888; |
| DDL | 建表/改表/删表 | CREATE TABLE t(id INT); ALTER TABLE t ADD name VARCHAR(20); DROP TABLE t; |
| DML | 增删改查 | SELECT / INSERT / UPDATE / DELETE |
| 事务 | 要么全成功,要么全回滚 | COMMIT; ROLLBACK; SAVEPOINT sp1; |
重点
DELETE可回滚,TRUNCATE不可回滚且快。WHERE必须写,否则全表更新/删除。- 事务 ACID:原子、一致、隔离、持久。
2. 连接:JOIN 全家桶
准备:dept(deptno,dname),emp(empno,ename,deptno,sal,mgr)
2.1 四种连接
| 类型 | 含义 | 例子 |
|---|---|---|
| INNER JOIN | 只保留两边都匹配 | SELECT e.ename,d.dname FROM emp e JOIN dept d ON e.deptno=d.deptno; |
| LEFT JOIN | 左表全保留,右表没有补 NULL | SELECT d.dname,e.ename FROM dept d LEFT JOIN emp e ON d.deptno=e.deptno; |
| RIGHT JOIN | 右表全保留,左表没有补 NULL | SELECT d.dname,e.ename FROM dept d RIGHT JOIN emp e ON d.deptno=e.deptno; |
| FULL OUTER JOIN | 两边都保留 | SELECT d.dname,e.ename FROM dept d FULL OUTER JOIN emp e ON d.deptno=e.deptno; |
2.2 Oracle 老式外连接 (+)
(+) 放在可能缺数据的那张表列上。
sql
-- 保留 dept 所有部门,emp 没匹配补 NULL
SELECT d.dname,e.ename
FROM dept d, emp e
WHERE d.deptno = e.deptno(+);
-- 保留 emp 所有员工,dept 没匹配补 NULL
SELECT e.ename,d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno(+);
注意:
(+)不能和 ANSI 的LEFT JOIN混用。
2.3 自连接
同一张表起两个别名,查层级关系。
sql
SELECT e.ename AS 员工, m.ename AS 经理
FROM emp e
LEFT JOIN emp m ON e.mgr = m.empno;
2.4 多表连接
sql
SELECT e.ename,d.dname,l.city
FROM emp e
JOIN dept d ON e.deptno=d.deptno
JOIN location l ON d.loc_id=l.loc_id;
口诀
- INNER 只匹配,LEFT 保左,RIGHT 保右,FULL 两边保。
- 找「左表独有」:
LEFT JOIN ... WHERE 右表主键 IS NULL。 - 找「右表独有」:
RIGHT JOIN ... WHERE 左表主键 IS NULL。
3. 复杂查询
3.1 子查询
sql
-- 工资高于平均工资的人
SELECT ename,sal FROM emp
WHERE sal > (SELECT AVG(sal) FROM emp);
3.2 相关子查询 + EXISTS / IN
sql
-- 有员工的部门
SELECT * FROM dept d
WHERE EXISTS (SELECT 1 FROM emp e WHERE e.deptno=d.deptno);
-- 没有员工的部门
SELECT * FROM dept d
WHERE NOT EXISTS (SELECT 1 FROM emp e WHERE e.deptno=d.deptno);
重点
IN适合子查询结果小。EXISTS适合大表,找到一条就返回。NOT IN子查询有 NULL 会出问题,优先用NOT EXISTS。
3.3 集合运算
| 运算 | 含义 | 例子 |
|---|---|---|
| UNION | 并集,去重 | SELECT deptno FROM emp UNION SELECT deptno FROM dept; |
| UNION ALL | 并集,不去重 | 同上,速度快 |
| INTERSECT | 交集 | SELECT deptno FROM emp INTERSECT SELECT deptno FROM dept; |
| MINUS | 差集,在 A 不在 B | SELECT deptno FROM dept MINUS SELECT deptno FROM emp; |
3.4 GROUP BY / HAVING / 聚合
sql
SELECT deptno, COUNT(*) AS 人数, AVG(sal) AS 平均工资
FROM emp
GROUP BY deptno
HAVING COUNT(*) >= 2;
重点
WHERE过滤行,HAVING过滤组。COUNT(*)算所有行,COUNT(列)忽略 NULL。- 没有
GROUP BY时,聚合函数把整表当一组。
3.5 CASE / DECODE / NVL / COALESCE
sql
-- CASE:CASE WHEN 是条件判断
SELECT ename,
CASE WHEN sal>=3000 THEN '高'
WHEN sal>=1500 THEN '中'
ELSE '低' END AS 工资等级
FROM emp;
-- DECODE(Oracle):等值映射
SELECT ename, DECODE(deptno,10,'会计',20,'研发','其他') FROM emp;
-- NVL / COALESCE 空值替换
SELECT NVL(comm,0) FROM emp; -- Oracle
SELECT COALESCE(comm,0) FROM emp; -- 标准 SQL
4. 窗口函数
语法:
sql
函数() OVER (PARTITION BY 分组列 ORDER BY 排序列)
PARTITION BY = 窗口里的分组,不减少行。
4.1 排名:ROW_NUMBER / RANK / DENSE_RANK
sql
SELECT deptno,ename,sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) rn,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) rk,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) drk
FROM emp;
| 函数 | 并列时 |
|---|---|
| ROW_NUMBER | 1,2,3 不重复 |
| RANK | 1,1,3 跳号 |
| DENSE_RANK | 1,1,2 不跳号 |
每个部门工资前 3:
sql
SELECT deptno,ename,sal
FROM (
SELECT deptno,ename,sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) rn
FROM emp
) t
WHERE rn <= 3;
4.2 SUM OVER 累计 / 分组汇总
sql
-- 每行工资占部门总工资比例
SELECT deptno,ename,sal,
SUM(sal) OVER (PARTITION BY deptno) AS 部门总工资,
SUM(sal) OVER (PARTITION BY deptno ORDER BY hiredate) AS 累计工资
FROM emp;
4.3 LAG / LEAD 前后行
sql
SELECT ename,sal,
LAG(sal) OVER (ORDER BY sal) AS 上一名工资,
LEAD(sal) OVER (ORDER BY sal) AS 下一名工资
FROM emp;
5. CTE 和递归
5.1 CTE(WITH)
sql
WITH dept_avg AS (
SELECT deptno, AVG(sal) avg_sal
FROM emp
GROUP BY deptno
)
SELECT e.ename,e.sal,d.avg_sal
FROM emp e
JOIN dept_avg d ON e.deptno=d.deptno
WHERE e.sal > d.avg_sal;
5.2 递归
标准 SQL:
sql
WITH RECURSIVE nums(n) AS (
SELECT 1
UNION ALL
SELECT n+1 FROM nums WHERE n<5
)
SELECT * FROM nums;
Oracle 老写法:
sql
SELECT LEVEL FROM dual CONNECT BY LEVEL <= 5;
6. 分页
Oracle ROWNUM
sql
SELECT *
FROM (
SELECT e.*, ROWNUM rn
FROM (SELECT * FROM emp ORDER BY sal DESC) e
WHERE ROWNUM <= 20
)
WHERE rn > 10;
标准 OFFSET FETCH
sql
SELECT * FROM emp
ORDER BY sal DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
MySQL
sql
SELECT * FROM emp ORDER BY sal DESC LIMIT 10 OFFSET 10;
重点:分页必须先排序,再取范围。
7. 日期 / 字符串函数
| 类型 | Oracle | MySQL / 标准 |
|---|---|---|
| 当前时间 | SYSDATE |
NOW() |
| 格式化 | TO_CHAR(SYSDATE,'YYYY-MM-DD') |
DATE_FORMAT(NOW(),'%Y-%m-%d') |
| 转日期 | TO_DATE('2024-01-01','YYYY-MM-DD') |
STR_TO_DATE |
| 加减月 | ADD_MONTHS(SYSDATE,1) |
DATE_ADD |
| 取年 | EXTRACT(YEAR FROM SYSDATE) |
YEAR(NOW()) |
| 截取 | SUBSTR('abcdef',1,3) |
SUBSTRING |
| 查找 | INSTR('abcdef','c') |
LOCATE |
| 拼接 | CONCAT('a','b') 或 ` |
|
| 去空格 | TRIM(' a ') |
TRIM |
| 大小写 | UPPER / LOWER |
同 |
| 替换 | REPLACE('abc','b','x') |
同 |
| 长度 | LENGTH('abc') |
CHAR_LENGTH |
8. 性能:执行计划、索引、SQL 重写
8.1 执行计划
sql
EXPLAIN PLAN FOR
SELECT * FROM emp WHERE deptno=10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
看:全表扫描、索引扫描、连接方式、成本。
8.2 索引
- 在
WHERE、JOIN、ORDER BY常用列建索引。 - 避免在索引列上使用函数:
WHERE TO_CHAR(date_col)='2024'会失效。 - 复合索引注意最左前缀。
- 索引不是越多越好,影响插入更新。
8.3 统计信息
sql
-- Oracle
EXEC DBMS_STATS.GATHER_TABLE_STATS('SCHEMA','EMP');
统计信息新鲜,优化器才能选对执行计划。
8.4 避免全表扫描 / SQL 重写
- 只取需要列,别
SELECT *。 - 用绑定变量,减少硬解析。
- 大表分区。
EXISTS代替IN(大表)。UNION ALL代替UNION(不需要去重时)。- 子查询改 JOIN。
- 分页先排序再取范围。
9. 面试手写速练
9.1 左右连接
sql
-- 所有部门及员工
SELECT d.dname,e.ename
FROM dept d LEFT JOIN emp e ON d.deptno=e.deptno;
-- 没有员工的部门
SELECT d.*
FROM dept d LEFT JOIN emp e ON d.deptno=e.deptno
WHERE e.empno IS NULL;
-- 没有部门的员工
SELECT e.*
FROM emp e LEFT JOIN dept d ON e.deptno=d.deptno
WHERE d.deptno IS NULL;
9.2 多表 + 分组 + 子查询
sql
SELECT d.dname, COUNT(e.empno) AS 人数, AVG(e.sal) AS 平均工资
FROM dept d
LEFT JOIN emp e ON d.deptno=e.deptno
GROUP BY d.dname;
9.3 窗口函数排名、累计
sql
-- 每个部门工资前 3
SELECT deptno,ename,sal
FROM (
SELECT deptno,ename,sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) rn
FROM emp
) t
WHERE rn <= 3;
-- 累计工资
SELECT ename,sal,
SUM(sal) OVER (ORDER BY hiredate) AS 累计
FROM emp;
9.4 分页
sql
SELECT * FROM (
SELECT e.*, ROWNUM rn
FROM (SELECT * FROM emp ORDER BY sal DESC) e
WHERE ROWNUM <= 20
)
WHERE rn > 10;
10. 一句话口诀
- 连接:INNER 只匹配,LEFT 保左,RIGHT 保右,FULL 两边保。
- 外连接条件 :右表条件放
ON保左表;放WHERE退化成内连接。 - 分组 :
WHERE过滤行,HAVING过滤组。 - 窗口 :
PARTITION BY分组不合并行,ROW_NUMBER不重复,RANK跳号,DENSE_RANK不跳。 - 分页:先排序,再取范围。
- 性能 :看执行计划,建索引,避函数,用绑定变量,少
SELECT *。
1 小时过一遍,2 小时把第 9 节手写一遍,面试重点基本覆盖。