怎么把 SQL 的增删改查写成"稳、准、可维护"的
写 SQL 的目标从来不只是"跑出来结果",而是:
- 不踩 NULL 的坑
- 不发生"手滑全表更新/删除"的事故
- 能做 统计、分组、分页
- 需要时用函数把数据处理干净
下面按常用的节奏走一遍。
1)Create:INSERT 新增
1.1 全列插入(不写列名)
sql
INSERT INTO exam VALUES (1, '张三', 90, 95, 88);
只有在"确定表结构顺序稳定、列数也不变"的情况下才这么写,否则更推荐写列名。
1.2 指定列插入(推荐)
sql
INSERT INTO exam (id, name, chinese) VALUES (2, '李四', 92);
好处是:列顺序不怕变,也能只给部分列。
1.3 多行插入
sql
INSERT INTO exam (id, name, chinese) VALUES
(3, '王五', 85),
(4, '赵六', 78);
2)Retrieve:SELECT 查询(最常用的一套)
2.1 查全表 / 查指定列
sql
SELECT * FROM exam;
sql
SELECT id, name, chinese FROM exam;
在真实项目里不可以无脑 SELECT *,字段多、数据大时会拖慢一切。
2.2 SELECT 里写表达式(常量、运算、总分)
sql
SELECT id, name, chinese + math + english AS total_score
FROM exam;
一个会牢记的坑:如果 math 或 english 有 NULL,总分会变成 NULL(因为 NULL 参与运算结果仍是 NULL)。
2.3 DISTINCT 去重("整行"去重)
sql
SELECT DISTINCT name, chinese FROM exam;
用 DISTINCT 时会明确:它不是"按某一列去重",而是"查询结果里所有列都相同才算重复"。
3)WHERE 条件过滤:比较、范围、集合、NULL、模糊
3.1 比较运算符 + NULL 安全等于 <=>
sql
SELECT * FROM exam WHERE chinese >= 90;
如果要判断 NULL,我不会写 = NULL,而是:
sql
SELECT * FROM exam WHERE english IS NULL;
如果真的需要"NULL 也能比较相等",我会用 <=>:
sql
SELECT * FROM exam WHERE english <=> NULL;
3.2 BETWEEN / IN
sql
SELECT * FROM exam WHERE chinese BETWEEN 80 AND 90;
sql
SELECT * FROM exam WHERE id IN (1, 3, 5);
3.3 LIKE 模糊匹配(% 和 _)
sql
SELECT * FROM exam WHERE name LIKE '张%';
sql
SELECT * FROM exam WHERE name LIKE '_三';
3.4 AND/OR 优先级(强制加括号)
sql
SELECT * FROM exam
WHERE (chinese >= 90 AND math >= 90) OR english >= 95;
因为 AND 优先级高于 OR,宁愿多写括号,也不赌"记得优先级"。
3.5 一个硬规则:WHERE 里不能直接用别名
下面这种很多人会写错:
sql
-- ❌(别名 total_score 在 WHERE 阶段不可用)
SELECT id, name, chinese + math + english AS total_score
FROM exam
WHERE total_score >= 270;
改成:
sql
SELECT id, name, chinese + math + english AS total_score
FROM exam
WHERE (chinese + math + english) >= 270;
4)ORDER BY 排序:多列、表达式、别名、NULL 排序
4.1 按总分降序(表达式排序)
sql
SELECT id, name, chinese + math + english AS total_score
FROM exam
ORDER BY total_score DESC;
用别名排序:ORDER BY 阶段是能识别别名的。
4.2 多列排序(先总分,再语文)
sql
SELECT id, name, chinese + math + english AS total_score
FROM exam
ORDER BY total_score DESC, chinese DESC;
记住:NULL 被认为比任何值都小------升序时靠前,降序时靠后。
5)LIMIT 分页:最常用的写法
这种,语义清晰:
sql
SELECT id, name, chinese
FROM exam
ORDER BY id
LIMIT 10 OFFSET 20;
含义是:从第 21 条开始取 10 条(OFFSET 从 0 开始计)。
6)Update:UPDATE 修改(单列/多列 + 安全写法)
6.1 基于原值递增
sql
UPDATE exam
SET math = math + 10
WHERE id = 1;
不写 math += 10,SQL 不认这种语法。
6.2 一次改多列
sql
UPDATE exam
SET chinese = 95, english = 96
WHERE name = '张三';
UPDATE 不写 WHERE 等于自爆。
7)Delete:DELETE 删除(同样支持排序与限制)
7.1 删除某个条件下的记录
sql
DELETE FROM exam WHERE id = 4;
7.2 只删"排序后的前 N 条"
sql
DELETE FROM exam
ORDER BY id DESC
LIMIT 3;
同样铁律:DELETE 不写 WHERE,基本就是事故预备役。
8)TRUNCATE:清空整张表(快,但很"硬")
sql
TRUNCATE TABLE exam;
把 TRUNCATE 当成"重置按钮":
- 只能整表清空
- 通常更快
- 不走逐行删除逻辑,很多场景下不可回滚
- 会重置自增(AUTO_INCREMENT)
所以只在非常确定的时候用。
9)INSERT ... SELECT:把查询结果落到新表(去重落表特别好用)
一个很常用的"去重落表"套路:
sql
CREATE TABLE exam_new LIKE exam;
INSERT INTO exam_new
SELECT DISTINCT * FROM exam;
这样我以后查 exam_new 就不需要每次都 DISTINCT 了(查询成本更低、逻辑更干净)。
10)聚合函数:COUNT / SUM / AVG / MAX / MIN(最常用的统计)
10.1 统计行数、统计非 NULL 数量
sql
SELECT COUNT(*) AS total_rows FROM exam;
sql
SELECT COUNT(english) AS english_not_null FROM exam;
牢记:COUNT(col) 不数 NULL。
10.2 求和/平均/最大最小
sql
SELECT
SUM(chinese) AS sum_chinese,
AVG(chinese) AS avg_chinese,
MAX(chinese) AS max_chinese,
MIN(chinese) AS min_chinese
FROM exam;
11)GROUP BY 分组 + HAVING 分组过滤(统计分析的核心)
11.1 按部门统计人数(示例:emp 表)
sql
SELECT dept_id, COUNT(*) AS cnt
FROM emp
GROUP BY dept_id;
11.2 HAVING:过滤"分组后的结果"
比如想找"平均工资大于 1500 的部门":
sql
SELECT dept_id, AVG(salary) AS avg_salary
FROM emp
GROUP BY dept_id
HAVING AVG(salary) > 1500;
区分 WHERE 和 HAVING 的方式很简单:
- WHERE 过滤的是"原始行"
- HAVING 过滤的是"聚合后的组"
12)常用内置函数:日期、字符串、数学、其他(直接上手的那种)
12.1 日期函数
取当前时间:
sql
SELECT NOW();
取日期部分:
sql
SELECT DATE(NOW());
日期加减(例如:当前日期加 7 天):
sql
SELECT DATE_ADD(CURDATE(), INTERVAL 7 DAY);
算日期差(天):
sql
SELECT DATEDIFF('2025-01-10', '2025-01-01') AS diff_days;
12.2 字符串函数
长度(字符长度 vs 字节长度):
sql
SELECT CHAR_LENGTH('重邮'), LENGTH('重邮');
拼接:
sql
SELECT CONCAT(name, '-', id) AS tag FROM exam;
截取与替换:
sql
SELECT SUBSTRING('abcdef', 2, 3);
sql
SELECT REPLACE('hello world', 'world', 'SQL');
去两端空格:
sql
SELECT TRIM(' sql ');
12.3 数学函数
绝对值/取整/四舍五入:
sql
SELECT ABS(-10), CEIL(3.14), FLOOR(3.99), ROUND(3.14159, 2);
随机数:
sql
SELECT RAND();
12.4 其他常用(排障/处理 NULL 经常用)
当前库/当前用户/版本:
sql
SELECT DATABASE(), USER(), VERSION();
NULL 兜底(总分避免 NULL 传染):
sql
SELECT id, name,
IFNULL(chinese, 0) + IFNULL(math, 0) + IFNULL(english, 0) AS total_score
FROM exam;
"SQL 防翻车三原则"
1)UPDATE/DELETE:先写 WHERE,再写 SET/删法,再执行
2)AND/OR 混用:一律上括号,不靠记忆
3)遇到 NULL:判断用 IS NULL,兜底用 IFNULL,比较需要时用 <=>