SQL
写在开头
很早以前就已经接触到了SQL,但一直只会CTRL+C/V,遂决定自己研究一下。
用过的命令都会写在这里。
sql命令结尾为;
运行环境:Ubuntu 20.04 大部分使用MySQL
25.6
运行环境也可能在windows,大差不差
基本
登录&退出
mysql -u root -p yourpassword
本地登录
mysql -h -p port -uroot -p yourpsw
远程登录
exit or quit or /q
退出
导入
SOURCE 文件路径
支持正反斜杠,不用加引号。
增删改查
数据库操作
create database db_name; #增
drop database db_name; #删
alter database exampleDb charset utf8; #改
show databases; #查
数据表操作
#创建表
create table table_name();
#查所有表
show tables;
#查表结构
describle table_name;
设置全局变量
set global 变量=值;
eg.
set global validate_password.policy=LOW;
设定密码安全政策为低
正式
26.4.10 终于正式开始学习SQL啦,相隔快4年了,我能否在期末达成不挂科的好成绩呢,拭目以待吧^ ^
SQL 学习笔记
一、基础查询
1. SELECT 基础
SELECT 列名1, 列名2 FROM 表名;
SELECT * FROM 表名; -- * 表示所有列
2. WHERE 条件过滤
SELECT * FROM 表名 WHERE 条件;
- 文本需要用单引号:
city = 'SQL City' - 数字直接写:
date = 20180115 - 多个条件用
AND或OR
3. AS 起别名
给表或列起临时名字,简化书写。AS 可以省略。
FROM drivers_license AS dl -- 表别名
SELECT height AS h FROM ... -- 列别名
FROM drivers_license dl -- 省略 AS
二、高级过滤
4. 模糊匹配 LIKE
WHERE name LIKE '%John%' -- 名字里包含 John
| 写法 | 含义 |
|---|---|
'%John%' |
包含 “John”(前后可有任意字符) |
'John%' |
以 “John” 开头 |
'%John' |
以 “John” 结尾 |
'J_n' |
“J” + 任意单个字符 + “n” |
%= 任意多个字符_= 单个字符
5. BETWEEN ... AND ...(范围查询)
判断某个值是否在指定范围内(包含边界)。
WHERE height BETWEEN 65 AND 67;
等价于:
WHERE height >= 65 AND height <= 67;
适用类型:数字、日期(整数格式)
6. IN 与 = 的区别
| 运算符 | 子查询返回 | 适用场景 |
|---|---|---|
= |
必须是单个值 | 标量子查询 |
IN |
可以是多个值 | 列表子查询 |
-- ✅ 正确
WHERE sno IN (SELECT sno FROM sc)
-- ❌ 错误(如果子查询返回多行)
WHERE sno = (SELECT sno FROM sc)
7. IS NULL / IS NOT NULL(空值判断)
在 SQL 中,NULL 不等于任何值,包括它自己。判断空值必须用 IS NULL 或 IS NOT NULL。
-- ✅ 正确
WHERE price IS NULL
WHERE price IS NOT NULL
-- ❌ 错误(永远返回空)
WHERE price = NULL
WHERE price != NULL
| 错误写法 | 正确写法 |
|---|---|
= NULL |
IS NULL |
!= NULL |
IS NOT NULL |
三、排序与分组
8. 排序 ORDER BY
SELECT * FROM 表名
ORDER BY 列名 ASC; -- 升序(默认)
ORDER BY 列名 DESC; -- 降序
9. 分组 GROUP BY
SELECT 列名, COUNT(*)
FROM 表名
GROUP BY 列名;
注意:GROUP BY 单独使用时(不带聚合函数),效果等同于 SELECT DISTINCT。
-- 以下两条语句结果相同
SELECT City FROM customers GROUP BY City;
SELECT DISTINCT City FROM customers;
10. HAVING vs WHERE
| 关键字 | 执行时机 | 能否用聚合函数 |
|---|---|---|
WHERE |
分组之前过滤行 | ❌ 不能 |
HAVING |
分组之后过滤组 | ✅ 能 |
SELECT person_id, COUNT(*) AS times
FROM facebook_event_checkin
WHERE event_name = 'SQL Symphony Concert' -- 先过滤行
GROUP BY person_id -- 再分组
HAVING COUNT(*) = 3; -- 最后过滤组
11. DISTINCT 去重
去掉查询结果中的重复行。
SELECT DISTINCT 列名1, 列名2 FROM 表名;
⚠️ 注意:DISTINCT 作用于所有选中列的组合,不是只作用于第一列。
| 方法 | 适用场景 | 推荐度 |
|---|---|---|
SELECT DISTINCT |
单纯去重,不需要聚合函数 | ⭐⭐⭐⭐⭐ |
GROUP BY |
需要同时使用聚合函数(如 COUNT、AVG) | ⭐⭐⭐⭐ |
四、聚合函数
| 函数 | 作用 |
|---|---|
COUNT(*) |
统计行数(包括 NULL) |
COUNT(列名) |
统计该列非 NULL 的行数 |
SUM(列) |
求和 |
AVG(列) |
求平均值 |
MAX(列) |
求最大值 |
MIN(列) |
求最小值 |
重要规则:聚合函数不能放在 WHERE 中,只能放在 SELECT 或 HAVING 中。
-- ❌ 错误
SELECT * FROM sc WHERE AVG(grade) > 75;
-- ✅ 正确
SELECT sno, AVG(grade) FROM sc GROUP BY sno HAVING AVG(grade) > 75;
五、表结构与约束
12. PRIMARY KEY(主键)
- 每张表中唯一标识一行数据的列
- 不能重复,不能为空
- 例如:
person.id、drivers_license.id
13. FOREIGN KEY(外键)
- 指向另一张表的主键
- 表示两张表之间的关联关系
- 例如:
person.license_id指向drivers_license.id
14. 查看表结构(SQLite)
PRAGMA table_info(表名); -- 简洁版
SELECT sql FROM sqlite_master WHERE name = '表名'; -- 完整版
六、表关联(JOIN)
15. 基本 JOIN
SELECT *
FROM 表A
JOIN 表B ON 表A.外键 = 表B.主键;
例如:
SELECT *
FROM person
JOIN drivers_license ON person.license_id = drivers_license.id;
16. INNER JOIN vs JOIN
在大多数数据库中,JOIN 默认就是 INNER JOIN,两者完全等价。
SELECT * FROM A JOIN B ON ... -- 等价于
SELECT * FROM A INNER JOIN B ON ...
INNER JOIN 的特点:只返回两张表能匹配上的行,匹配不上的行直接丢弃。
17. 多表 JOIN(链式连接)
一次连接三张或更多张表,通过中间表传递关联关系。
FROM 表A
JOIN 表B ON 表A.某列 = 表B.某列
JOIN 表C ON 表B.某列 = 表C.某列;
18. SELECT 表.* 的用法
只取某一张表的所有列,而不是所有表的列。
SELECT person.*, drivers_license.plate_number
FROM person
JOIN drivers_license ON person.license_id = drivers_license.id;
19. JOIN 的结果行数变化
| 左表一行 | 右表匹配行数 | 结果行数 |
|---|---|---|
| 1 行 | 0 行 | 0 行(被丢弃) |
| 1 行 | 1 行 | 1 行 |
| 1 行 | 3 行 | 3 行 |
20. LEFT JOIN(左连接)
返回左表的所有行,即使右表没有匹配的行(右表列填 NULL)。
SELECT *
FROM 表A
LEFT JOIN 表B ON 表A.外键 = 表B.主键;
典型场景:查询“各专业的学生人数,没有学生的专业显示 0”
SELECT m.mname, COUNT(s.sno) AS student_count
FROM major m
LEFT JOIN stu s ON m.mno = s.mno
GROUP BY m.mno, m.mname;
| JOIN 类型 | 没有匹配的行 |
|---|---|
INNER JOIN |
❌ 不显示 |
LEFT JOIN |
✅ 显示,右表列为 NULL |
21. RIGHT JOIN(右连接)
返回右表的所有行,即使左表没有匹配的行。LEFT JOIN 更常用。
七、子查询
22. 子查询基础
子查询是嵌套在另一个查询中的 SELECT,用括号包裹。
-- 标量子查询(返回单个值)
SELECT *
FROM person
WHERE address_number = (
SELECT MAX(address_number)
FROM person
WHERE address_street_name = 'Northwestern Dr'
);
执行顺序:子查询先独立执行,结果返回给外层查询。
| 概念 | 说明 |
|---|---|
| 子查询 | 嵌套在另一个查询中的 SELECT,用括号包裹 |
| 执行顺序 | 子查询先独立执行,结果返回给外层查询 |
| 标量子查询 | 返回单个值,可用在 = 后面 |
| 为什么用子查询 | 动态计算,数据变化时自动适配 |
23. 相关子查询
子查询中引用了外层查询的列,对外层每一行执行一次。
-- 查询超过自己平均成绩的课程
SELECT s.sno, c.cname, s.grade
FROM sc s
JOIN cou c ON c.cno = s.cno
WHERE s.grade > (
SELECT AVG(grade)
FROM sc s2
WHERE s2.sno = s.sno -- 关联到外层当前学生
);
24. 派生表(子查询在 FROM 中)
-- 用派生表实现同上需求
SELECT s.sno, c.cname, s.grade
FROM sc s
JOIN cou c ON c.cno = s.cno
JOIN (
SELECT sno, AVG(grade) AS avg_grade
FROM sc
GROUP BY sno
) AS avg_t ON s.sno = avg_t.sno
WHERE s.grade > avg_t.avg_grade;
25. EXISTS 子查询
检查子查询是否至少返回一行。
-- 找出选了课程 C01 的学生
SELECT sname FROM stu
WHERE EXISTS (
SELECT 1 FROM sc WHERE sc.sno = stu.sno AND cno = 'C01'
);
特点:
- 只要子查询有结果就返回 TRUE
SELECT 1是习惯写法(内容不重要)- 找到第一条匹配就停止,性能好
26. NOT EXISTS 子查询
找出在关联表中不存在匹配记录的行。
-- 找出没有任何评论的商品
SELECT g.id, g.name
FROM goods g
WHERE NOT EXISTS (
SELECT 1
FROM goods_comment c
WHERE c.goods_id = g.id
);
为什么推荐 NOT EXISTS:
- 性能好(找到匹配就停止)
- 无 NULL 陷阱
- 语义清晰:“不存在”
27. 外键查主键表的两种方式
已知第一张表的外键值(或能查到外键值),想查第二张表的主键行。
方式一:直接查(已知外键值)
SELECT * FROM drivers_license WHERE id = 423327;
方式二:子查询或 JOIN(需先查出外键值)
-- 子查询
SELECT * FROM drivers_license
WHERE id = (SELECT license_id FROM person WHERE name = 'Jeremy Bowers');
-- JOIN
SELECT dl.*
FROM person p
JOIN drivers_license dl ON p.license_id = dl.id
WHERE p.name = 'Jeremy Bowers';
八、高级查询模式
28. 查询“选了全部课程”的学生(关系除法)
场景:找出选修了张老师所教所有课程的学生。
方法1:计数比较法
SELECT s.sname
FROM stu s
JOIN sc ON s.sno = sc.sno
JOIN cou c ON sc.cno = c.cno
WHERE c.teacher = '张老师'
GROUP BY s.sno, s.sname
HAVING COUNT(DISTINCT c.cno) = (
SELECT COUNT(*) FROM cou WHERE teacher = '张老师'
);
方法2:NOT EXISTS + EXCEPT
SELECT sname FROM stu s
WHERE NOT EXISTS (
SELECT cno FROM cou WHERE teacher = '张老师'
EXCEPT
SELECT cno FROM sc WHERE sno = s.sno
);
| 方法 | 优点 | 缺点 |
|---|---|---|
| 计数比较 | 简单直观 | 需要知道总门数 |
| NOT EXISTS + EXCEPT | 逻辑清晰 | 部分数据库不支持 EXCEPT |
29. 查询某个值最大的行
方法1:子查询 + =(返回所有并列最大)
SELECT *
FROM orders
WHERE Freight = (
SELECT MAX(Freight) FROM orders
);
方法2:ORDER BY + LIMIT(只返回一行)
SELECT *
FROM orders
ORDER BY Freight DESC
LIMIT 1;
方法3:窗口函数(MySQL 8.0+)
SELECT *
FROM (
SELECT *, RANK() OVER (ORDER BY Freight DESC) AS rk
FROM orders
) AS t
WHERE rk = 1;
| 方法 | 返回所有并列最大 | 性能 |
|---|---|---|
子查询 + = |
✅ | 一般 |
ORDER BY + LIMIT 1 |
❌ | 最好 |
| 窗口函数 | ✅ | 一般 |
30. 查询“超过平均成绩”的课程(相关子查询 vs 派生表)
需求:查询每个同学超过他选修的平均成绩的课程。
方法1:相关子查询
SELECT s.sno, c.cname, s.grade
FROM sc s
JOIN cou c ON c.cno = s.cno
WHERE s.grade > (
SELECT AVG(grade)
FROM sc s2
WHERE s2.sno = s.sno
);
方法2:派生表
SELECT s.sno, c.cname, s.grade
FROM sc s
JOIN cou c ON c.cno = s.cno
JOIN (
SELECT sno, AVG(grade) AS avg_grade
FROM sc
GROUP BY sno
) AS avg_t ON s.sno = avg_t.sno
WHERE s.grade > avg_t.avg_grade;
| 方法 | 子查询执行次数 | 适用场景 |
|---|---|---|
| 相关子查询 | 每学生一次 | 简单直观 |
| 派生表 | 一次 | 多次引用时效率更高 |
九、特殊数据类型
31. 整数日期的处理
数据库中日期存储为整数格式,如 20171206 表示 2017年12月6日。
-- 查询 2017年12月的所有记录
WHERE date BETWEEN 20171201 AND 20171231
-- 查询 2018年1月15日
WHERE date = 20180115
32. 日期时间类型(DATETIME)
数据库中日期存储为 DATETIME 类型,如 '2001-10-01 00:00:00'。
-- 比较日期(忽略时间部分)
WHERE DATE(birdate) = '2001-10-01'
-- 查询年龄最大的学生(生日最小)
SELECT * FROM stu ORDER BY birdate ASC LIMIT 1;
-- 查询某年出生的学生
WHERE YEAR(birdate) = 2001;
年龄与生日的关系: | 生日 | 年龄 | |——|——| | 2000-01-01(小) | 大 | | 2010-12-31(大) | 小 |
- 年龄最大 →
MIN(birdate) - 年龄最小 →
MAX(birdate)
十、查询执行顺序(非常重要)
SQL 语句的书写顺序和执行顺序不同,理解这一点能避免很多错误。
书写顺序:
SELECT -- 5
FROM -- 1
WHERE -- 2
GROUP BY -- 3
HAVING -- 4
ORDER BY -- 6
执行顺序:
FROM -- 1. 确定查询哪张表
WHERE -- 2. 过滤行(分组前)
GROUP BY -- 3. 分组
HAVING -- 4. 过滤组(分组后)
SELECT -- 5. 选择要显示的列
ORDER BY -- 6. 排序
常见错误:
-- ❌ 错误:WHERE 放在 GROUP BY 之后
SELECT CustomerID, AVG(Freight)
FROM orders
GROUP BY CustomerID
WHERE ShipCountry = 'Belgium';
-- ✅ 正确
SELECT CustomerID, AVG(Freight)
FROM orders
WHERE ShipCountry = 'Belgium'
GROUP BY CustomerID;
记忆口诀:
WHERE 先筛行,GROUP 再分组,HAVING 最后筛组。
或更完整: FROM 找表 WHERE 筛,GROUP 分组 HAVING 裁,SELECT 选列 ORDER 排。
十一、自定义排序
当需要按特定顺序(非字母、非数字)排序时,使用 FIELD() 或 CASE WHEN。
33.1 使用 FIELD()(MySQL/MariaDB)
SELECT City
FROM customers
GROUP BY City
ORDER BY FIELD(City, 'Berlin', 'London', 'Paris', 'Madrid');
FIELD() 返回指定值的索引位置(从1开始),未列出的值返回0。
33.2 使用 CASE WHEN(通用,所有数据库都支持)
SELECT City
FROM customers
GROUP BY City
ORDER BY
CASE City
WHEN 'Berlin' THEN 1
WHEN 'London' THEN 2
WHEN 'Paris' THEN 3
WHEN 'Madrid' THEN 4
ELSE 10
END;
原理:ORDER BY 按数字大小排序,1→2→3…,未指定的排最后。
十二、WHERE 常见错误汇总
| 错误类型 | 错误示例 | 正确做法 |
|---|---|---|
| 聚合函数在 WHERE 中 | WHERE AVG(score) > 75 |
放到 HAVING 中 |
| 文本值没用引号 | WHERE city = London |
WHERE city = 'London' |
多个值用 = |
WHERE country = 'Belgium, Switzerland' |
WHERE country IN ('Belgium', 'Switzerland') |
| WHERE 位置错误 | FROM ... GROUP BY ... WHERE |
FROM ... WHERE ... GROUP BY |
NULL 判断用 = |
WHERE price = NULL |
WHERE price IS NULL |
十三、INSERT 语句
34.1 基本插入
INSERT INTO 表名 (列1, 列2, 列3) VALUES (值1, 值2, 值3);
34.2 插入多行
INSERT INTO stu (sno, sname, sex, mno, birdate, memo) VALUES
('S001', '张三', 1, '01', '2001-10-01 00:00:00', '二年级,菜'),
('S002', '李四', 0, '02', '2001-05-13 00:00:00', NULL);
34.3 NULL 值处理
- 在
INSERT中直接写NULL - 在表格中显示为
(Null)的值应写为NULL
知识点速查表
| 序号 | 知识点 | 关键词 |
|---|---|---|
| 1 | 基础查询 | SELECT, FROM |
| 2 | 条件过滤 | WHERE, AND, OR |
| 3 | 别名 | AS |
| 4 | 模糊匹配 | LIKE, %, _ |
| 5 | 范围查询 | BETWEEN ... AND ... |
| 6 | 成员判断 | IN |
| 7 | 空值判断 | IS NULL, IS NOT NULL |
| 8 | 排序 | ORDER BY, ASC, DESC |
| 9 | 分组 | GROUP BY |
| 10 | 分组后过滤 | HAVING |
| 11 | 去重 | DISTINCT |
| 12 | 聚合函数 | COUNT, SUM, AVG, MAX, MIN |
| 13 | 主键 | PRIMARY KEY |
| 14 | 外键 | FOREIGN KEY |
| 15 | 查看表结构 | PRAGMA table_info |
| 16 | 表关联 | JOIN, ON |
| 17 | 内连接 | INNER JOIN |
| 18 | 多表连接 | 链式 JOIN |
| 19 | 选择特定表 | 表.* |
| 20 | JOIN 行数变化 | 一对多影响 |
| 21 | 左连接 | LEFT JOIN |
| 22 | 右连接 | RIGHT JOIN |
| 23 | 子查询 | SELECT 嵌套 |
| 24 | 相关子查询 | 子查询引用外层列 |
| 25 | 派生表 | 子查询在 FROM 中 |
| 26 | 存在性子查询 | EXISTS |
| 27 | 不存在性子查询 | NOT EXISTS |
| 28 | 外键查主键 | 直接查 / 子查询 / JOIN |
| 29 | 关系除法 | 选了全部课程 |
| 30 | 查询最大值行 | 子查询 / ORDER BY + LIMIT / 窗口函数 |
| 31 | 超过平均成绩查询 | 相关子查询 / 派生表 |
| 32 | 整数日期 | 直接用数字 |
| 33 | 日期时间类型 | DATETIME, DATE(), YEAR() |
| 34 | 年龄与生日 | MIN() 年龄最大,MAX() 年龄最小 |
| 35 | 自定义排序 | FIELD(), CASE WHEN |
| 36 | WHERE 常见错误 | 聚合函数、引号、IN、位置、NULL |
| 37 | INSERT 语句 | INSERT INTO ... VALUES |
| 38 | NULL 值处理 | NULL |
| 39 | 查询执行顺序 | FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY |