2026/10/20 5:38:58

SQL入门实战:从基础查询到增删改与避坑指南

SQL入门实战:从基础查询到增删改与避坑指南 写SQL这几年我见过太多人卡在同一个地方看教程觉得什么都懂打开命令行就不知道从哪下手。尤其是零基础一上来背了一堆语法结果连“把某张表里某个条件的数据查出来”都写不利索。其实SQL没那么玄乎它就是跟数据库打交道的标准语言你要查数据、改数据、删数据都得用它发号施令。这篇笔记不搞花活按“查询→增删改→避坑”的顺序走一遍附带大量可以直接抄的实战套路和教训适合刚入门、学了一半想查漏补缺的读者。1. 入门前的关键认知1.1 数据库到底是什么SQL又是什么数据库就是“存数据的仓库”但比Excel更规范、更安全、更擅长处理海量数据。Excel里的一张工作表对应数据库里的一个“表”工作表的列叫“字段”行叫“记录”。SQLStructured Query Language结构化查询语言就是专门操作这些表的语言。你不需要懂底层存储引擎的原理只要会写SQL就能让数据库按你的意愿返回数据、写入数据、修改数据。这里有个很重要的认知转变Excel里你用鼠标点点点SQL里你写的是“指令”。最开始会觉得不习惯但你一旦把查询、筛选、排序这些操作翻译成SQL会发现它能做的事远超手工操作。尤其是数据量上了几十万行Excel直接卡死SQL几秒钟给你结果。零基础建议直接从“读数据”开始也就是SELECT查询这是SQL里最常用、最能看到反馈的部分。先会查再会加、改、删一步步来。1.2 选哪种数据库、需要什么工具市面上数据库很多常见的几种数据库适合场景特点MySQL中小型项目、Web应用的主流选择免费、资料多、社区活跃PostgreSQL复杂查询、数据分析场景功能更强部分语法略有不同SQLite本地小工具、手机App单文件、零配置适合入门练习SQL Server企业级Windows环境商业产品学校和企业常见Oracle大型企业系统功能最全学习曲线也最陡零基础练手我建议先装个SQLite。原因很简单不用安装数据库服务下载一个工具就能玩。等你想认真走这条路再装MySQL或者PostgreSQL二者语法绝大多数通用不用怕白学。工具方面SQLite可以用DB Browser for SQLiteMySQL可以用Navicat或者DBeaver。这里提一句练习阶段别太依赖可视化工具。有人一上来就学生成ER图、拖拽连表结果SQL本身反而没学会。先老老实实在命令行或者编辑区域手敲SQL哪怕慢但每一条都能加深理解。1.3 先搞懂这几个基础概念动手之前把这几个词弄明白后面内容全建立在它们之上库database装表的大容器系统里可以建多个库。表table二维结构行和列组成的集合。字段column列表里的一个属性比如姓名、年龄。记录row行一条完整的数据比如一个人。主键primary key唯一标识一条记录的字段必须非空不能重复。外键foreign key指向另一张表主键的字段用来建立表之间关系。举个生活例子你手机通讯录里“联系人”是一张表每个人的“姓名”“电话”是字段每条联系人记录就是一行。主键相当于每个人的身份证号就算有两个人同名也能靠主键区分。另外还要了解数据类型。常见的有整数INT、小数DECIMAL、字符串VARCHAR、日期时间DATETIME。写SQL建表时每个字段都要指定类型不然数据库不知道该怎么存。零基础最容易犯的错把手机号存成整数导致前面带0的手机号被截掉。这种事我见过太多了后面避坑部分会单独说。2. SELECT查询SQL的核心中的核心2.1 基础查询SELECT、FROM、WHERE、ORDER BY查询就是“从哪张表取哪些列满足什么条件”这个思路。一个最基本的查询长这样SELECT 姓名, 年龄 FROM 学生表 WHERE 年龄 18;理解它的执行顺序很重要虽然语法上SELECT写在最前面但数据库实际的处理逻辑大致是先定位FROM后面的表再用WHERE过滤行最后SELECT挑选列并计算、输出ORDER BY排序在最后执行。这个认知能帮你在写复杂SQL时不乱。比如你想查18岁以上的人里年龄最大的三个写的时候虽然是先SELECT再ORDER BY但要清楚过滤和排序发生在不同阶段。条件查询的常用符号比较运算符、不等于、、、、范围BETWEEN AND集合IN、NOT IN模糊匹配LIKE%代表任意多个字符_代表单个字符空值判断IS NULL、IS NOT NULL逻辑组合AND、OR、NOT举个例子查名字里带“张”、年龄要么在20到25之间、要么在30岁以上的记录SELECT * FROM 学生表 WHERE 姓名 LIKE 张% AND (年龄 BETWEEN 20 AND 25 OR 年龄 30);注意LIKE里的中文通配在部分数据库的字符集配置下可能出问题我后面避坑会细讲。排序ORDER BY后面跟列名默认ASC升序想降序就写DESC。支持多列排序先排前面的相同的再排后面的。SELECT 姓名, 成绩 FROM 成绩表 ORDER BY 成绩 DESC, 姓名 ASC;去重查有哪些城市直接SELECT城市会返回一堆重复值用DISTINCT去重SELECT DISTINCT 城市 FROM 用户表;新手建议动手做这几个练习查全部数据SELECT *、查指定列、加WHERE条件、加排序、加去重。把这些练熟你已经能应对日常工作中80%的简单查询场景。2.2 聚合函数与分组统计光会按条件筛选还不够大多数实际需求要的是“统计结果”总数、平均值、最大值、最小值、求和。常用聚合函数COUNT(*)统计行数COUNT(字段)统计该字段非空值的个数SUM(字段)求和只能用于数值AVG(字段)平均值MAX/MIN(字段)最大值/最小值看这个例子统计每个班级的平均分、最高分、人数SELECT 班级, COUNT(*) AS 人数, AVG(成绩) AS 平均分, MAX(成绩) AS 最高分 FROM 成绩表 GROUP BY 班级;关键在GROUP BY它把同一类的行划到一起再对各组做聚合计算。很多新手写完GROUP BY之后SELECT后面出现没被分组的普通列数据库会报错或者返回随机值。记住使用GROUP BY时SELECT后面能出现的列要么是分组的依据列要么是聚合函数。这条规则几乎所有关系型数据库都一样不是MySQL的bug。HAVING是给分组结果加过滤条件的。WHERE不能和聚合函数配合使用因为WHERE过滤的是原始行发生在分组之前。想找“平均分超过90的班级”必须用HAVINGSELECT 班级, AVG(成绩) AS 平均分 FROM 成绩表 GROUP BY 班级 HAVING AVG(成绩) 90;初学者经常混淆WHERE和HAVING的位置WHERE写在GROUP BY之前HAVING写在GROUP BY之后。本质区别就是过滤时机不同。2.3 多表查询JOIN到底在连什么现实中的数据不会全放一张表里。比如“订单表”里存用户ID而用户的姓名在“用户表”里。要查“谁买了什么”单查任何一张表都不够必须把两张表连接起来。JOIN最常用的是INNER JOIN内连接只返回两边都匹配得上的记录SELECT 用户表.姓名, 订单表.订单号 FROM 用户表 INNER JOIN 订单表 ON 用户表.用户ID 订单表.用户ID;除了内连接还有LEFT JOIN左连接、RIGHT JOIN右连接、FULL OUTER JOIN全外连接部分数据库不支持。这里只重点讲LEFT JOIN是你以后被坑最多的地方。LEFT JOIN会返回左表全部记录右表没有匹配的就用NULL填充。场景要列出一共有哪些用户以及他们是否下过单内连接会把没下过单的用户抛弃但LEFT JOIN能把所有用户都显示出来没下单的订单字段是NULL。SELECT 用户表.姓名, 订单表.订单号 FROM 用户表 LEFT JOIN 订单表 ON 用户表.用户ID 订单表.用户ID;有一个经典翻车场景你在LEFT JOIN后的右表上加了WHERE条件比如WHERE 订单表.订单号 IS NOT NULL这个条件会把NULL的行过滤掉导致结果跟INNER JOIN一样。要过滤“没匹配上的左表记录”条件必须写在ON后面。还有更复杂的多表连接比如A JOIN B ON ... JOIN C ON ...。思路是先确定“事实表”通常是业务核心表比如订单再把其他信息用合适JOIN挂上来。别一上来就想着写嵌套子查询很多时候JOIN比子查询更清晰、效率更稳定。2.4 子查询与UNION子查询就是SQL里套SQL常用于以下几种场景WHERE里条件来源于另一张表的统计结果比如“查成绩大于平均分的学生”SELECT 姓名, 成绩 FROM 成绩表 WHERE 成绩 (SELECT AVG(成绩) FROM 成绩表);FROM里套子查询把结果当作临时表比如“按班级平均分排序”SELECT * FROM ( SELECT 班级, AVG(成绩) AS 平均分 FROM 成绩表 GROUP BY 班级 ) AS tmp ORDER BY 平均分 DESC;用EXISTS判断是否存在关联记录比如“找没下过单的用户”SELECT 姓名 FROM 用户表 u WHERE NOT EXISTS ( SELECT 1 FROM 订单表 o WHERE o.用户ID u.用户ID );子查询的括号、别名容易写错建议用缩进和换行把层次写得清楚一点。别把所有逻辑硬塞进一个子查询担心性能的话后面可以用JOIN或EXPLAIN看执行计划不用靠猜。UNION用于纵向合并多个查询结果。两个查询结果的列数、列类型必须对应否则报错。默认UNION会去重想保留全部记录用UNION ALL性能更好。例如合并两个班的成绩SELECT 姓名, 成绩 FROM 一班的成绩表 UNION ALL SELECT 姓名, 成绩 FROM 二班的成绩表;UNION和JOIN的区别拿Excel打比方JOIN是把两列拼在一起UNION是把两行拼到同一列下面。2.5 查询常见菜鸟错误这部分是我最想让你先看的。很多零基础代码报错不是语法不认识而是踩了这些坑写错表名或列名。数据库表名和字段名区分大小写情况不一MySQL在Linux下区分在Windows下不区分别赌这个统一按建表时的写法来。中英文标点搞混。SQL里所有符号都是英文半角的逗号、分号一旦用了中文全角必然报错。这是新手最常见的低级错误。字符串忘加引号。字符串要加单引号不能靠缩进判断数据库没有你想象的智能。数字可以不加但建议也别乱用引号否则可能发生隐式转换影响性能。NULL用去比较。SQL里NULL既不是值也不是空字符串用 字段NULL 查不出来必须用 IS NULL。字符串里的引号没转义。比如查姓名是 ONeil 的记录单引号需要写成两个单引号或者用反斜杠处理视数据库而定。3. 增删改写数据要稳更要考虑后果3.1 INSERT往表里插数据增删改查里INSERT相比SELECT逻辑上简单但细节也不少。最基础写法是给所有字段一一对应插入INSERT INTO 学生表 (学号, 姓名, 年龄, 班级) VALUES (1001, 张三, 20, 软件班);有几点必须注意字段顺序要和VALUES里的顺序一一对应不一定要按表结构的顺序但一定要和冒号前面的列表保持一致。没列出来的字段会用默认值或NULL。如果那个字段设置了NOT NULL约束插入会直接报错。字符类型和日期类型必须加单引号数字不用。还有一种批量插入的方式要插多条数据用逗号分隔INSERT INTO 学生表 (学号, 姓名, 年龄) VALUES (1002, 李四, 21), (1003, 王五, 19), (1004, 赵六, 22);批量插入比一条一条插效率高很多。有些框架比如MyBatis会故意把批量插入拆成多条单插可能是Oracle等数据库的批量语法限制但MySQL推荐这种一次VALUES多行。还有INSERT ... SELECT把查询结果直接插入新表或目标表常用于数据迁移、备份INSERT INTO 优秀学生表 (学号, 姓名, 班级, 成绩) SELECT 学号, 姓名, 班级, 成绩 FROM 学生成绩表 WHERE 成绩 90;这个语句非常好用但前提是目标表和SELECT结果的列要能对得上。建议先跑一遍SELECT确认结果无误再插。3.2 UPDATE修改数据前先加WHEREUPDATE的基本写法UPDATE 学生表 SET 班级 智能班, 年龄 年龄 1 WHERE 学号 1001;这条命令的意思好理解但杀伤力也强。新手最容易犯的一个错就是忘记写WHERE。没有WHERE等于“更新表中所有行”这种事故在正式环境属于严重事故等级。我处理过不少线上数据被全表更新的事故原因几乎都是这个。还有一个操作习惯建议在执行UPDATE前先写一条等价的SELECT确认范围。-- 先确认要改哪些 SELECT * FROM 学生表 WHERE 学号 1001; -- 再执行更新 UPDATE 学生表 SET 班级 智能班 WHERE 学号 1001;这样能保证你的WHERE写对了。即使你不喜欢这种繁琐方式至少也要在事务里执行并检查受影响行数详见后面事务部分。UPDATE支持跨表更新。比如根据订单状态回写用户表的会员等级。不同数据库语法有差异以MySQL为例UPDATE 用户表 u JOIN 订单表 o ON u.用户ID o.用户ID SET u.会员等级 VIP WHERE o.订单金额 5000;这里JOIN相当于限定“要更新哪些行”。不过跨表更新时要小心关联结果有重复记录否则会把同一行更新多次。如果金额最大的订单符合条件那你用MAX聚合先处理再更新更稳妥。3.3 DELETE删除数据要谨慎TRUNCATE也要了解DELETE语法DELETE FROM 学生表 WHERE 学号 1001;同样没写WHERE就是清空全表。实战中删除数据比更新数据更敏感毕竟删了很难找回。有些公司会禁止不带WHERE的DELETE强制要求开发先SELECT确认。DELETE和TRUNCATE都用来清除数据但差别很大对比点DELETETRUNCATE条件可带WHERE删除部分行只能清空整张表速度慢逐行删记录日志快直接释放表空间自增ID不会重置继续增长会重置回滚事务内可以回滚部分数据库不可回滚需特别注意触发器会触发通常不触发跨表删除也有特定写法DELETE 用户表 FROM 用户表 LEFT JOIN 订单表 ON 用户表.用户ID 订单表.用户ID WHERE 订单表.订单号 IS NULL;这个操作就是把“下过单的用户”保留下把没下过单的用户删掉。写跨表删除时心里要清楚DELETE后面跟的是哪张表的名字别删错表。3.4 事务保证数据“要改就一起改”事务是增删改环节最值得花时间理解的概念。拿转账举例A扣1000B加1000这两个操作必须“要么都成功要么都失败”。如果A扣了钱B那边加钱失败系统就乱了。事务就是把一组SQL操作打包成一个整体。最基本的事务语法START TRANSACTION; UPDATE 账户表 SET 余额 余额 - 1000 WHERE 账户ID A; UPDATE 账户表 SET 余额 余额 1000 WHERE 账户ID B; COMMIT; -- 提交让改动永久生效如果中间某条语句失败可以执行ROLLBACK回滚让所有改动全部撤销。很多命令行工具默认是自动提交的也就是每条语句自己就是一个事务。生产环境的代码则要注意不要在循环里一条一条提交事务要么用批量提交要么把一组逻辑包进一个大事务。事务太大也会有问题比如锁太多、耗时太长、回滚段膨胀这是后话。零基础先记住多表更新删除、多个步骤的写操作能包事务就包。3.5 主键、外键、约束对写操作的影响写数据时数据库不是照单全收。建表时设定的约束就是数据进出的“安检门”。主键约束插入时主键重复会报错除非用ON DUPLICATE KEY UPDATE或INSERT IGNORE等策略。唯一约束字段值不能重复比如身份证号、手机号。非空约束字段不允许NULLINSERT时忘填会报错。外键约束插入子表数据时外键值必须在父表主键里存在否则报错删除父表数据时如果子表还有引用会被拦截或级联处理。外键约束常见的处理方式有两种-- 有子表引用时禁止删除 FOREIGN KEY (用户ID) REFERENCES 用户表(用户ID) -- 删除父表记录时把子表外键置为NULL FOREIGN KEY (用户ID) REFERENCES 用户表(用户ID) ON DELETE SET NULL还有ON DELETE CASCADE删除父表记录时自动把子表关联记录一起删掉。这个很危险我曾经在一个模拟项目里用CASCADE一条DELETE把几千条关联数据静默删光了。所以我不建议默认用级联删除要么程序里分步处理要么用软删除加一个is_deleted标记列。4. 实战练手从零做一个查询统计4.1 设计练习场景和数据理论讲得再多不动手都是纸上谈兵。给你一个可以直接照做的练习场景模拟一个简单的学生成绩系统三张表。创建学生表CREATE TABLE 学生表 ( 学号 INT PRIMARY KEY, 姓名 VARCHAR(50) NOT NULL, 性别 VARCHAR(10), 班级 VARCHAR(50), 入学年份 INT );创建课程表CREATE TABLE 课程表 ( 课程号 INT PRIMARY KEY, 课程名 VARCHAR(100) NOT NULL, 学分 DECIMAL(2,1) );创建成绩表CREATE TABLE 成绩表 ( 学号 INT, 课程号 INT, 成绩 DECIMAL(5,1), FOREIGN KEY (学号) REFERENCES 学生表(学号), FOREIGN KEY (课程号) REFERENCES 课程表(课程号) );插入几条数据然后用前面学的知识回答以下问题查所有女生的姓名和班级。查2023年入学的学生按班级分组统计每个班人数。查每门课程的平均分按平均分降序排序。查每个学生所有课程的总分找出总分前3名。查没选任何课程的学生。第5个问题思路是学生表LEFT JOIN成绩表成绩表字段为NULL说明没选课。写出来是SELECT s.姓名 FROM 学生表 s LEFT JOIN 成绩表 sc ON s.学号 sc.学号 WHERE sc.学号 IS NULL;这五道题覆盖了选择、过滤、分组、聚合、排序、连表做完基本入门。4.2 一个完整实操统计课程不及格率假设你要汇总每门课的不及格人数和不及格率结果包含课程名、总人数、不及格人数、不及格率。第一步先想清楚需要哪些信息课程名在课程表成绩在成绩表。于是用课程表JOIN成绩表同时分组建课程号聚合统计。SELECT c.课程名, COUNT(*) AS 总人数, SUM(CASE WHEN sc.成绩 60 THEN 1 ELSE 0 END) AS 不及格人数, ROUND( SUM(CASE WHEN sc.成绩 60 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2 ) AS 不及格率 FROM 课程表 c LEFT JOIN 成绩表 sc ON c.课程号 sc.课程号 GROUP BY c.课程号, c.课程名;这里用到了CASE WHEN它相当于在SQL里写if-else。如果没学也没关系按这个结构先抄一遍之后再深入。LEFT JOIN保证没人选的热门课程也能显示出来不然这种课程连参与统计的机会都没有。4.3 常用“套路”总结练习多了会发现很多复杂需求其实是由几个固定套路组合而成“每个X的Y统计” GROUP BY X 聚合函数。“前N名” ORDER BY 指标 DESC LIMIT或TOP取决于数据库。“A存在但B不存在的记录” LEFT JOIN 右表IS NULL或者NOT EXISTS。“数量超过N的组” GROUP BY HAVING COUNT(*) N。“求某个值占整体的比例” 分子用CASE WHEN或SUM(条件表达式) / 总聚合。建议你做一个“SQL函数速查笔记”把CASE WHEN、字符串拼接、日期格式化、ROUND这些高频函数踩过的坑记下来。这不只是给新手用我工作好几年了也还在补这类笔记。5. 避坑指南你一定会遇到的问题5.1 NULL三值逻辑比你想的更坑普通逻辑里条件的结果是“真”或“假”但SQL里还有NULL于是出现三值逻辑。NULL既不是真也不是假而是“未知”。这导致一个现象在WHERE里写 字段 NULL永远过滤不出任何数据因为“NULL NULL”的结果不是真而是UNKNOWN。具体表现为SELECT * FROM 学生表 WHERE 姓名 NULL; -- 查不出来 SELECT * FROM 学生表 WHERE 姓名 IS NULL; -- 正确写法聚合函数遇到NULL也会自动忽略。COUNT(字段)不统计NULL但COUNT(*)统计。AVG也是。如果某列有一半是NULL你求平均值时到底想除以多少默认只对非NULL值求平均。这很容易造成数据偏差。对策分组统计时先考虑“这个字段有没有NULL”NULL是否需要被计为0、未知还是直接剔除。建议把有大量NULL的表字段在查询前先SELECT一下用肉眼扫一眼分布再写逻辑。5.2 字符串、日期、编码、字符集中国用户玩SQL遇到中文乱码的频率极高。如果你连接MySQL后看到中文全是问号基本是字符集不匹配。连接参数加utf8mb4SET NAMES utf8mb4;建库建表也尽量用utf8mb4不要用utf8因为utf8在MySQL里最多存3个字节生僻字和Emoji会插入失败。这是MySQL独有的坑PostgreSQL基本没这问题。字符串比较方面MySQL默认排序规则不区分大小写但查询时用LIKE、默认可能不区分大小写PostgreSQL区分。跨数据库开发时不要依赖“默认行为”要在查询里写清楚。日期时间类型同样暗坑不少。常见错误是把日期存成字符串比如“2024-5-1”这样按日期排序会变成字符串排序导致“2024-10-1”排在“2024-5-1”前面。建议日期列统一用DATE或DATETIME类型比较时用DATE()函数转化。查询某天的数据别用 BETWEEN 2024-05-01 AND 2024-05-02 这种口径很容易误把5月2号凌晨的数据也算进去。正确写法是SELECT * FROM 订单表 WHERE 下单时间 2024-05-01 00:00:00 AND 下单时间 2024-05-02 00:00:00;半开区间比闭区间更可靠。5.3 保留字、别名、大小写有些列名正好是SQL保留字比如order、group、select、desc。直接写会报错。两种处理方式建表时避免用保留字当列名非用不可时加上反引号MySQL或双引号PostgreSQL、SQL标准SELECT order FROM 订单表; -- MySQL SELECT order FROM 订单表; -- PostgreSQL用中文列名虽然能跑但跨平台、团队协作都不推荐。我见过有些内部工具生成的SQL列名全是中文改需求时改得痛不欲生。规范做法是英文列名显示时用别名转中文比如SELECT 姓名 AS name FROM 学生表;反过来也行英文列名 AS中文别名专门给报表看。比如SELECT user_id AS 用户ID, user_name AS 用户名 FROM 用户表;5.4 索引和性能新手容易忽视零基础阶段不需要深入研究索引但有两个观念建议早点建立WHERE条件里经常用来筛选的列建议建索引。学生表几万条数据时不明显几百万条时建没建索引可能是0.1秒和10秒的差别。查询时别用函数包住索引列。比如 WHERE YEAR(下单时间) 2024哪怕下单时间有索引这么写会导致索引失效因为数据库没法对函数结果使用普通索引只能全表扫描。换成范围查询SELECT * FROM 订单表 WHERE 下单时间 2024-01-01 AND 下单时间 2025-01-01;另外LIMIT高偏移量分页越到后面越慢。比如LIMIT 1000000, 20数据库得先把前100万行找出来再丢效率很差。这时候可以用上一页的最后一个ID做条件SELECT * FROM 订单表 WHERE 订单ID 1000000 ORDER BY 订单ID LIMIT 20;这个方式只适用于主键递增的场景业务有删除、乱序插入时要重新评估。5.5 别在循环里查数据库这个问题严格来说不算是SQL语法问题但几乎所有写SQL的人都会踩而且面试必问。比如程序里循环100个用户每个用户查一次订单表表现出来就是“数据库CPU突然飙高页面卡死”。正确做法是一次性查出所有用户的订单再在程序里做关联SELECT u.用户ID, o.订单号 FROM 用户表 u LEFT JOIN 订单表 o ON u.用户ID o.用户ID WHERE u.用户ID IN (101, 102, 103, ...);以后你会听到一个名词叫N1查询本质就是“先查N条主表记录再循环每条去查关联表”。能JOIN就JOIN能一次查出来就别循环查。6. 常见问题速查与排查思路6.1 报错信息怎么看看到SQL报错别慌先看三样东西哪个数据库、什么错码、错在哪一行。比如MySQL报“You have an error in your SQL syntax”后面会跟一个片段那附近的语法就有问题。常见报错对照报错常见开头原因解决办法Unknown column列名写错或者表不存在该字段检查建表语句看字段列表Syntax error / near语法错误可能标点、关键词冲突、漏括号检查中英文符号、保留字、括号Column cannot be null插入时没给非空字段赋值检查NOT NULL字段Duplicate entry for key主键或唯一键冲突找重复值决定忽略还是覆盖Data too long for column字符串值超过了定义的长度调整VARCHAR长度或修改数据Cannot delete or update a parent row外键约束阻止了父表删除操作先处理子表引用或者用级联删除Deadlock found多个事务互相等待锁导致死锁调整事务顺序缩短事务时间6.2 写复杂SQL前先拆需求遇到一条复杂SQL我习惯先用自然语言说清楚需求再转成SQL步骤“表A有哪些列、表B有哪些列”“哪些列是关联条件”“要筛选什么条件”“要不要分组、分组后要不要过滤”“最后排序、分页怎么处理”比如“统计每个城市的订单总金额只保留总金额大于10000的城市按总金额降序取前5”。拆解之后是城市来自用户表或订单表订单总金额SUM(金额)按城市分组HAVING过滤ORDER BY排序LIMIT取前5。写起来就顺了。6.3 使用EXPLAIN看执行计划如果你的SQL跑得很慢直接上EXPLAIN。EXPLAIN不真正执行查询会告诉你它打算怎么查EXPLAIN SELECT ... FROM ... WHERE ...;重点看type列如果是ALL说明是全表扫描建议优化如果是ref或eq_ref说明用到了索引。rows列是估计扫描的行数越大越值得关注。这个工具不需要你会看全部信息抓住“是否走索引”“扫描了多少行”两个点就已超过一大批选手。遇到慢查询时先用EXPLAIN看全表扫描再想办法加索引或改写SQL。6.4 数据修复的常规思路线上误更新、误删除第一件事是冷静。如果是MySQL并且开启了binlog可以借助binlog找回如果有备份就恢复备份后按时间点追平。零基础不需要马上学会这些但你要知道尽量开启数据库自动备份每次大范围更新数据前先备份相关表用CREATE TABLE 备份表 AS SELECT * FROM 原表;或者导出SQL文件写更新删除语句时先SELECT验证结果。我自己经历过一次模拟项目的教训写DELETE忘了WHERE一瞬间清空了业务表。后来所有高危SQL无WHERE条件的UPDATE/DELETE都必须经过另一个同事确认同时数据库账号分成只读和读写权限。这个习惯救了我好多次。7. 学习顺序建议与练习方向7.1 按什么顺序学最省力不少零基础的朋友一上来就啃完整语法手册背了几百个函数结果真写的时候还是找不到北。我建议按这个顺序来建表、插入几条数据能跑通SELECT基础选择列、WHERE、ORDER BY、LIMIT聚合分组COUNT、SUM、AVG、GROUP BY、HAVINGJOIN先INNER JOIN再LEFT JOIN子查询UPDATE/DELETE/INSERT配合事务索引、执行计划、性能优化。不要追求“每个函数都背下来”。用得多了自然记住记不住就查手册比死记硬背高效得多。7.2 去哪找练习题想系统地练SQL有几个方向可以参考各大平台都有SQL在线练习题库搜索“SQL练习平台”就能找到建议选支持MySQL的题目从简单到复杂层级分明的网上能找到很多开源数据集比如电商订单、电影评分拿这些数据做“自由练习”自己给自己出题比如“每个电影类别的平均评分”“最近一周的订单量趋势”参与一些技术社区的SQL挑战题批量生成的题目覆盖了JOIN、子查询、窗口函数等进阶内容。练习的关键是输出。每次做题时把题目要求翻译成SQL再对照答案想想为什么别人用JOIN而你写子查询差别在哪。时间久了你看到需求脑子里就会自动浮现“这题要GROUP BY HAVING”。7.3 窗口函数和进阶知识的启动时机学会了基础查询和聚合推荐再接触一个非常实用的新工具窗口函数。它和GROUP BY最大的区别是窗口函数不会把多行压成一行而是保留每一行同时在旁边给你算出一个聚合值。比如查每个班级的学生成绩并额外显示该班级平均分SELECT 班级, 姓名, 成绩, AVG(成绩) OVER (PARTITION BY 班级) AS 班级平均分 FROM 成绩表;这个能力在排名、同环比、移动平均等场景里非常常用。零基础不要一上来就学它但练熟GROUP BY之后这绝对是下一步最值得学的内容。8. 最后分享几个实操体会我自己带过不少入门者总结下来能坚持学会SQL的人都有一个共同习惯不空想直接敲。哪怕第一天只会建表插入也要动手造数据。SQL这门技能看一百篇教程不如亲手写一百条语句。我见过太多人卡在“概念都懂一写就错”的状态破解方法只有一个——代码报错就去查查完再写循环几轮就熟了。另外建议养成写SQL的格式习惯。关键字大写、表名字段名用小写、每行一个字段、用缩进表示逻辑层次。这能让其他人——包括未来的你自己——更容易读懂你的SQL。我以前写SQL也是一坨挤在一起后来做了一次项目维护看到自己两个月前写的“天书”才痛下决心规范格式。还有一个习惯值得培养任何影响数据的操作先问自己三个问题——会影响多少行是否在事务里能否回滚这三个问题能帮你避开绝大多数数据事故。如果你工作中必须操作正式库开始动手前哪怕多花两分钟SELECT验证一下都不亏。SQL这条路越往后越能体会到它的威力。希望这篇笔记能让你少走点弯路早日写出第一条让自己满意的查询。