SQL执行顺序和基本语法

在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;

注意:

  1. WHERE 后面绝对不能使用聚合函数(如 COUNT, SUM)。
  2. 尽量避免在 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’;
  3. 结果去重: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

数据通常分散在多张表中,我们需要把它们拼起来。

  1. 准备两张表
  • 表 A:employees (员工表:包含 id, name, dept_id)
  • 表 B:departments (部门表:包含 id, dept_name)
  1. 内连接 INNER JOIN (取交集)

含义:只返回两张表中条件完全匹配的数据。(比如:只查出有部门归属的员工,和底下有人的部门)。

SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;
  1. 左连接 LEFT JOIN (以左表为主)

含义:返回左表的所有数据,即使右表没有匹配的也返回(右表缺失的字段显示为 NULL)。(比如:查出所有员工,如果他们没部门,部门名字就显示为空)。

SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
  1. 右连接 RIGHT JOIN (以右表为主)

含义:和左连接相反。(实际开发中极少用,因为都可以用 LEFT JOIN 替代,改变表的前后顺序即可)。‌

笛卡尔积:使用 JOIN 时,千万不要忘记写 ON 条件!如果漏写 ON,会产生“笛卡尔积”(等同于CROSS JOIN),两张表的数据会成倍数交叉组合,直接把数据库卡死。

SQL 语言的宏观分类

  1. DQL (Data Query Language) —— 数据查询语言
  • 核心指令:SELECT
  • 作用:负责从数据库里找数据,不改变数据本身。
  • 比喻:你去仓库里看货、盘点、找特定的商品。
  • 备注:也就是你这篇博客前面大篇幅在讲的核心内容。它是日常开发中使用频率最高的语言(占 80% 以上)。
  1. DML (Data Manipulation Language) —— 数据操纵语言
  • 核心指令:INSERT(增)、UPDATE(改)、DELETE(删)
  • 作用:负责对表里的具体数据行进行操作。
  • 比喻:你往仓库里进货(INSERT)、修改某个商品的价格标签(UPDATE)、或者扔掉过期商品(DELETE)。
  • 重点提示:DML 操作会真正改变数据,所以通常需要配合事务 (TCL) 使用,以便出错时回滚。
  1. DDL (Data Definition Language) —— 数据定义语言
  • 核心指令:CREATE(建)、ALTER(改)、DROP(删)、TRUNCATE(清空)
  • 作用:负责定义或改变数据库的结构(建表、建库、建索引、建视图、建触发器)。
  • 比喻:你决定要在仓库里新建一个货架(CREATE TABLE),或者把一个货架拆掉(DROP TABLE)。
  • 重点提示:在 MySQL 中,DDL 操作是隐式提交的,也就是执行完立刻生效,无法回滚!千万别在生产环境随便按 DROP。
  1. DCL (Data Control Language) —— 数据控制语言
  • 核心指令:GRANT(授权)、REVOKE(取消授权)
  • 作用:负责管理数据库的权限和访问控制。
  • 比喻:你是仓库老板,你给员工 A 发了一张“只能看不能拿”的门禁卡(GRANT SELECT),或者没收了员工 B 的钥匙(REVOKE)。
  1. TCL (Transaction Control Language) —— 事务控制语言
  • 核心指令:COMMIT(提交事务)、ROLLBACK(回滚事务)、SAVEPOINT(设置保存点)
  • 作用:负责保证一组 DML 操作的原子性(要么全成功,要么全失败)。
  • 比喻:你在仓库里同时搬了 10 箱货。如果搬到第 9 箱摔倒了,TCL 可以让你一键“倒带”,让前 9 箱货自动回到原位(ROLLBACK)。

DML(数据增删改)

前面讲的 SELECT 只是“看”,真正改变表内数据的是 INSERT、UPDATE 和 DELETE。

  1. 插入数据:INSERT

用于向表中添加新行。

-- 推荐写法:指定列名插入(即使表结构改变,也不会报错)
INSERT INTO users (username, age, email) 
VALUES ('星鸿', 25, 'xinghong@example.com');

-- 批量插入(性能更好,一次性插入多行)
INSERT INTO users (username, age) 
VALUES ('张三', 20), ('李四', 22), ('王五', 24);
  1. 更新数据:UPDATE

用于修改表中已存在的数据。

-- 语法:UPDATE 表名 SET 列名 = 新值 WHERE 条件;
UPDATE users 
SET age = 26, email = 'new_email@example.com' 
WHERE id = 1;
  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) :

  1. A – 原子性 (Atomicity):不可分割,要么全做,要么全不做(撤销)。
  2. C – 一致性 (Consistency):事务前后,数据库的完整性约束不被破坏(比如转账前后总金额不变)。
  3. I – 隔离性 (Isolation):多个事务并发时,互不干扰(像隔音房)。
  4. 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 ;
  1. AFTER DELETE:表示动作发生在删除之后。触发器还支持 BEFORE(之前)、AFTER INSERT(插入后)、AFTER UPDATE(更新后)。
  2. FOR EACH ROW:行级触发器。意思是如果你一口气删了 10 个人,这个触发器会执行 10 次(每次针对一行记录)。
  3. OLD 和 NEW 关键字:
    • 在 DELETE 触发器中,只能使用 OLD(代表被删掉的那一行数据)。
    • 在 INSERT 触发器中,只能使用 NEW(代表新插入的数据)。
    • 在 UPDATE 中,OLD 代表修改前的值,NEW 代表修改后的值。

结语

以上是 SQL 的基本语法与执行逻辑。如果你想写出不卡顿、且后期容易维护的数据库,在动手写 SQL 之前,一定要去了解 数据库设计范式与函数依赖。掌握了它,你建出来的表才不会成为未来的噩梦!

本文章作者为星鸿,转载务必注明出处!请依据《 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 》保留本文链接,地址:https://blog.xhsr.org.cn/archives/1312
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇