简介:一份面向SQL初学者备考与复习的速查笔记,提炼自B站Mosh老师三小时SQL入门教程。资源以1个PDF文件呈现,压缩包约2.43MB,适合配合原视频学习,也适合已了解基本概念、需要快速回顾核心语法的读者。内容从基础查询入手,系统梳理SELECT、WHERE及AND、OR、NOT等逻辑操作符,并覆盖IN、BETWEEN、LIKE与REGEXP的过滤写法,以及IS NULL、ORDER BY、LIMIT等进阶子句。同时包含内连接、外连接、USING、交叉连接、联合等表关联常用方法,并在最后给出插入数据的语法示例,基本串起单表查询到多表联查再到数据写入的完整路径。已有973人学习浏览,对正在上手SQL或准备面试基础题的开发者有一定参考价值,可以按主题快速翻阅,省去回看视频逐帧查找的时间。
1. B站Mosh老师SQL三小时课程笔记:一次跟学能省三天的入门路线
很多人刷B站时把Mosh老师那套SQL三小时课程放进收藏夹,想着哪天系统学一遍,结果多半是吃灰。这套课程的实际定位很明确:它用三个小时覆盖了从SELECT基础到多表连接、聚合、子查询、窗口函数和存储过程的完整语法链,比翻一本五百页的SQL教材快得多,又比刷碎片化教程成体系。如果你是要把SQL当作日常工作工具,而不是去卷数据库原理,那么这份笔记适合你。我跟着课程整理了一份可复现的跟学路径,把环境配置、关键语法和所有踩过的坑一次说清。
2. 跟学前的环境准备:MySQL 8 + Navicat的最小可复现配置
2.1 为什么选MySQL 8而不是SQL Server或Oracle
Mosh老师的课程主线基于MySQL语法,B站版本里几乎所有示例都跑在MySQL上。很多初学者会犹豫:公司用的是SQL Server,我要不要跟着课程装SQL Server?我的建议是不要。课程里的建库语句、存储过程写法、窗口函数调用都是MySQL风格,你用SQL Server去执行,大概率在存储过程和默认配置上先翻车一轮,学完还得再学一次方言差异。先用MySQL把课程跑通,之后切到SQL Server或Oracle时,语法迁移成本比你想的低得多。
安装版本上选MySQL 8.0或更新的8.x系列就行,不要装5.7。原因有两个:一是窗口函数在8.0里才是完整可用的,课程后段讲RANK、LAG这些函数时,5.7会直接报语法错误;二是8.0的默认字符集是utf8mb4,省去很多中文乱码的麻烦。装的时候注意选“Server only”最小化安装,开发版和完整版里带的一大堆组件我们用不上,反而会拖慢启动速度。
数据库客户端我用Navicat Premium,版本16或17都可以。Navicat的查询编辑器对新手最友好的一点是能看到完整结果集,还能直接右键导出Excel,比MySQL Workbench的界面直观很多。这里先提醒一句:Navicat是商业软件,试用期只有14天,跟完三小时课程绰绰有余;网上那些所谓激活码来源不明,风险后面在第5章专门说。
2.2 安装与建库:把课程配套样例库装进本地
装好MySQL后,第一件事不是急着看视频,而是先把课程里要用的四个样例库准备好。Mosh课程里反复出现的库名是sql_store、sql_inventory、sql_hr和sql_invoicing,其中sql_store(电商订单场景)出场率最高。视频里他会演示用MySQL Workbench的图形按钮导入,但我们用命令行或Navicat脚本执行也一样能跑,关键是拿到对应的.sql文件。
我一般直接用Navicat的“运行SQL文件”功能导入,也可以先在命令行里建好库再执行脚本。下面是最小可复现的命令行流程:
# 登录MySQL,-u指定用户,-p表示需要密码,注意-p和密码之间不要有空格 mysql -u root -p # 登录后创建课程使用的数据库,字符集必须指定utf8mb4 CREATE DATABASE IF NOT EXISTS sql_store DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 退出mysql交互终端,用source执行.sql文件 # 假设你已经把课程提供的sql_store.sql下载到了本地 mysql -u root -p sql_store < /path/to/sql_store.sql上面命令的核心逻辑是先建一个空库,再把.sql文件里的建表语句和数据插入语句灌进去。用<重定向执行SQL脚本和手动在终端里逐条粘贴是等效的,但重定向方式不会遇到粘贴超长内容被截断的问题。如果你是用Navicat,操作为右键数据库选择“运行SQL文件”,效果一致。
2.3 Navicat查询窗口的三个必调设置
打开Navicat后,连接到本地MySQL实例,这时还别急着写SQL,先在查询窗口里做三个设置。第一个是把结果集限制关掉,默认情况下Navicat只显示前1000条记录,课程里join多表后返回结果往往超过这个数,你以为是自己写错了,其实是显示被截断。在“工具-选项-记录”里把限制改成不限制,或者临时用“LIMIT 100”控制输出。
第二个是把“自动提交”打开,位置在查询菜单栏里。Mosh的课程中段会讲INSERT、UPDATE、DELETE的写法,自动提交开启后每次执行都能立即看到数据变化,对初学者验证结果特别直观。如果你不开,执行完UPDATE发现表没变,很容易怀疑是不是语句写错了,其实只是没提交。
第三个是确认连接字符集。连接属性里有个“编码”选项,必须选utf8mb4。这个不设置的话,SELECT语句里带中文条件(比如WHERE city = '北京')会查不出结果,因为客户端和服务器之间用latin1传输中文,直接乱码。这三项设置做完,跟学过程就不会被环境问题反复打断。
3. 三小时课程的主线拆解:从SELECT到窗口函数的递进结构
3.1 SELECT与DISTINCT:先把单表查询的姿势练对
课程开头一个小时都在讲单表查询,这部分看似基础,却是后面所有复杂查询的地基。Mosh的顺序是先讲SELECT指定列,再讲WHERE过滤条件,然后讲ORDER BY排序和LIMIT分页。很多自学的人会跳过这个阶段直接学JOIN,结果就是连字段别名和运算列都看不懂。
单表查询里最容易被忽略的是执行顺序。你在一条SQL里同时写WHERE、GROUP BY、ORDER BY时,数据库的执行顺序是FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。这意味着SELECT里定义的别名无法在WHERE里使用,只能用在ORDER BY上。课程里他会写这样一个例子:
SELECT customer_id, first_name, points, points * 1.1 AS bonus_points FROM customers WHERE points > 2000 ORDER BY bonus_points DESC LIMIT 5;这条SQL的逻辑很清晰:查出积分大于2000的客户,计算每个人的奖金积分(原积分乘以系数1.1),按奖金降序取前五名。注意两点:一是AS bonus_points这个别名在ORDER BY里能用,但你想在WHERE里写WHERE bonus_points > 1000就会报错,因为WHERE在SELECT之前执行;二是LIMIT 5要放在最后,不然排序就白做了。
DISTINCT去重也是这部分的高频考点。课程里会演示SELECT DISTINCT state FROM customers来查看哪些州有客户。这里有个热词场景,就是“sql语句去重”的实际应用:DISTINCT是返回结果集的去重,它和GROUP BY的去重逻辑有差异,前者不配合聚合函数时只做简单去重,后者一般带着COUNT、SUM这类聚合一起出现。如果你只想看某个字段有哪几种取值,用DISTINCT更直观。
3.2 JOIN多表连接:课程中段的分水岭
课程第二小时进入多表连接,这是整个三小时里最容易劝退人的部分。Mosh的处理方式很务实:先讲INNER JOIN,再讲LEFT JOIN和RIGHT JOIN,最后讲USING和自连接。学JOIN的关键不是记语法,而是先想清楚“我要哪个表做主表”,主表决定了最终结果集保留哪些行。
以sql_store库为例,orders表和customers表通过customer_id关联。INNER JOIN只返回两边都能匹配上的记录,也就是说如果一个客户没有任何订单,他就不会出现在结果里。而LEFT JOIN以左表orders为主,即使某客户没有订单也会保留该订单记录(字段显示NULL)。Mosh课程里的经典示例是查每个客户的订单数量:
SELECT c.customer_id, c.first_name, COUNT(o.order_id) AS order_count FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.first_name ORDER BY order_count DESC;这条语句涉及三个知识点叠加:表别名(customers c表示c是customers的别名)、LEFT JOIN保留所有客户、GROUP BY配合COUNT统计订单数。为什么用LEFT JOIN而不是INNER JOIN?因为你想看的是所有客户的订单情况,包括那些从来没有下过单的客户——如果用了INNER JOIN,零订单客户会凭空消失,业务报表就失真了。这就是课程里反复强调的选型判断:先定主表,再定连接类型。
3.3 聚合函数与HAVING:GROUP BY的边界在哪里
聚合是SQL里语义最丰富也最容易写错的部分。Mosh课程会演示COUNT、SUM、AVG、MIN、MAX配合GROUP BY的用法,但真正考验人的是HAVING和WHERE的区别。很多人在分组查询里筛选行,下意识在WHERE后面写聚合条件,直接报错。
规则只有一句话:WHERE过滤的是原始行,HAVING过滤的是分组后的结果。比如你统计每个州的客户数量,只想看客户数大于2的州:
SELECT state, COUNT(*) AS customer_count FROM customers GROUP BY state HAVING COUNT(*) > 2 ORDER BY customer_count DESC;如果你在WHERE里写COUNT(*) > 2,MySQL直接报“无效使用组函数”的错误。这里还藏着一个MySQL 8特有的坑:SELECT后面出现的非聚合字段必须全部出现在GROUP BY中。上面的state在GROUP BY里有,所以没问题;但如果你多选一个city又没写进GROUP BY,MySQL 8的ONLY_FULL_GROUP_BY模式会直接报错,而5.7默认可能只是警告。这个坑在课程视频里没仔细讲,很多人跟到这一步就卡住了。
3.4 窗口函数和存储过程:三小时课程的进阶天花板
课程最后半小时节奏明显加快,Mosh会用一组对比展示窗口函数和普通聚合的差异。普通GROUP BY会让多行合并成一行,窗口函数则是保持每一行原始记录不变,同时把聚合结果挂在每行后面。这个特性对排名、同环比、累计求和这类场景是革命性的。
SELECT customer_id, order_date, order_id, ROW_NUMBER() OVER ( PARTITION BY customer_id ORDER BY order_date ) AS order_rank FROM orders;这段代码把orders表按客户分组,组内按日期排序并编号,每组从1开始。ROW_NUMBER()是每行唯一编号,PARTITION BY customer_id是分组界限,ORDER BY order_date是组内排序规则。如果你想获得课程热词里那些SQL窗口函数面试题的答案,核心就是把OVER子句里的三个组成部分想清楚:窗口怎么切、组内怎么排、函数算什么。窗口函数之后课程还有视图和存储过程的快速演示,这部分能听懂最好,听不懂也不影响前面的基础,可以二刷时再补。
4. 课后复现:把课程笔记转成自己的20条关键SQL
4.1 复现清单:每节课抽一条代表性语句
看完视频和记笔记是两回事,真正让知识变成技能的是复现。我整理课程时做了一个清单:每一段视频抽一到两条核心SQL,用自己的话重新写一遍,再换到自己的数据表上跑。下面这20条覆盖了课程九成考点的清单路径,你可以直接照着来。
| 模块 | 关键语法 | 对应课程位置 |
|---|---|---|
| 基础查询 | SELECT / WHERE / ORDER BY | 第1小时前段 |
| 去重 | DISTINCT / 空值过滤 | 第1小时中段 |
| 分页 | LIMIT / OFFSET | 第1小时中段 |
| 连接 | INNER / LEFT / RIGHT JOIN | 第1小时后段 |
| 自连接 | 同表多次JOIN | 第1小时末 |
| 聚合 | GROUP BY / HAVING | 第2小时前段 |
| 子查询 | WHERE子查询 / FROM子查询 | 第2小时中段 |
| 增删改 | INSERT / UPDATE / DELETE | 第2小时后段 |
| 窗口函数 | RANK / ROW_NUMBER / LAG | 第3小时前段 |
| 存储过程 | CREATE PROCEDURE | 第3小时末 |
与其追求刷题数量,不如每一条都做到不查笔记能默写出来。我复现时有一条经验:把课程里的customer/order表换成自己业务里的表结构,逻辑完全不变,但如果你能迁移到新表上,说明你理解的是SQL语义而不是死记表名。
4.2 构造自己的练习库:从订单表到成绩表
课程样例库的局限是数据量太小,几十条记录根本看不出性能问题。我自己复现时额外建了一个成绩表,用来练聚合和窗口函数,数据量自己插入,感觉完全不同。下面是建表和插入数据的脚本:
-- 建一张学生成绩表,包含学号、姓名、科目、分数和考试日期 CREATE TABLE scores ( student_id INT, student_name VARCHAR(50), subject VARCHAR(20), score DECIMAL(5,2), exam_date DATE ); -- 插入模拟数据,同一学生有多科多场考试成绩 INSERT INTO scores VALUES (1, '张三', '语文', 85.0, '2024-03-01'), (1, '张三', '数学', 92.0, '2024-03-01'), (1, '张三', '语文', 70.0, '2024-06-01'), (2, '李四', '语文', 88.0, '2024-03-01'), (2, '李四', '数学', 79.0, '2024-03-01'), (3, '王五', '英语', 65.0, '2024-03-01');这段脚本的要点是:DECIMAL(5,2)表示总分五位小数两位,存学科分数足够;插入语句用了多行VALUES,一次插六条,比逐条插入高效。建这张表的意义在于后面去重、排名、环比都能练到。课程里的订单表你只能按视频走,这张表是你自己的数据,想怎么折腾都不心疼。
4.3 核心语句复现:排名、去重与子查询
有了scores表,就能把课程知识点真正用一遍。先看一个综合查询:统计每个学生在每个科目上的最近一次考试成绩与名次。这里用到了窗口函数DENSE_RANK和子查询的组合:
SELECT student_id, student_name, subject, score, exam_date, DENSE_RANK() OVER ( PARTITION BY subject ORDER BY score DESC ) AS subject_rank FROM ( SELECT student_id, student_name, subject, score, exam_date FROM scores ) AS t ORDER BY subject, subject_rank;子查询在这里的作用是先把所有记录取出来,外层再做窗口计算。DENSE_RANK()和RANK()的差别是:DENSE_RANK在分数并列时编号连续(1、2、2、3),RANK会跳过(1、2、2、4)。如果你要的是班级排名且允许并列,DENSE_RANK更符合业务直觉。ORDER BY放在整个窗口计算之后,所以可以用subject_rank这个别名排序。
接着看sql语句去重的典型题:找出每个学生最近一次考试记录,也就是按学生分组取最新日期那一行。这个用普通GROUP BY写不出来,因为分组后你只能拿到聚合值,拿不到那一行的完整字段。更准确的解法是用窗口函数ROW_NUMBER配合CTE:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY student_id ORDER BY exam_date DESC ) AS rn FROM scores ) SELECT student_id, student_name, subject, score, exam_date FROM ranked WHERE rn = 1;这段的核心是CTE(WITH子句)先把每个学生按考试日期倒序编号,日期最近的编号为1,外层查询只保留rn等于1的行。这是“每组取最新一条”的通用解法,比先GROUP BY再回表JOIN的性能和可读性都好。我在实际工作中处理业务表的重复数据,也是这一招。
最后验证一下空值处理。课程里讲IS NULL判断空值,很多新手会用= NULL,这是SQL里最经典的错误。NULL不是值,它是“未知”,所以不能用等于或不等于来比较。正确写法是:
SELECT student_id, student_name FROM scores WHERE score IS NOT NULL ORDER BY score DESC;如果这里你写WHERE score != NULL,返回结果永远是空集,因为任何和NULL的比较结果都是UNKNOWN,会被WHERE过滤掉。这也是面试里高频出现的SQL空值陷阱,跟课程示例里address为空时的处理逻辑一致。
4.4 结果校验:怎么确认自己写对了
复现完SQL不代表结束,还要有一双能验证结果的眼睛。我的习惯是每条查询先问三个问题:返回行数是多少?有没有重复行?聚合数字粗略估算是否合理?比如上面取每个学生最新记录的查询,正确结果应该有三行,因为scores表里只有三个学生;如果返回六行,那一定是RN过滤条件写错了。
另一个校验方法是把窗口函数的结果和普通GROUP BY的结果交叉对比。比如用COUNT(DISTINCT student_id)确认学生总数,再用分组查询算每个学生的考试次数,两者互相印证。实践里我会直接用Navicat的导出功能把结果导成Excel,再拉透视表看一眼,既能验证SQL正确性,又锻炼了数据校验的思维。SQL写出来跑通不等于写对,数据量小的时候肉眼核对是最快的后悔药。
5. 跟学Mosh课程笔记的避坑与常见排查
5.1 连接不上MySQL:服务没起还是端口被占
现象:打开Navicat点连接,提示“Can't connect to MySQL server on localhost (10061)”,或者命令行执行mysql -u root -p直接报ERROR 2003。第一次装MySQL的人八成会卡在这一步。
原因:MySQL服务没有启动,或者端口3306被其他进程占用。Windows上安装时如果你没勾选“配置为Windows服务”,关闭安装窗口后MySQL就停了。另外有些人电脑上有旧版MySQL或者虚拟机软件占用3306,导致新装的MySQL起不来。
解决:先按Win+R输入services.msc打开服务管理器,找到MySQL80服务,右键启动,并把启动类型改成自动。如果服务启动后仍然连不上,用命令行检查端口占用:
netstat -ano | findstr :3306 tasklist | findstr 进程号如果发现PID对应的进程不是mysqld.exe,说明3306被其他程序占用了。要么在my.ini里改端口,要么结束占用进程。还有一个冷门坑:MySQL 8默认的认证插件是caching_sha2_password,Navicat旧版本不支持,如果你用Navicat 15以下版本连8.0会报认证失败,升级客户端即可。
5.2 GROUP BY查询报错:ONLY_FULL_GROUP_BY模式的锅
现象:执行SELECT state, city, COUNT(*) FROM customers GROUP BY state时报错“Expression #2 of SELECT list is not in GROUP BY clause”,这条语句在课程视频的老版本MySQL里是能跑的。
原因:MySQL 5.7开始默认开启ONLY_FULL_GROUP_BY模式,要求SELECT后面出现的非聚合字段必须全部出现在GROUP BY里。上面的SQL里city既不在GROUP BY里,也没有被聚合函数包裹,数据库不知道这行的city该取哪个值,所以直接报错。课程里Mosh用MySQL老版本演示,很多语法在8.0下会翻车。
解决:把缺失的字段补进GROUP BY,或者用ANY_VALUE()包起来。业务语义上取哪个city并不重要时,用ANY_VALUE(city)最省事。我一般会优先改GROUP BY把字段补全,因为这能促使你思考分组粒度到底该怎么定。如果你确认不需要这个限制,可以执行SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')),但重启后会失效,而且关掉MySQL的安全机制不是好习惯。
5.3 查询结果中文乱码:字符集从连接到表的全链路
现象:INSERT中文后SELECT出来全是问号,或者WHERE city = '上海'查不到数据,但数据库里肉眼能看到中文。
原因:字符集在四个环节都要一致,缺一个就乱码。这四环是:客户端连接编码、数据库默认字符集、表的字符集、字段的字符集。最常见的是安装MySQL时默认用了latin1,或者建表时没指定utf8mb4。MySQL 8默认utf8mb4所以很少见,但如果你在MySQL 8里建表时显式写了DEFAULT CHARSET=latin1,还是会踩出来。
解决:检查并统一为utf8mb4。一条命令看清楚当前库和表的字符集,然后对已有的表做转换:
-- 查看数据库字符集 SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = 'sql_store'; -- 修改表字符集,convert to后面的utf8mb4同时转换所有文本列 ALTER TABLE customers CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Navicat连接属性里的编码也要同步设置成utf8mb4。这一套做完,中文才算是从存储到展示全链路通顺。注意ALTER TABLE ... CONVERT TO和ALTER TABLE ... DEFAULT CHARACTER SET的差别:前者会把现有列的数据重新编码转换,后者只改默认值不影响已存在的列,实操中要用前者才能真正修复乱码数据。
5.4 粘贴长SQL被截断:复制执行不如脚本执行
现象:在Navicat或命令行里粘贴一条很长的存储过程定义,执行后报语法错,或者感觉SQL后面一大截“消失了”。这是热词里“sql长度大于复制长度”的实际场景。
原因:Windows终端和部分GUI客户端对粘贴内容的长度有限制,尤其是一条CREATE PROCEDURE动辄几十行,粘贴时被截断成半截,自然语法报错。这个问题看着很玄学,其实是终端输入缓冲区的限制。
解决:不要粘贴长脚本,把SQL存成.sql文件,用source命令执行。Navicat里右键连接选择“运行SQL文件”,命令行里用前面第2章提过的重定向方式。文件方式执行没有长度问题,而且脚本可重复运行,比每次打开查询编辑器粘贴靠谱得多。
5.5 Navicat试用过期与激活码风险:免费的安全替代
现象:跟课跟到一半,Navicat提示试用期已结束,弹窗要求输入许可证。很多人第一反应是去搜索“navicat激活码”,下载注册机和补丁。
原因:Navicat Premium只有14天试用,三小时课程完全够用,但后续日常练习如果长期依赖它就很别扭。网上流传的激活码、绿色版、注册机,来源不明,经常捆绑挖矿木马或后门程序。作为技术从业者,数据库客户端能连到你的真实服务器,运行不明来历的破解工具风险太高,这个黑匣子不值得赌。
解决:如果只跟着课程学,试用版到期前足够跑完全部示例;如果以后要长期使用,两个选择:一是付费购买正版授权,二是用DBeaver Community——开源免费,同样支持MySQL、PostgreSQL、SQL Server,界面稍朴素但对学习完全够用。把连接字符串和查询习惯迁移过去半小时搞定。数据库是吃饭的工具,不要贪便宜装来路不明的破解包,这是我给你的血泪经验。
6. 课程之后的三步进阶:EXPLAIN慢SQL排查、窗口函数实战与索引验证
三小时课程给你的是语法广度,真正的实战能力要靠“查得慢时知道为什么慢”来建立。这里分享一个课程没讲但投产必备的验证技巧:用EXPLAIN分析一条慢SQL的执行计划。加一个关键字就能看到MySQL怎么走查询,这是从会写SQL到会优化SQL的关键一步。
我自己的进阶路径是这样:学完课程后,把scores表数据量插入十万行,再执行一条不加索引的查询,然后用EXPLAIN看它扫了多少行。比如WHERE student_name = '张三'这种条件,EXPLAIN结果里会显示type=ALL,意思是全表扫描,扫描行数等于整张表的数据量。这时候你在student_name上建一个索引再查一次,发现type=ref,扫描行数骤降。通过这个对照实验,索引的原理就从抽象概念变成了亲眼可见的差异。这正是慢SQL优化建议里最常见的一条:先看执行计划,再决定加不加索引,而不是凭感觉乱加。
第二个进阶方向是把窗口函数用于带业务语义的场景。课程只讲了语法,没讲怎么用。我建议你选一个自己的真实数据,比如订单表,分别算“每个用户本月消费排名”和“每个用户相比上一单的消费变化”。前者用DENSE_RANK,后者用LAG取上一行值做差值。这些查询在数据分析岗的面试里出现的频率极高,也是把窗口函数从看懂变成会用的最短路径。
最后给你一个检验课程吸收度的自测方法:不查笔记,在半小时内完成三件事——从一张订单表里查出最近七天每个城市的订单量并降序排列;用窗口函数给每个客户打上消费等级标签;写一条UPDATE把分数低于60分的记录统一改成60。三件事都能独立完成,这门课的入门目标就算真正达成了。我当年学完这门课又花了三个晚上做这类迁移练习才形成手感,期间踩过不少坑,但每一步都扎实。希望帮到你。
本文还有配套的精品资源,点击获取