简介:数据仓库实践系列课程首部分课件,面向数据库初学者和数据仓库入门者,以数据库基础与SQL语言为主线,系统讲解数据、数据库、数据库系统的基本概念,并借助学生选课等实例梳理概念模型、关系模型、主键与关系代数运算,为后续数据仓库实践打下扎实基础。资源包仅含1个PPTX文档,约3.06MB,内容紧凑、结构清晰,适合自学或作为培训讲义。课件主要覆盖数据库基本概念、关系代数、SQL基础练习三大模块,包含选择、投影、连接、除等关系运算示例,以及SELECT、聚合函数、GROUP BY等SQL常用操作,并结合具体业务场景演示如何查询、更新和删除数据。已有127人学习下载,对于希望从零入门数据管理、提升SQL实操能力的读者而言,是一份便于快速上手的入门资料。
1. 一份数据库基础与SQL课件,适合正在补齐数仓地基的人
最近帮几位转岗做数据开发的同事补基础,发现大家手里攒了一堆课程资料,真正能从头啃到尾的没几份。数据仓库实践系列课程的第一讲这份PPT,主题落在“数据库基础与SQL”,恰好是多数人最薄弱也最绕不开的一环。它不是那种罗列概念的百科型课件,而是按照关系模型、事务、范式、四类SQL、查询优化这条主线,把做数仓之前需要的地基知识串了一遍。按行业分类找文档资料型资源的话,这份属于能直接拿来当内部培训教材的类型。适合刚接触数仓、SQL写过但说不清原理的新手,也适合准备带新人、需要一套讲解框架的熟手。我的建议很直接:别只翻PPT,每个知识点配一个能跑的练习再往下一章走。
2. 先把概念立住:关系模型、事务与范式,三层地基缺一不可
很多人学数据库直接跳到SQL语法,建表、查询都能跑,但一问“为什么这张表要拆成两张”“为什么数仓里的宽表反而不拆”,就答不上来。这份课件的价值在第一部分——它把数据库最基本的三块基石按顺序摆好:关系模型、事务、范式。这三块没立住,后面写SQL容易写出“看似对、实际错”的语句,排查起来特别费劲。
2.1 关系模型:为什么数据仓库偏爱二维表结构
关系模型的核心思想并不复杂:数据用二维表组织,一张表由行和列组成,行是记录,列是字段。表的集合构成数据库,表与表之间通过主键和外键建立关联。你日常操作MySQL、Oracle,用的都是关系模型;你在数仓里看到的维度表、事实表,本质上也是关系模型的延伸,只是换了一套建模语言。
课件里反复强调一个点:主键必须唯一且非空,外键用来引用另一张表的主键。这两条规则是关系模型的骨架。举个实际的例子,订单表里的customer_id,关联到客户表的id,这个关联字段就是外键。没有外键约束,也能通过JOIN完成查询,但那属于“逻辑上的关系”,不保证数据完整性。数仓场景里,宽表常常故意把外键冗余进来,牺牲范式换查询速度,这个后面会展开说。
从Excel思维转到关系模型思维,最需要适应的一点是“拆”。Excel可以把所有信息堆在一张表里,关系模型会劝你拆成多张互相关联的表,用JOIN把它们拼回来。课件里配的E-R图练习,就是在训练这种拆分直觉。我当时带新人做某电商项目的模拟练习,让他先画E-R图再建表,比直接写CREATE TABLE有效得多。
2.2 事务的ACID:用一笔转账把四个性质说透
事务这一节,课件用了银行转账的场景来解释ACID,这个例子虽然老,但确实最直观。假设要从A账户扣100元,往B账户加100元。如果扣款成功、入账失败,钱凭空消失,系统就出了问题。事务把这“一扣一加”绑成一个整体,要么全部成功,要么全部回滚。
四个性质拆开看:
- 原子性,转账操作不可分割,只执行一半等于没执行;
- 一致性,转账前后总额不变,数据始终符合业务规则;
- 隔离性,两个同时进行的转账互不干扰,A转B和C转D同时发生,结果和分开执行一致;
- 持久性,只要事务提交了,哪怕断电,数据也不能丢。
PPT里讲完概念后抛出一个问题:数仓里的批量写入要不要用事务?很多人的直觉是“数仓只读,不需要事务”,但实际做增量更新时,一个批次写入几十万行,中间失败怎么办?常见的做法是用事务包裹整个批次,失败整体回滚,避免出现“一半新数据、一半旧数据”的中间态。这一点在后续课程的ETL章节会反复出现,先记住结论:事务不是OLTP系统专属,数仓的批量写入同样需要边界。
2.3 三大范式:建表之前先把冗余问题想明白
范式这一节,课件讲了三层,层层递进。第一范式要求字段原子性,也就是每个字段不可再分,比如“地址”不能拆成“省市区”又当作一个字段,要存就分开存;第二范式在1NF基础上要求非主键字段必须完全依赖主键,不能只依赖主键的一部分;第三范式进一步要求非主键字段之间不能有传递依赖。
听着抽象,举个订单表案例就清楚了。一张订单表包含:订单号、商品名称、商品价格、客户姓名、客户电话。假设订单号是主键,商品名称和价格依赖订单号(这张单子买了什么),客户姓名和电话也依赖订单号(谁买的),看似没问题。但仔细看,商品名称和商品价格之间也存在依赖关系——价格本来就属于商品,和订单无关。如果把商品ID单独拆成一张商品表,订单表只留商品ID,就消掉了这个传递依赖,这就是第三范式的意义。
课件里特别补了一句:数仓建模经常主动反范式,把商品名称冗余回订单表,减少JOIN次数。这不是打脸,而是OLTP和OLAP的诉求不同:OLTP追求不冗余,避免更新异常;OLAP追求查询快,用空间换时间。新手最容易犯的错是把OLTP的范式标准硬套到数仓宽表设计上,造出几十个字段还要JOIN七八张表的“伪宽表”。记住三个范式是面试题的底线,知道什么时候该打破它,才是工作中的分水岭。
3. 把SQL拆成四类来练:DDL建表、DML改数、DQL查询、DCL控权
课件第二个大部分直接把SQL分成四类,每一类配示例和练习题。这个分类方式看似基础,但作用很大——我在面试数据开发候选人时,很多写着“熟练使用SQL”的,让他分别说出DDL、DML、DQL、DCL的典型命令,反而支支吾吾。分类不只是考试考点,它决定你拿到一个需求时,先动哪类语句、事务边界画在哪里。
3.1 DDL:建表语句里的约束、默认值与字符集
数据库操作的第一步是建表。DDL(Data Definition Language)负责定义结构,主流命令是CREATE、ALTER、DROP。课件在练习部分给了学生表、课程表、成绩表三张表的设计题,先自己写,再看参考写法。
CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID', student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT DEFAULT 0 COMMENT '性别:0未知,1男,2女', birthday DATE COMMENT '出生日期', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', KEY idx_student_no (student_no) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '学生表';先说逻辑:主键用自增整数,业务字段学号单独加唯一约束——这两者的区别很多人分不清。主键是物理层面的唯一标识,学号是业务层面的人类可读编号,两者不该混用。再看几个容易被忽略的参数:
ENGINE = InnoDB,事务支持靠它,MyISAM不支持事务和外键,数仓场景如果涉及批量写入回滚,选错了引擎就是灾难;DEFAULT CHARSET = utf8mb4,比utf8多覆盖了emoji字符,建表时不指定,继承库级别设置,不同库混用字符集,查询时踩坑概率极高;ON UPDATE CURRENT_TIMESTAMP,每次更新记录自动刷新时间字段,做数据审计时省很多事;- 最后一行
KEY idx_student_no是普通二级索引,查询按学号过滤时不用全表扫描,建索引的取舍后面单开一章。
ALTER TABLE是后续优化的常用手段,注意一点:大表上执行ALTER会锁表。业务高峰期给几百万行的表加字段,可能直接把线上查询拖垮。常见做法是避开流量高峰,或者用工具做在线DDL,依赖版本和数据库类型,需要结合自己公司的运维规范来定。
3.2 DML:增删改查的动作链,别把UPDATE写成没条件的“核弹”
DML(Data Manipulation Language)是对数据的操作,INSERT、UPDATE、DELETE属于这一类。代码写起来简单,难的是拿捏操作的边界。
-- 插入单条记录 INSERT INTO student (student_no, name, gender, birthday) VALUES ('20240001', 'A同学', 1, '2000-01-15'); -- 批量插入,注意和INSERT SELECT的区别 INSERT INTO student (student_no, name, gender, birthday) VALUES ('20240002', 'B同学', 2, '2000-03-22'), ('20240003', 'C同学', 1, '1999-11-08'); -- 安全更新:一定要带WHERE,先SELECT确认再UPDATE UPDATE student SET gender = 1 WHERE student_no = '20240003';批量插入时如果数据量达到十万到百万级别,逐行INSERT效率很低,常见的做法是分批次提交,每批几百到几千行,穿插COMMIT,避免单个大事务拖垮日志。UPDATE和DELETE的核心原则,用三个字概括——带条件。圈里有个词叫“裸UPDATE”,指的是写了UPDATE但漏了WHERE,执行完整个表的数据都被改掉。这是新手翻车最高频的SQL事故,没有之一。
课件里配了个练习:把某门课程不及格的学生成绩统一加5分。考的就是两条:UPDATE匹配条件怎么定,以及事务怎么包。常见做法是先把符合条件的数据SELECT出来,COUNT一下确认范围,再执行UPDATE,全程包在事务里,最后检查影响行数与预期一致再COMMIT。
3.3 DQL:SELECT的执行顺序决定你怎么写查询
DQL(Data Query Language)是SELECT的天下,也是工作里占用时间最多的部分。读过再多查询技巧,不如把执行顺序刻在脑子里。课件把SELECT的执行顺序画成了一张表,这条线的优先级从高到低是:FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT。
SELECT gender, COUNT(*) AS cnt FROM student WHERE birthday >= '1999-01-01' GROUP BY gender HAVING cnt >= 1 ORDER BY cnt DESC LIMIT 10;这段代码有几层顺序陷阱。先说WHERE和GROUP BY的顺序:WHERE在分组之前过滤原始行,换句话说,WHERE里不能写聚合函数。如果你手滑写了WHERE COUNT(*) > 1,数据库会直接报错或行为异常,正确的写法是把聚合结果过滤放到HAVING。再说SELECT和ORDER BY的顺序:ORDER BY排在最底层执行,意味着它可以引用SELECT里的别名cnt,而WHERE因为执行在前,看不到别名,这也是“WHERE不能使用别名”这条规则的由来。
DISTINCT去重要慎用:一张千万行的大表做DISTINCT,内存开销很高。课件里给出的替代思路是先确认“去重”是不是业务真需求——多数时候是GROUP BY + 聚合函数能解决的问题,DISTINCT是偷懒写法。
3.4 DCL:权限控制是数仓安全的第一道门
DCL(Data Control Language)负责权限管理,核心命令是GRANT和REVOKE。这一节在课件里篇幅不大,但在数仓团队里是红线级的话题。数据权限管不好,轻则报表口径混乱,重则数据泄露。
-- 给数据开发账号授予查询权限,只开放需要的库表 GRANT SELECT ON dwd.* TO 'dev_user'@'%'; -- 给ETL账号授予增删改查,但仍然限制在指定库 GRANT SELECT, INSERT, UPDATE, DELETE ON dws.* TO 'etl_user'@'%'; -- 回收某张表的权限 REVOKE SELECT ON dwd.dwd_order_detail FROM 'dev_user'@'%';这里有个参数值得说明:'账号'@'%'里的%代表任意主机,线上环境不建议这样开,应该限制到公司内网IP段,比如192.168.1.%,把暴露面收小。最小权限原则是通用的——每个人只拿完成自己任务所需的权限。数仓项目里经常出现“账号共用”的情况,几个人都知道同一个密码,权限收不回来,到时候出了数据问题连审计都没法做。我的习惯是:至少做到“一个应用一个账号”,读库账号和写库账号分离,绝不混用。
4. 查询性能的关键一步:执行计划、索引取舍与窗口函数的边界
课件后半部分开始讲查询优化。“SQL能跑”和“SQL跑得快”之间隔着一条鸿沟,这条鸿沟的通行证就是执行计划。很多人在业务库里写了两年SQL,遇到慢查询还是靠猜,或者干脆甩给DBA。其实看懂执行计划并没有那么玄学,抓住几个关键字段就能定位绝大多数性能问题。
4.1 执行计划:让数据库告诉你慢在哪,不用靠猜
MySQL里用EXPLAIN关键字,SQL Server里用SET STATISTICS PROFILE ON,思路一致。课件用MySQL的EXPLAIN逐列拆解,我最看重的字段有三个:type、key、rows。
EXPLAIN SELECT s.student_no, s.name, sc.score FROM student s LEFT JOIN score sc ON s.id = sc.student_id WHERE sc.course_id = 1 ORDER BY sc.score DESC LIMIT 100;执行计划里,type字段从好到差大致是:system > const > eq_ref > ref > range > index > ALL。ALL代表全表扫描,通常意味着这条查询没走索引,数据量一大必然慢。key字段显示实际用到的索引名,如果为NULL,说明没命中索引。rows是预估扫描行数,数值越大越危险。
只看理论字段还不够,我把课件里的慢查询案例改造了一下:一张成绩表500万行,查询某个课程的前100名,原始SQL跑了3秒。EXPLAIN一看,type = ALL,rows = 500万。我们在(course_id, score)上建了联合索引,再把ORDER BY改成走索引排序,同一个查询降到50毫秒以内。这个案例的核心收获是:(course_id, score)联合索引让WHERE过滤和ORDER BY排序都命中了索引,避免了文件排序。优化慢SQL不看执行计划,等于蒙着眼睛修车。
4.2 索引不是越多越好:七个失效场景要记牢
索引能提速,也能拖垮写入。每个表的索引不是白送的,INSERT和UPDATE都要同步维护索引结构,所以“能少建就少建,建了就建对”。课件里列了索引失效的几个典型场景,我在实际项目中几乎每一条都撞过。
| 失效场景 | 典型写法 | 结果 |
|---|---|---|
| 最左前缀被破坏 | 联合索引(col_a, col_b),WHERE只查col_b | 索引未命中 |
| 隐式类型转换 | WHERE phone = 12345678901,字段是varchar | 索引失效 |
| LIKE以通配符开头 | WHERE name LIKE '%张' | 索引失效 |
| 函数包裹字段 | WHERE DATE(created_at) = '2024-01-01' | 索引失效 |
| OR连接非索引列 | WHERE id = 1 OR phone = 123 | 部分索引失效 |
| 索引列参与运算 | WHERE salary * 2 > 10000 | 索引失效 |
| NULL值判定 | WHERE name IS NULL | 可能全表扫描 |
以函数包裹字段那条为例,很多人写日期过滤时习惯用DATE(created_at),这样写优雅,但索引列被函数处理后就失去了有序性。常见做法是改成范围查询:WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'。执行计划从ALL变range,扫描行数降了几个量级。记住一个原则:索引列独立在比较符左侧,不套函数、不参与运算、不做隐式类型转换。
4.3 窗口函数:从GROUP BY到排名分析的进阶写法
窗口函数是SQL进阶的分水岭,也是数据开发和数据分析面试的高频考点。GROUP BY会把多行压成一行,窗口函数则保留每一行,同时额外计算聚合结果。
SELECT student_id, course_id, score, ROW_NUMBER() OVER (PARTITION BY course_id ORDER BY score DESC) AS rank_in_course FROM score ORDER BY course_id, rank_in_course;ROW_NUMBER()给每个分组内的记录按成绩降序编号,PARTITION BY course_id指定分组维度,ORDER BY score DESC决定组内排序。一个非常经典的用法是取每组前N名:在外面套一层SELECT,WHERE rank_in_course <= 3,就能拿到每门课的前三名。这和GROUP BY的区别很微妙,GROUP BY之后你只能看到每门课的最高分、平均分这种聚合值,但你看不到“考了最高分的是谁”。
ROW_NUMBER、RANK、DENSE_RANK三个函数长得像,细节不同:RANK并列时会跳号,比如两个并列第一,下一个就是第三名;DENSE_RANK不跳号,并列第一之后直接是第二名。做报表口径时选哪个,取决于业务怎么定义“排名”。窗口函数性能比自连接快很多,但要注意:窗口函数在数据量极大时对内存有压力,配合分区和索引使用,否则可能把一个查询拖成慢SQL典型样本。
5. 避坑:六个课堂上不常见、工作中天天撞的SQL坑
这份课件里没有单独设“踩坑”章节,但我在拿它带新人实操时,几乎每个项目节点都会撞出几条血泪经验。整理六个高频问题,每条按现象、原因、解决三步写,给正在照着课件练习的同学省点时间。
5.1 分页查询越翻越乱,数据重复或丢失
现象:ORDER BY score LIMIT 10, 10翻到第二页,发现第一页看过的记录又出现了,或者有些记录永远翻不到。
原因:ORDER BY的字段不唯一。假设按score排序,大量学生同分,数据库不保证同分记录之间的顺序稳定,每次查询返回顺序可能不同,分页就错位了。
解决:ORDER BY后面追加一个唯一字段做第二排序键,比如ORDER BY score DESC, id ASC。id天然唯一,排序结果就稳定了。如果表很大且分页很深,LIMIT 100000, 20会扫描前面十万行,更高效的做法是用ID游标:WHERE id > 上次最大id ORDER BY id LIMIT 20。
5.2 WHERE条件里NULL参与比较,结果悄悄消失
现象:WHERE score < 60查不及格学生名单,结果里缺了好几条记录。看数据,那几个学生的score字段是NULL,明明没及格,却查不出来。
原因:SQL里NULL和任何值做比较结果都是未知(UNKNOWN),NULL < 60不等于真,也不等于假,它被过滤掉了。这是SQL三值逻辑的基本特性。
解决:显式处理NULL,WHERE score < 60 OR score IS NULL。如果要排除NULL,就写WHERE score IS NOT NULL AND score < 60。写查询前先考虑字段是否可空,空值处理比大多数人想象中更影响结果集。
5.3 字符串与数值隐式转换,索引白白失效
现象:某条WHERE条件明明匹配了索引列,查询却走了全表扫描,数据量一大就慢成“蜗牛”。
原因:字段类型是VARCHAR,但查询条件写成了数值,比如WHERE student_no = 20240001,数据库会先把字段隐式转换成数值再比较,导致索引失效。之前的索引失效场景表里已经列了这条,这里强调一下:这是工作中出现频率最高的索引失效原因。
解决:把查询条件写成字符串,WHERE student_no = '20240001',类型匹配,索引回归。写SQL时看到字段带VARCHAR属性,参数一律用引号包裹,不仅避免隐式转换,也避免误写导致的大范围更新事故。
5.4 SELECT * 把内存打爆,ETL任务反复超时
现象:一个离线ETL任务读一张2000万行的宽表,SELECT *取全部字段,任务每天凌晨3点准时超时报警。
原因:宽表本身有80多个字段,实际下游只需要其中10个。跑一次任务重复读了几十GB无用数据,I/O和网络全被拖累,内存更是吃紧。
解决:把SELECT *改成显式列出需要的字段,并把过滤条件下推,尽量减少扫描行数。这个优化不需要动任何索引,单靠收窄字段就能砍掉一半耗时。养成习惯:写SELECT时点名需要的列,除非是快速探查表结构,否则永远不用星号。
5.5 删除数据没有留后悔药,DROP和TRUNCATE分不清
现象:想清空一张临时表,手一抖执行了DROP TABLE temp_table,整张表连带结构一起消失,但第二天发现下游脚本依赖这张表的结构,不得不花半天重建。
原因:DELETE是DML,删数据不删表结构,可以按条件删;TRUNCATE是DDL,清空所有数据但保留表结构;DROP直接连表带结构一起删。三者破坏能力递增,没有区分就直接上手,是事故源头。
解决:养成一个习惯——删数据之前先备份。常见做法是CREATE TABLE temp_table_bak AS SELECT * FROM temp_table,或者直接用数据库的备份恢复功能。严格区分DELETE和TRUNCATE,如果只要清数据保留结构,用TRUNCATE;如果只清部分数据,用DELETE带WHERE。任何时候不确定,先备份再动手。
5.6 字符集不一致导致中文乱码,查出来的数据看着像天书
现象:从一个库查数据插入另一个库,中文全变成????????,或者查询WHERE条件带了中文,匹配不到任何结果。
原因:源头表的字符集是utf8mb4,目标表是latin1,插入时字符编码转换失败。字符集不一致不只影响显示,还影响WHERE条件的匹配——两个字段字符集不同,排序和比较都可能出问题。
解决:建表统一用utf8mb4,连接字符串里显式指定字符集。跨库同步数据时,先确认两边的字符集一致,再跑工具或脚本。这事听着简单,但业务库老旧混杂时,经常是“每个库都有自己的脾气”,遇到乱码先查两边字符集,基本能定位八成的编码问题。
6. 把SQL练成肌肉记忆:用这份课件做一次能力体检
课件全部过完一遍之后,我建议你把它当成体检表,而不是教材。这里给一份自查清单,每项都能对照课件里的练习章节来检验自己。
| 检查项 | 通过标准 | 对应课件章节 |
|---|---|---|
| 关系模型 | 能画出5张表的E-R图并解释外键关系 | 第1部分 |
| 事务理解 | 能解释ACID四个性质并举出业务反例 | 第1部分 |
| 范式应用 | 能指出设计表中的传递依赖 | 第1部分 |
| DDL建表 | 能独立写好约束、字符集、索引 | 第2部分 |
| DML操作 | 能在事务内安全执行UPDATE/DELETE | 第2部分 |
| DQL查询 | 能写出含聚合、分组、排名的查询 | 第2部分 |
| 执行计划 | 能定位一条慢SQL的瓶颈字段 | 第3部分 |
| 窗口函数 | 能写出分组TopN查询 | 第3部分 |
| 权限管理 | 能按最小权限给账号授权 | 第2部分 |
一个具体技巧,是我带新人时必用的“三遍练习法”。第一遍,只看PPT,把知识点过一遍,看懂不算会;第二遍,合上PPT,在一张干净的数据库实例里完成所有练习,卡在哪道题就回看对应小节;第三遍,把每个练习改成不同的业务场景,比如学员管理改成订单管理,重新写一遍,确保不是背答案而是理解了逻辑。三遍下来,基本功会比直接背十条常用命令扎实得多。
说实话,我这几年见过不少人简历上写着“熟练使用SQL”,一上手写个分组TopN都要憋半天。数据库基础这块没有捷径,认真过一遍课件配一次实操练习,胜过收藏夹里存一百篇教程。课件里每个章节设置的练习题,我建议不要跳过——我第一次带那个数仓模拟项目X时,就是因为跳过了范式练习,后面设计维度表绕了一大圈弯路。从那以后,每次带新人或者自己学新工具,我都强制走一遍“先概念、再练习、后变体”的流程,确实省掉了后续很多返工的麻烦。希望这份数据库基础与SQL的课件也能帮你把地基打牢,后面进数仓实战会轻松不少。
本文还有配套的精品资源,点击获取