很多想学数据分析或者刚转行做后端的朋友,都绕不开一个叫 SQL 的东西。但真去学的时候,往往第一天就被各种概念劝退了:数据库、表、字段、主键、外键、索引……还没开始写代码,先被名词砸晕。我自己刚接触那会儿也这样,买了一堆资料,看了三天视频,结果连“建一张表”都没搞明白。“SQL基础0”这个标题,说的就是真正从零开始,不预设你有任何数据库基础,连“表”和“Excel 表格”什么区别都不清楚的那种零。这篇内容,我会带你从数据库到底是什么、为什么需要它讲起,一路走到能自己建表、自己写增删改查,最后把过滤、排序、聚合、关联这些高频操作掰开揉碎讲清楚。看完之后,你能独立跑通一个练手项目,而不是只会照着教程敲例句。
这篇内容适合三类人:完全没碰过 SQL 的纯小白、学过一点但始终没串成体系的自学者、以及准备面试需要快速捡起 SQL 基础的求职者。我会尽量避开抽象的理论,多用实际例子和踩坑经历说话,让你看完能直接动手。
1. 学 SQL 之前,先搞清楚它到底在解决什么问题
1.1 数据库、表、SQL 是什么关系
很多教程上来就甩 CREATE TABLE、SELECT 这些语法,结果学生连“表”这个概念都还模糊。我习惯用一个类比来解释:把数据库想象成一个仓库,里面有一排排货架,每个货架就是一张表,货架上的每个格子是一个字段,格子里放的货物就是具体的数据。你操作仓库时不是直接伸手去翻货架,而是通过一套指令让仓库管理员帮你取放,这套指令就是 SQL。
听起来复杂,但拆开就三类角色:数据库是存储数据的地方,表是数据的组织方式,SQL 是和数据库对话的语言。你不需要先成为仓库管理专家才能下单,你只需要学会喊话的规则就行。
1.2 为什么先学 SQL 本身,而不是纠结选哪个数据库
新手最容易犯的一个错误,是花大量时间研究 MySQL、PostgreSQL、SQL Server 之间的区别,纠结该装哪个。实际上,90% 的基础 SQL 语法在这些数据库里是完全通用的:建表、增删改查、聚合、关联,写法几乎一模一样。差异主要体现在少数高级功能和存储引擎层面,对于基础阶段完全不用操心。
我见过有人为了“选对数据库”反复横跳了两个星期,最后啥也没学。我的建议是,随便选一个主流的装上,先跑起来再说。你先学会用标准的 SQL 把业务逻辑写出来,日后换数据库,最多改改少量细节,核心思路一点都不会浪费。
1.3 一套 SQL,走到哪都能用
SQL 的强大之处在于它的通用性。你可能会换公司、换项目、换数据库软件,但只要还会 SQL,到任何一家公司做数据相关工作,都能快速上手。甚至很多非技术人员,比如产品经理、运营,学会了 SQL 之后,自己拉数、自己做报表,再也不用排队等开发排期。
这也是为什么我强烈建议新人把 SQL 作为第一门要掌握的技能:它不像某些编程语言那样自带框架绑定,也不像工具类软件一样换个版本就要重新学。SQL 是贴近数据本质的语言,学会了就是长期资产。
2. 环境准备与第一张表:把基础语法落到实地
2.1 练手选哪个数据库最省心
对于零基础,我推荐直接用 MySQL,原因有三:第一,它免费,官方提供社区版,下载安装没有门槛;第二,它的周边工具多,比如可视化管理工具 Navicat、DBeaver 都支持得很好;第三,网上中文资料最多,遇到报错随便一搜就有答案。
当然,如果你机器上已经有 PostgreSQL 或者 SQLite,也没必要卸载重装。SQL Server 也可以,只要别在这个阶段陷入“选择困难症”。我记得有个朋友,为了比较各数据库的优劣,读了一整周对比文章,最后连建表语句都没写过一行——千万别学他。
2.2 建库建表的基本语法
一旦环境装好,第一步就是创建数据库和表。先创建一个练习用的库,名字随意,比如demo_db:
CREATE DATABASE demo_db;然后切换到你新建的库:
USE demo_db;接下来建一张最经典的学生表,用来做后续练习。这张表包含学生的编号、姓名、年龄、班级和入学日期:
CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age INT, class VARCHAR(20), enroll_date DATE );这段代码看着简单,但每一行都值得解释。id是主键,PRIMARY KEY保证每一行数据唯一;AUTO_INCREMENT表示自增,你不用手动填编号,数据库会自动生成。VARCHAR(50)是变长字符串,括号里的 50 代表最长 50 个字符,一般够用。DATE专门用来存日期,后面查询排序都很方便。
2.3 数据类型怎么选,别一上来就全用 VARCHAR
我见过不少新手的“骚操作”,就是把所有字段都定义成 VARCHAR,觉得反正都能存。短时间内确实能存进去,但后面做排序、统计的时候就会翻车。比如年龄字段如果存成字符串,按年龄排序就会变成按字典序排,“11”会排在“2”前面,完全不是你要的结果。
同样的道理,日期就该用 DATE 或者 DATETIME,金额就用 DECIMAL,数字就用 INT 或 BIGINT。选对数据类型,不仅让数据更规范,查询效率也更高,算是“一次选对,长期受益”。
2.4 实操:建表常见报错与注意点
如果你执行上面那段建表语句报错,最常见的原因是没先选库,也就是没有执行USE demo_db;,系统不知道要把表建在哪里。其次可能是库名冲突,再次建同名的库会报错,需要先删掉旧的库,或者换一个名字。
删除库和表的语法要慎用:
DROP DATABASE demo_db;这条命令会把整个库连同所有表一起销毁,没有后悔药。新手练习时无所谓,但到了公司环境,一条 DROP 语句就可能酿成大事故。我的习惯是,凡是涉及删除的操作,先SELECT出来看一眼再执行,确认无误后再删。
3. 增删改查:最基础也最常用的四板斧
3.1 插入数据 INSERT 的几种写法
建完表之后,第一步当然是往里面塞数据。最基础的插入语句长这样:
INSERT INTO student (name, age, class, enroll_date) VALUES ('张三', 18, '计算机1班', '2024-09-01');姓名、年龄、班级、入学日期按顺序对应前面括号里的字段。如果一次性插入多条数据,可以用逗号分隔:
INSERT INTO student (name, age, class, enroll_date) VALUES ('李四', 19, '计算机1班', '2024-09-01'), ('王五', 20, '计算机2班', '2024-09-02');这里有个细节:我没有给id设值,因为它是自增主键,数据库会自动生成。如果你硬塞一个值进去,比如id=1,下次插入时有可能会因为主键冲突报错。
3.2 修改数据 UPDATE:别忘了 WHERE
修改数据是新手翻车率最高的操作。语法很简单:
UPDATE student SET age = 21 WHERE name = '张三';如果不加WHERE条件,比如写成:
UPDATE student SET age = 21;这条语句会把表里所有学生的年龄都改成 21,而且数据库不会提醒你。这几乎是每个 SQL 新手都踩过的坑,我当年也踩过。所以请养成一个习惯:执行 UPDATE 之前,先用相同的WHERE条件跑一遍SELECT,确认你锁定的确实是要修改的那几行。
3.3 删除数据 DELETE:同样要谨慎
删除操作和修改一样,必须带WHERE,否则就是把整张表清空:
DELETE FROM student WHERE id = 3;这里的WHERE id = 3精确锁定了某一行。如果没有单独条件,只是想清空表里所有数据,可以用TRUNCATE TABLE student;,它更快,但没法用条件筛选。
注意:DELETE 之后只是把数据标记删除,在某些数据库里可以通过事务回滚恢复,但 TRUNCATE 是清空物理文件,恢复难度极大。练习归练习,到了真实环境,删除操作前一定要确认。
3.4 查询数据 SELECT:你花 80% 时间在这条语句上
实际工作中,增删改的频率远低于查询,SELECT 才是使用率最高的语句。最基本的写法:
SELECT * FROM student;*表示把所有字段都查出来。如果表字段很多或者只想关注某几列,可以明确列出字段名:
SELECT name, class FROM student;进一步,你可以给查询结果起别名:
SELECT name AS 学生姓名, age AS 年龄 FROM student;AS 可以省略,但写上更清晰。别名只在当前查询结果中生效,不影响原表结构。查询结果本身也是一张虚拟表,可以继续往下做操作,这是后面所有复杂查询的基石。
4. 查询进阶:过滤、排序、聚合才是真正开始用 SQL 思考
4.1 WHERE 的常见坑与正确用法
WHERE 是 SQL 里最常用的过滤条件,但用法上有几个关键细节。比如想查年龄大于 19 岁的学生:
SELECT * FROM student WHERE age > 19;想查某个范围内的数据,用 BETWEEN:
SELECT * FROM student WHERE age BETWEEN 18 AND 20;这里注意 BETWEEN 是闭区间,包含了 18 和 20 本身。如果想匹配字符串里的模式,用 LIKE:
SELECT * FROM student WHERE name LIKE '张%';%是通配符,代表任意多个字符,所以'张%'匹配以“张”开头的所有名字。还有一个容易犯的错:判断某个字段是否为空时,不要用= NULL,而要写IS NULL:
SELECT * FROM student WHERE class IS NULL;因为 NULL 在 SQL 里代表“未知”,任何和 NULL 做等值比较的结果都是“未知”,不会返回 True。
4.2 ORDER BY 排序的细节
排序是展示数据的高频操作,语法不难,但容易出细节问题。默认排序是升序 ASC,如果要降序就用 DESC:
SELECT * FROM student ORDER BY age DESC;如果想先按班级排序,同班级内再按年龄排序,可以写:
SELECT * FROM student ORDER BY class ASC, age DESC;这里两个排序条件之间用逗号分隔,前一条件优先级高。排序字段如果用了别名,在大部分数据库里也可以直接引用别名。还有一个容易忽略的点:如果排序字段有空值,空值的排序位置各数据库表现不一致,MySQL 里默认空值最小,升序时排最前面。
4.3 聚合函数与 GROUP BY:把多条记录压成一行统计结果
当你想统计“每个班有多少人”时,就需要聚合了。最常用的聚合函数有 COUNT、SUM、AVG、MAX、MIN。看一个实际例子:
SELECT class, COUNT(*) AS 人数 FROM student GROUP BY class;这条语句按班级分组,然后统计每组的人数。COUNT(*)统计的是组内行数。如果想统计某个班的平均年龄:
SELECT class, AVG(age) AS 平均年龄 FROM student GROUP BY class;GROUP BY 的结果里,只能出现分组字段和聚合函数计算出来的列。比如下面这条 SQL 在大多数数据库里会报错:
SELECT name, class, COUNT(*) FROM student GROUP BY class;因为name既不在 GROUP BY 中,也没有被聚合函数包住,数据库不知道该怎么展示这个名字。这个限制刚接触时比较容易迷惑,需要多写几次才能从直觉上接受。
4.4 HAVING 和 WHERE 千万别混用
WHERE 是在分组之前过滤数据,HAVING 是在分组之后过滤结果。比如:要先剔除年龄大于 25 的“非典型学生”再按班级统计,然后只显示人数大于 2 的班级,可以这样写:
SELECT class, COUNT(*) AS 人数, AVG(age) AS 平均年龄 FROM student WHERE age <= 25 GROUP BY class HAVING COUNT(*) > 2;很多人会把 HAVING 写成HAVING 人数 > 2,以为能用别名,但在部分数据库的查询执行顺序里,HAVING 的执行比 SELECT 的别名解析更早,直接写别名可能报错。最稳妥的做法是在 HAVING 里写完整的聚合表达式。
5. 多表联动:JOIN 到底是怎样的一种操作
5.1 为什么有了一张表还不够
前面练习的 student 表孤零零的,现实中数据通常是拆分在多张表里的。比如学生选了哪些课程,单独存一张选课表,而不是把课程信息塞进学生表。理由很简单:如果一个学生选了 10 门课,塞进学生表就要重复 10 行学生信息,又浪费空间又容易出错。
于是就有了多表关联的需求。我先建一张选课表,关联学生表和课程表:
CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(50) ); CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT, course_id INT );student_course 里的 student_id 和 course_id,分别指向 student 表和 course 表的主键。
5.2 INNER JOIN 和 LEFT JOIN:先分清主次
想查出每个学生选了哪些课,需要把三张表关联起来:
SELECT student.name AS 学生姓名, course.course_name AS 课程名称 FROM student_course INNER JOIN student ON student_course.student_id = student.id INNER JOIN course ON student_course.course_id = course.id;INNER JOIN 只返回两边匹配上的数据,如果一个学生没有选课记录,他就不会出现在结果里。但如果你想知道“每个学生选了哪些课,没选课的也显示出来”,就要用 LEFT JOIN:
SELECT student.name AS 学生姓名, course.course_name AS 课程名称 FROM student LEFT JOIN student_course ON student.id = student_course.student_id LEFT JOIN course ON student_course.course_id = course.id;LEFT JOIN 在执行时,左边的表(这里是 student)是主表,不管右边有没有匹配,左边都会保留。我的理解方式是:INNER JOIN 只要交集,LEFT JOIN 是左表全要,右表能配上就配。
5.3 关联查询的常见翻车现场
JOIN 最容易翻车的地方,是关联条件写错导致出现笛卡尔积。如果你写:
SELECT * FROM student, student_course;不带 ON 条件的多表查询,会返回两个表的“所有组合”,也就是左边表每一行都跟右边表每一行配对一次。如果 student 有 100 条,student_course 有 1000 条,结果就是 10 万条数据,瞬间爆炸。
另一个常见问题是字段名冲突,两张表都有 id 时,直接写SELECT id会报错,必须写成student.id或course.id。这些细节不能靠背,得靠多写多踩。
6. 从入门到能上手:常见报错表格与避坑经验
6.1 新手高频报错速查表
| 报错场景 | 现象 | 原因 | 解决办法 |
|---|---|---|---|
| 建表时报库不存在 | Unknown database | 没用 USE 选中数据库 | 先执行 USE demo_db 或者用 库名.表名 的写法 |
| 插入字符串少了引号 | 语法错误 | 字符串值没加单引号 | 字符串用单引号包裹 |
| UPDATE 没加 WHERE | 所有行都被修改 | 忘写过滤条件 | 先 SELECT 预览,再执行 UPDATE |
| GROUP BY 查询报错 | 非聚合列报错 | SELECT 里放了分组外的字段 | 只保留分组字段和聚合函数 |
| 日期比较没加引号 | 结果不对或报错 | 日期常量没写成字符串 | 写 '2024-09-01' 而不是 2024-09-01 |
| 多表联查结果异常多 | 笛卡尔积 | JOIN 少了 ON 条件 | 检查关联条件是否写全 |
这张表只是冰山一角,但覆盖了新手前两周最常碰到的几类问题。遇到报错不要慌,先读错误信息,再检查语法顺序,99% 的问题都能自己定位。
6.2 练手建议:从模仿到改错再到自主设计
学 SQL 没有捷径,但有高效的路径。我自己带过几个新人,总结下来最有效的三步法:先跟着教程把建表、插入、查询完整跑一遍,不求理解,先混个眼熟;然后故意改错,比如把 WHERE 去掉跑一次,亲眼看看后果;最后试着自己设计一个小场景,比如设计一个简单的订单系统,包含用户表、商品表、订单表,自己写关联查询。
这个练手项目的价值,在于迫使你从“照着写”变成“想清楚再写”。设计表结构本身就是一个独立思考的过程,遇到字段类型选择、外键关联、查询需求不明确等问题时,你对 SQL 的理解会快速加深。
6.3 我的一些实际体会
最后聊一点个人感受。SQL 是一门很容易“假懂”的技能,看视频觉得全懂,上手一动笔就卡壳。真正的突破点,往往是你为了查清楚一个需求,来回写了几十遍关联查询之后。我最早学 SQL 的时候,也觉得自己已经学明白了,直到某天要统计“每周各品类的销售排名”,才意识到窗口函数、子查询这些高级用法我压根没掌握。但如果没有先把基础打牢,那些高级内容根本没有讨论的土壤。
如果你正处在“SQL基础0”的阶段,不用着急,也不用焦虑。SQL 的每一个概念都是建立在前面概念之上的,把建表、增删改查、过滤、排序、聚合、关联这七件事练到不用想就能写出来,你已经超过绝大多数只停留在“看过教程”的人。下一步再碰子查询、窗口函数、索引优化,那都是水到渠成的事。