在SQL中有一套严格的书写顺序(从上到下):
| SELECT | 选择 |
| FROM | 来源 |
| WHERE | 条件(行过滤) |
| GROUP BY | 分组 |
| HAVING | 条件(过滤后分组) |
| ORDER BY | 顺序 |
| LIMIT | 条数限制 |
| OFFSET | 条数偏移 |
书写时先SELECT,但系统执行时先FROM。
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT / OFFSET
这也是 WHERE里不能用SELECT定义的别名的原因。
单表查询基础
单表查询,顾名思义,就是只针对一张表进行的数据检索。它是所有复杂查询(如多表 JOIN)的基石。
最简单的查询语句只需两个关键字:
-- 查询所有列(不推荐在生产环境使用,容易导致性能问题)
SELECT * FROM users;
-- 查询指定列(推荐)
SELECT id, username, age FROM users;
SELECT 后面可以使用 AS 给列起别名(SELECT username AS 用户名),让结果更易读。
WHERE 是单表查询的灵魂,用于筛掉不需要的行。它支持非常丰富的操作符:
--比较运算:=, != (或 <>), >, <, >=, <=
SELECT * FROM users WHERE age >= 18;
--逻辑运算:AND (且), OR (或), NOT (非)
SELECT * FROM users WHERE age >= 18 AND gender = '男';
--范围查询:BETWEEN ... AND ..., IN (...)
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
SELECT * FROM users WHERE department_id IN (1, 3, 5);
--模糊查询:LIKE
-- % 代表任意多个字符,_ 代表单个字符
SELECT * FROM users WHERE username LIKE '张%'; -- 姓张的
SELECT * FROM users WHERE username LIKE '%飞%'; -- 名字里带飞的
--空值判断:IS NULL, IS NOT NULL
-- 注意:判断空值不能用 = NULL,必须用 IS NULL
SELECT * FROM users WHERE email IS NULL;
注意:
- WHERE 后面绝对不能使用聚合函数(如 COUNT, SUM)。
- 尽量避免在 WHERE 中对字段使用函数,比如 WHERE YEAR(create_time) = 2023 会导致索引失效,应使用SELECT * FROM orders WHERE create_time >= ‘2023-01-01 00:00:00’ AND create_time < ‘2024-01-01 00:00:00’;
- 结果去重:DISTINCT
如果查询结果有重复数据,可以用 DISTINCT 去重。
-- 查询当前公司有哪些部门(去掉重复的部门ID)
SELECT DISTINCT department_id FROM users;
结果排序:ORDER BY
默认是升序 (ASC),降序需要指定 DESC。
-- 按年龄降序排列,如果年龄相同,则按ID升序排列
SELECT * FROM users ORDER BY age DESC, id ASC;
分页神器:LIMIT 与 OFFSET
当数据量很大时,我们不需要一次性全部查出来,这时候就用分页。
- LIMIT n:表示只看前 n 条。
- OFFSET m:表示跳过前 m 条(从第 m+1 条开始)。
— 查询年龄最大的前 5 个人
SELECT * FROM users ORDER BY age DESC LIMIT 5;
-- 分页查询(假设每页 10 条,查询第 3 页的数据)
-- 第 3 页意味着跳过前 20 条
SELECT * FROM users LIMIT 10 OFFSET 20;
-- 另一种常见的简写格式:LIMIT offset, limit
-- 效果同上:跳过 20 条,取 10 条
SELECT * FROM users LIMIT 20, 10;
进阶查询
GROUP BY 与 聚合函数 (COUNT, SUM 等),以及 HAVING。
在业务中,我们经常需要做统计,比如“每个部门的平均工资”、“全公司的总人数”。这就需要用到聚合。
常用的聚合函数如下
COUNT():统计行数
SUM():求和
AVG():求平均值
MAX() / MIN():求最大/最小值
GROUP BY分组
-- 比如有一张 employees 表,按部门分组,计算每个部门的平均工资
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;
HAVING (组过滤)
- WHERE:在数据分组前执行,过滤的是行。绝对不能用聚合函数。
- HAVING:在数据分组后执行,过滤的是组。必须配合 GROUP BY 使用,可以使用聚合函数。
-- 完整逻辑:查在职人员,按部门分组,算出平均工资,最后只保留平均工资大于8000的部门
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE status = '在职' -- 先过滤人
GROUP BY department_id -- 再按部门分组
HAVING AVG(salary) > 8000; -- 最后过滤掉不达标的部门
多表关联:JOIN
数据通常分散在多张表中,我们需要把它们拼起来。
- 准备两张表
- 表 A:employees (员工表:包含 id, name, dept_id)
- 表 B:departments (部门表:包含 id, dept_name)
- 内连接 INNER JOIN (取交集)
含义:只返回两张表中条件完全匹配的数据。(比如:只查出有部门归属的员工,和底下有人的部门)。
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;
- 左连接 LEFT JOIN (以左表为主)
含义:返回左表的所有数据,即使右表没有匹配的也返回(右表缺失的字段显示为 NULL)。(比如:查出所有员工,如果他们没部门,部门名字就显示为空)。
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
- 右连接 RIGHT JOIN (以右表为主)
含义:和左连接相反。(实际开发中极少用,因为都可以用 LEFT JOIN 替代,改变表的前后顺序即可)。
笛卡尔积:使用 JOIN 时,千万不要忘记写 ON 条件!如果漏写 ON,会产生“笛卡尔积”(等同于CROSS JOIN),两张表的数据会成倍数交叉组合,直接把数据库卡死。
SQL 语言的宏观分类
- DQL (Data Query Language) —— 数据查询语言
- 核心指令:SELECT
- 作用:负责从数据库里找数据,不改变数据本身。
- 比喻:你去仓库里看货、盘点、找特定的商品。
- 备注:也就是你这篇博客前面大篇幅在讲的核心内容。它是日常开发中使用频率最高的语言(占 80% 以上)。
- DML (Data Manipulation Language) —— 数据操纵语言
- 核心指令:INSERT(增)、UPDATE(改)、DELETE(删)
- 作用:负责对表里的具体数据行进行操作。
- 比喻:你往仓库里进货(INSERT)、修改某个商品的价格标签(UPDATE)、或者扔掉过期商品(DELETE)。
- 重点提示:DML 操作会真正改变数据,所以通常需要配合事务 (TCL) 使用,以便出错时回滚。
- DDL (Data Definition Language) —— 数据定义语言
- 核心指令:CREATE(建)、ALTER(改)、DROP(删)、TRUNCATE(清空)
- 作用:负责定义或改变数据库的结构(建表、建库、建索引、建视图、建触发器)。
- 比喻:你决定要在仓库里新建一个货架(CREATE TABLE),或者把一个货架拆掉(DROP TABLE)。
- 重点提示:在 MySQL 中,DDL 操作是隐式提交的,也就是执行完立刻生效,无法回滚!千万别在生产环境随便按 DROP。
- DCL (Data Control Language) —— 数据控制语言
- 核心指令:GRANT(授权)、REVOKE(取消授权)
- 作用:负责管理数据库的权限和访问控制。
- 比喻:你是仓库老板,你给员工 A 发了一张“只能看不能拿”的门禁卡(GRANT SELECT),或者没收了员工 B 的钥匙(REVOKE)。
- TCL (Transaction Control Language) —— 事务控制语言
- 核心指令:COMMIT(提交事务)、ROLLBACK(回滚事务)、SAVEPOINT(设置保存点)
- 作用:负责保证一组 DML 操作的原子性(要么全成功,要么全失败)。
- 比喻:你在仓库里同时搬了 10 箱货。如果搬到第 9 箱摔倒了,TCL 可以让你一键“倒带”,让前 9 箱货自动回到原位(ROLLBACK)。
DML(数据增删改)
前面讲的 SELECT 只是“看”,真正改变表内数据的是 INSERT、UPDATE 和 DELETE。
- 插入数据:INSERT
用于向表中添加新行。
-- 推荐写法:指定列名插入(即使表结构改变,也不会报错)
INSERT INTO users (username, age, email)
VALUES ('星鸿', 25, 'xinghong@example.com');
-- 批量插入(性能更好,一次性插入多行)
INSERT INTO users (username, age)
VALUES ('张三', 20), ('李四', 22), ('王五', 24);
- 更新数据:UPDATE
用于修改表中已存在的数据。
-- 语法:UPDATE 表名 SET 列名 = 新值 WHERE 条件;
UPDATE users
SET age = 26, email = 'new_email@example.com'
WHERE id = 1;
- 删除数据:DELETE
用于删除表中的行。
-- 删除 id 为 2 的用户
DELETE FROM users WHERE id = 2;
辨析
DELETE:属于 DML。它是“一行一行”删的,会记录日志,可以回滚(如果开启了事务)。表结构还在,自增 ID 不会重置。
TRUNCATE TABLE:属于 DDL。它是“直接把表推倒重新建一个空表”,速度极快,不能回滚。自增 ID 会重置为 1。
DROP TABLE:属于 DDL。直接把表连同数据和结构全毁了。
进阶概念
1. 事务(Transaction)
概念:事务是一组不可分割的 SQL 操作。要么全部成功,要么全部失败(比如银行转账,A 扣钱、B 加钱必须同时发生)。
比喻:像打游戏存档。你打完 BOSS 存个档(COMMIT);如果打输了,直接读档重来(ROLLBACK)。
四大特性 (ACID) :
- A – 原子性 (Atomicity):不可分割,要么全做,要么全不做(撤销)。
- C – 一致性 (Consistency):事务前后,数据库的完整性约束不被破坏(比如转账前后总金额不变)。
- I – 隔离性 (Isolation):多个事务并发时,互不干扰(像隔音房)。
- D – 持久性 (Durability):事务一旦提交,哪怕拔电源,数据也不会丢(写进硬盘了)。
得益于各种语言框架和数据库的不断发展,现在数据库事务已经不用手动管理,下面示例代码仅为介绍。
START TRANSACTION; -- 开启事务 (MySQL 用法)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 如果上面两行都成功,提交保存
COMMIT;
-- 如果任何一步报错或发现不对,一键撤销
-- ROLLBACK;
2. 视图(View)
概念:视图不存真实数据,它只是一条被保存下来的 SELECT 查询语句。
比喻:像电脑桌面上的“快捷方式”。你点快捷方式(查询视图),实际打开的是 C 盘里的真实程序(基表)。
优缺点:
- 优点:安全(只给用户看部分字段,隐藏敏感信息)、简化查询(把复杂的 JOIN 写成视图,以后直接 SELECT * FROM 视图名)。
- 缺点:性能一般(底层还是去查原来的表)。
-- 创建一个只包含用户姓名和部门的视图(隐藏了薪资等)
CREATE VIEW v_user_info AS
SELECT id, username, dept_name FROM users u JOIN departments d ON u.dept_id = d.id;
-- 像查普通表一样查视图
SELECT * FROM v_user_info WHERE username LIKE '张%';
3. 触发器 (Trigger)
概念:绑定在表上的程序。当你对表执行 INSERT、UPDATE、DELETE 时,它自动触发。
应用场景:审计日志。比如“只要有员工离职(DELETE),自动往 离职日志表 里插一条记录”。
注意:触发器极其难调试,且会隐式增加性能开销,现代开发中极少使用,一般交给后端代码处理。
场景设计:假设我们有一张 employees(员工表)。为了防止数据丢失,我们规定:只要有人被删除(开除/离职),系统必须自动把这个人记录到 emp_audit_log(离职审计表)里。
表定义
-- 1. 员工表
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
salary DECIMAL(10,2)
);
-- 2. 离职审计日志表
CREATE TABLE emp_audit_log (
log_id INT AUTO_INCREMENT PRIMARY KEY,
emp_id INT,
emp_name VARCHAR(50),
deleted_at DATETIME
);
触发器定义
-- 注意:在 MySQL 中,包含多条语句的触发器需要用 DELIMITER 改变结束符
DELIMITER $$
CREATE TRIGGER trg_after_employee_delete
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
-- 逻辑:当 employees 表删除了数据,把被删除的旧数据(OLD 关键字)插入到日志表
INSERT INTO emp_audit_log (emp_id, emp_name, deleted_at)
VALUES (OLD.id, OLD.name, NOW());
END$$
DELIMITER ;
- AFTER DELETE:表示动作发生在删除之后。触发器还支持 BEFORE(之前)、AFTER INSERT(插入后)、AFTER UPDATE(更新后)。
- FOR EACH ROW:行级触发器。意思是如果你一口气删了 10 个人,这个触发器会执行 10 次(每次针对一行记录)。
- OLD 和 NEW 关键字:
• 在 DELETE 触发器中,只能使用 OLD(代表被删掉的那一行数据)。
• 在 INSERT 触发器中,只能使用 NEW(代表新插入的数据)。
• 在 UPDATE 中,OLD 代表修改前的值,NEW 代表修改后的值。
结语
以上是 SQL 的基本语法与执行逻辑。如果你想写出不卡顿、且后期容易维护的数据库,在动手写 SQL 之前,一定要去了解 数据库设计范式与函数依赖。掌握了它,你建出来的表才不会成为未来的噩梦!



