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
  • 多个条件用 ANDOR

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 NULLIS 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 中,只能放在 SELECTHAVING 中。

-- ❌ 错误
SELECT * FROM sc WHERE AVG(grade) > 75;

-- ✅ 正确
SELECT sno, AVG(grade) FROM sc GROUP BY sno HAVING AVG(grade) > 75;

五、表结构与约束

12. PRIMARY KEY(主键)

  • 每张表中唯一标识一行数据的列
  • 不能重复,不能为空
  • 例如:person.iddrivers_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

results matching ""

    No results matching ""