资讯详情

SQL Server学习笔记:概念分水岭与建表范式实战

📅 2026/10/12 0:29:58 | 华诺云谱 👁 阅读
SQL Server学习笔记:概念分水岭与建表范式实战
简介这份SQL Server学习笔记专为数据库初学者和管理人员整理系统梳理了SQL Server的关系数据库体系、核心概念与常用操作可作为课前预习、课后复习或面试速查参考。资源包内共1个doc文件约499KB内容集中、便于离线查看。笔记覆盖Create、Drop、Alter等数据库对象操作C/S架构与ODBC、VB、VC、Access等编程接口以及表、索引、触发器、存储过程等对象要点还说明Master、Model、Tempdb、Msdb等系统数据库作用数据文件.mdf与日志文件.ldf存储结构。同时展开关系模型、候选码/主码/外部码等概念整理主键、外键、默认值、Check、Unique约束用法并附创建数据库、插入数据及视图的SQL语句示例方便对照练习。已有440人学习下载适合需要快速搭建SQL Server知识框架、复习数据库对象与SQL编写要点的学习者。1. 这份 SQL Server 学习笔记最反直觉的地方语法好背概念才是分水岭这份 SQL Server 学习笔记最值得抄的不是某一条 SQL 语法而是它把数据库入门里最容易混的那组概念——语句分类、对象层次、范式边界——串成了同一张网。你不需要逐条去背CREATE、INSERT、UPDATE的拼写顺序而是先搞清楚哪些动作属于DDL、DML、DCL哪些对象挂在数据库下面哪些约束管的是列、哪些管的是行。笔记适合两类人刚接触SQL Server的新手拿它当速查手册对着敲语法、对着建表已经写过一阵子SQL但总是被范式、候选码、簇索引这类概念卡住的从业者用它查漏补缺。里面有几个结论挺反直觉的比如主键值不能为空但候选码可以比如存储过程最多能带1024个参数却只有32级嵌套比如一个表只能有一个簇索引但可以有249个非簇索引——这些数字不是考点是设计系统时真正的边界。把边界摸清了写出来的表结构才不容易翻车。2. 先搭骨架文件体系、系统库与建表语句2.1 从文件到库先分清 mdf、ndf、ldf 和六大系统库很多人第一次接触SQL Server时习惯直接在SSMS里右键“新建数据库”点两下就完事了。但真到了要手动部署、迁移或者排查磁盘空间问题时数据库在磁盘上到底长什么样就成了绕不开的问题。笔记里有一段讲得清楚数据库的存储会被映射成若干个操作系统文件。主数据文件是.mdf后缀它包含数据库的启动信息也存储实际数据。如果数据量大到需要跨多个磁盘可以加次要数据文件后缀是.ndf用来存放主数据文件放不下的那部分数据。日志文件是.ldf后缀里面记录的是事务日志负责保证数据库的可恢复性。这三个后缀必须记牢迁移数据库或者做备份恢复时找错文件类型是常见的低级错误。文件组则是为了方便管理这些文件而引入的逻辑单元。简单理解文件组是容器mdf、ndf文件可以被分配进不同的文件组。生产环境里常见做法是把数据文件放到不同的物理磁盘上再把它们归到同一个文件组里让SQL Server可以并行读写以此摊薄I/O压力。系统数据库的职责也要分清它们各管一摊系统库作用Master保存登录账号、配置信息是所有数据库的总入口Model模板库新建数据库时以它为蓝本复制结构Tempdb存放临时表和临时对象重启后清空Msdb调度作业、警报、操作员信息默认约12MBPubs / NorthwindSQL Server自带的示例库适合练手提示Tempdb 是排查性能问题时最先要看的地方。大量使用临时表或表变量时Tempdb 会迅速膨胀导致整个实例变慢。2.2 建表的正确姿势从 CREATE DATABASE 到临时表笔记里给出了创建数据库的完整语句结构核心骨架是CREATE DATABASE 数据库名 ON ( NAME 逻辑文件名, FILENAME 物理文件路径, SIZE 初始大小, MAXSIZE 最大大小, FILEGROWTH 增长增量 ) LOG ON ( NAME 日志逻辑文件名, FILENAME 日志物理文件路径, SIZE 初始大小, MAXSIZE 最大大小, FILEGROWTH 增长增量 )这段语句里的参数其实都是在回答“文件放哪、长多大、长多快”三个问题。SIZE 是初始大小MAXSIZE 是上限FILEGROWTH 是每次自动增长的步长。生产环境里我一般会把 FILEGROWTH 设成固定值比如 64MB、128MB而不是设成百分比——百分比增长在文件已经很大时一次增长可能分配过多空间磁盘一下子被吃掉反而不好控制。建表的时候逻辑名可以用中文关键字大小写不敏感这是笔记里明确提到的。真正需要注意的是 INSERT 语句的匹配规则。看这个例子CREATE TABLE goods_1 ( goods_id INT PRIMARY KEY, goods_name VARCHAR(50), goods_type_name VARCHAR(30), goods_number INT ); INSERT INTO goods_1 (goods_id, goods_name, goods_type_name, goods_number) VALUES (1, 机械键盘, 电子产品, 50);INSERT 的列值和表结构必须严格匹配列数要一致顺序要一致。如果创建表时某列允许为空插入时这一项可以写 NULL 占位。这是新手最容易翻车的地方——少写一列或者把 VARCHAR 字段直接写成数字SQL Server 立刻报错而且报错信息有时候不够直白得自己数括号里的值。建表时约束才是重头戏。笔记里归纳了五种约束每一种都对应一个完整性维度主键约束PRIMARY KEY唯一标识每一行不能为空。外键约束FOREIGN KEY ... REFERENCES在两表之间建立连接关系。默认值约束DEFAULT用户没输入时系统自动填值。检查约束CHECK限制输入值必须满足某个范围。唯一性约束UNIQUE列值不可重复。它们的应用位置要分清主键和唯一性约束管的是行的唯一标识属于实体完整性默认值和 CHECK 管的是某一列的输入有效性属于域完整性外键管的是表与表之间的对应关系属于参照完整性。设计表的时候先把这层对应关系想清楚后面维护数据才不会乱套。3. 关系模型三件套码、依赖与范式判断3.1 候选码、主码、外码三个码别混笔记里有一组概念特别容易混候选码、主码、外码。我见过不少写了两三年SQL的人主键外键用得溜但被问到“候选码”时还是会愣一下。候选码指的是表中能唯一标识一行数据的属性组合可以是一列也可以是多列组合。比如学生表里学号本身就能唯一标识一个人学号就是候选码。如果表里既有学号又有身份证号那它俩各自都是候选码——都能完成同一件事只是身份不同。主码是从候选码里挑出来的一个“代表”用来作为主键。余下没有被选中的候选码就叫备用码。主码的属性叫主属性不包含在任何一个候选码里的属性叫非主属性。这个划分是下面范式的判断基础绕不过去。外码更直白它是联系两个表的那一列。在订单表里放一个 customer_id指向客户表的 id这个 customer_id 就是外码。外码存在的意义不是“多一张表可以引用”而是保证参照完整性——你插入订单时填的客户编号必须在客户表里真实存在否则操作被拒绝。三者的关系用一句话收束候选码是“所有能当身份证的”主码是“我选它当身份证的那一个”外码是“我拿别人的身份证号来关联对方”。这个逻辑理清了后面读范式定义就不会卡壳。3.2 第一范式到 BCNF用一张商品表讲透范式的概念如果不落到具体表上很容易变成背定义。笔记里给了一个很典型的反例一张商品表里商品编号是主键但某一列存了多个商品类别。比如 goods_id 是 101 的行goods_type_name 这一格里塞了“生活用品, 电子产品, 办公用品”三个值。想按“生活用品”去查商品编号结果查不到或者查出来是错的——因为数据存成了一整个字符串数据库根本没法拆分匹配。这就是违反第一范式表的每一列必须是不可再分的数据项。修正方式有两种把组合列拆成多列或者把一行拆成多行。更彻底的方案是拆表把商品和类别变成两张表用外键关联。第一范式解决的是“列还是行”的问题。第二范式建立在第一范式之上要求每个非主属性完全依赖于码不能存在部分依赖。笔记里的例子是购买信息表CREATE TABLE buy_info ( 购买了商品的用户编号 VARCHAR(10), 用户购买商品编号 VARCHAR(10), 用户购买商品名称 VARCHAR(10), PRIMARY KEY (购买了商品的用户编号, 用户购买商品编号) );这张表的码是用户编号和商品编号的组合。问题出在“用户购买商品名称”这一列——商品名称只由“商品编号”决定和“用户编号”没关系。也就是说它只依赖联合主键的一部分这就是部分依赖。后果很直接同一个商品被多个用户购买时商品名称在表里存了多份改一次得改N处稍不注意就改漏数据就乱了。修正的方案是拆表购买记录表放用户编号和商品编号商品信息单独建表商品编号做主键商品名称放那边。第三范式要求消除传递依赖如果 A 决定 BB 决定 C而 B 不能反推 A那 C 就是通过 B 间接依赖 A 的这时候要把 B、C 拆出去单独建表。比如订单表里有“客户编号”和“客户姓名”两列客户姓名实际上只依赖客户编号而这个依赖是通过订单行间接带进来的——所以客户姓名应该待在客户表里。笔记里还提到了BCNF也就是修正的第三范式。它的含义更严格任何属性都不能再依赖于非主属性的属性组。理解到“消除部分依赖消除传递依赖”这两层日常工作基本够用。范式不是越高级越好实践中很多报表库为了查询效率会主动反范式化这个后面结合业务权衡。3.3 约束落地一个完整的建表案例概念说完了关键是落到真实 SQL 里。下面这个例子综合了五种约束可以直接抄来改CREATE TABLE goods_1 ( goods_id INT IDENTITY(1,1) PRIMARY KEY, goods_name VARCHAR(50) NOT NULL, goods_type_name VARCHAR(30) DEFAULT 生活用品, goods_price MONEY CHECK (goods_price 0), goods_number INT UNIQUE ); CREATE TABLE bid_record ( bid_id INT IDENTITY(1,1) PRIMARY KEY, goods_id INT NOT NULL, reg_name VARCHAR(20) NOT NULL, bid_date DATETIME DEFAULT GETDATE(), FOREIGN KEY (goods_id) REFERENCES goods_1(goods_id) );这段代码里有几个细节值得展开。IDENTITY(1,1)是自增列从 1 开始每次加 1省去手动维护编号的麻烦。DEFAULT约束和CHECK约束直接写在列类型后面是列级约束。外键约束写在表最后是表级约束——两张表的关联关系由这行定义管着插入 bid_record 时如果 goods_id 在 goods_1 里不存在SQL Server 会直接拒绝。注意MONEY 类型在 SQL Server 里算内置货币类型不需要加单引号但值必须合法。自定义 CHECK 条件时范围写太小容易误伤业务数据建议先确认边界条件再发布。4. 视图、索引、存储过程与游标四个必会对象4.1 视图创建、加密与检查选项视图本质上是一张虚拟表它不存储数据而是保存了一条 SELECT 语句。访问视图等于执行这条查询语句拿到的结果像表一样可以被 SELECT、UPDATE、DELETE。笔记里给的创建语法是CREATE VIEW goods_1_view_1 AS SELECT goods_name, goods_type_name FROM goods_1;视图最有用的场景是“把复杂查询包装成简单接口”。比如经常要查“电子产品类别下的商品名称和竞拍日期”每次都写一遍三表 JOIN 太啰嗦建一个视图把逻辑固定住业务方只需要SELECT * FROM goods_1_view_3就能拿到结果CREATE VIEW goods_1_view_3 AS SELECT g.goods_name, g.goods_type_name, b.bid_date FROM goods_1 g, bid_record b WHERE g.goods_id b.goods_id;视图还可以加选项笔记列了四个WITH CHECK OPTION、WITH ENCRYPTION、WITH SCHEMABINDING、WITH VIEW_METADATA。其中WITH ENCRYPTION是把视图定义文本加密存储别人想查看这个视图的创建语句时会看到一堆乱码适合保护核心业务逻辑。WITH CHECK OPTION则是强制约束所有通过视图做的数据修改都必须满足视图定义里 WHERE 条件的限制。视图的查询限制才是真正的坑。笔记明确指出定义视图的 SELECT 语句里不能有 INTO、ORDER BY、COMPUTE、COMPUTE BY也不能引用临时表。这个限制的根源在于视图的结果集要能稳定映射到基表行临时表会消失ORDER BY 的结果不能保证顺序所以干脆禁止。新手在这块翻车率极高老是试图在视图里排个序结果一执行就报错。4.2 索引参数怎么设unique、clustered 与复合索引笔记里用了“树状结构”来描述索引这个概括很准。索引存在的目的就是加速检索原理是提前为某列建一棵查找树查询时不用全表扫描走树结构直接定位到目标行。索引分两类。簇索引clustered会改变表数据本身的物理顺序一个表只能有一个簇索引。非簇索引nonclustered保存的是索引键值和行位置的对照关系不改变数据实际上存储在磁盘上的顺序所以执行时先通过索引找到位置再去取数据。两者最大的差异在于簇索引自带排序效果适合范围查询非簇索引灵活但多一次回表。创建索引的规范语句长这样CREATE UNIQUE CLUSTERED INDEX goods_index ON goods_1 (goods_id);参数逐个说。UNIQUE表示唯一性索引不允许两行索引值相同。CLUSTERED表示建的是簇索引如果省略它建的是非簇索引。ASC | DESC控制排序方向默认升序。复合索引就是多列组合比如CREATE INDEX goods_index2 ON goods_1 (goods_id, goods_type_id, goods_name);复合索引的计算顺序是从左往右的最左列必须出现在查询条件里才能命中索引。这个特性决定了一个实用原则经常一起查询的列放到同一个复合索引里顺序上把区分度最高的列放最左边。笔记还给了一份“适合建索引的列”清单经常被搜索的列、需要排序的列、主键或外键列、经常出现在 WHERE 子句的列。反过来更新频繁的列不适合建太多索引因为每次 UPDATE/DELETE 都要同步维护索引树写放大严重。索引是典型的用写入成本换查询速度取舍全看业务。4.3 存储过程参数上限与嵌套深度背后的设计思路存储过程就是把一段 SQL 逻辑封装起来起个名字调用时一次性执行内部所有语句。笔记里给了一个非常典型的例子CREATE PROCEDURE modify_process bid_user VARCHAR(10), bid_date DATETIME, bid_number INT, bid_goods_id VARCHAR(10), goods_price MONEY AS UPDATE goods SET goods_number goods_number - bid_number WHERE goods_id bid_goods_id; INSERT INTO user_history (用户名, 成交金额, 成交日期) VALUES (bid_user, goods_price * bid_number, bid_date); GO EXECUTE modify_process 王菲, 2025-01-15, 2, G001, 500;这个存储过程干了三件事接收五个参数、扣减商品库存、写入成交记录。外面调用时只需要传一次参数整个过程在数据库服务端完成不用在应用层拼多条 SQL减少了网络往返也保证了两个操作要么一起成功、要么一起回滚。笔记里提到存储过程的两个上限最多 1024 个参数、32 级嵌套。参数多了说明设计有问题大概率是把本来应该拆分的过程硬揉成了一个嵌套超过 32 层则是递归调用过深比如存储过程A调用B、B再调用C链条太长时执行性能会急剧下降排查起来也痛苦。日常设计里我一般控制在一层调用最多两层超过就要停下来想想是不是该拆模块了。4.4 游标什么时候值得用怎么安全释放游标是被很多人嗤之以鼻却绕不开的对象。它的存在是为了逐行处理查询结果集适合少量数据的复杂逻辑不适合大批量数据操作。笔记里给出了标准八连句式DECLARE goods CURSOR FOR SELECT goods_name, goods_number FROM goods; OPEN goods; FETCH NEXT FROM goods; WHILE FETCH_STATUS 0 BEGIN -- 这里写逐行处理逻辑 FETCH NEXT FROM goods; END; CLOSE goods; DEALLOCATE goods;关键字顺序是固定的DECLARE 声明游标OPEN 打开游标FETCH 取行WHILE 循环处理CLOSE 关闭游标DEALLOCATE 释放游标。最容易漏掉的是最后一步DEALLOCATE不释放的话会占着连接资源长此以往把连接池拖垮。我在生产环境里写游标时会先把 CLOSE 和 DEALLOCATE 放在最前面写好再去补中间的循环逻辑这样思路跑偏时资源也能保证被回收。提示能用集合操作解决的问题不要用游标。SQL Server 是集合式处理引擎逐行处理天然慢。如果数据量过万尽量把逻辑改成 UPDATE ... FROM 或临时表关联性能差距能到几十倍。5. 常见问题排查五个我踩过的 SQL Server 坑这份笔记本身记录了学习者踩过的真实问题我从里面挑出五个高频翻车点按“现象→原因→解决”拆开讲。坑一INSERT 提示列数不匹配现象插入数据时报错信息类似“列名或所提供值的数目与表定义不匹配”。原因VALUES 里的值数量或顺序与 INSERT 指定的列不一致。少一列、多一列或者日期没有加引号写成裸数字都会触发。解决写 INSERT 时始终显式列出列名不要用省略列名的写法。常规做法是先把列名列一遍再对着填值。如果某列允许 NULL用 NULL 显式占位避免踩空。坑二删除两张表的关联数据时失败现象DELETE FROM 表A单独执行没问题但只要关联了表B就删不掉报外键约束错误。原因表间有外键约束子表还有引用父表的行直接删父表会被参照完整性挡住。解决先删子表引用方再删父表被引用方。笔记里的例子是DELETE FROM bid_record WHERE reg_name王飞然后再删 goods 里对应的行。如果业务允许也可以先把外键约束暂时禁用但生产环境不到万不得已不要这么干容易造成孤儿数据。坑三建视图报错SELECT 语句总写 order by现象视图创建失败提示 ORDER BY 子句在视图定义中无效。原因视图的结果集默认是集合SQL Server 不允许在定义视图的查询里排序。笔记里明确列了 INTO、ORDER BY、COMPUTE 这些关键字在视图里是禁用的。解决视图里把 ORDER BY 去掉排序放到查询视图时再外部处理。如果业务上确实需要视图本身有序可以在视图里加TOP 100 PERCENT再配 ORDER BY这只是绕法本质上不值得推荐。坑四改表删列时被约束挡住现象ALTER TABLE goods DROP COLUMN 商品数量执行报错提示有对象依赖于该列。原因该列上存在约束或者被索引、默认值对象绑定SQL Server 不允许直接删有依赖的列。解决先删除依赖对象再删列。笔记里的操作顺序是ALTER TABLE goods DROP CONSTRAINT 商品数量约束名然后再执行 DROP COLUMN。删之前用sp_help 表名查看列上挂的索引和约束列得清清楚楚再动手属于典型的手快毁库操作。坑五存储过程内部改了表却不报错现象修改存储过程引用的表结构后存储过程还能执行但结果不对。原因存储过程的 SQL 在创建时不会验证被引用表的存在性这是笔记里提到的特性。一旦表结构变了比如删了某个列存储过程里那一行还在运行时才报错。解决改完表结构后至少执行一遍所有依赖它的存储过程做回归验证。养成习惯凡是动过生产表结构立刻跑一遍相关过程清单。依赖关系可以用系统视图查出来别靠记忆。6. 再进一步模糊查询与拼音首字母搜索的工程化思路学习笔记的最后部分留了几个问答场景其中“客户提问怎么查”“怎么实现模糊查询”“怎么支持拼音首拼”非常贴近真实业务。我直接说结论SQL Server 里的模糊查询基础就是LIKE配合%通配符但工程上要把搜索做到“能用”得考虑用户到底会怎么输。最简单的写法是SELECT * FROM wenti WHERE title LIKE %关键字%。这个写法能命中标题任意位置的关键字搭配%在两边时走的是全表扫描数据量大了性能会明显下降。如果业务只要求前缀匹配比如用户习惯输入“SQL”“SQL Server”这种开头词写LIKE 关键字%后配合该列上的索引查询是可以走索引定位的。更实用的一招是拼音首字母搜索。用户在搜索框里输入首字母比如“sqlfw”期望系统能查出“SQL Server 服务”相关的常见问题。做法是在表里增加一个拼音字段比如叫py用程序自动把每条标题转换成拼音首字存储。查询时把用户输入的首字母拼起来去和py字段做匹配SELECT * FROM wenti WHERE py LIKE %sqlfw% ORDER BY title;同时保留汉字关键词的模糊匹配两条路径并行。这个方案的坑在于分词边界和同音字。用户名或标题里有生僻字、多音字时自动生成拼音很容易出错所以我的处理方式是拼音字段由程序根据标准词库生成生成后人工抽查一遍而不是完全甩手交给函数。输入端的拼音不区分大小写查询时做统一 lower 处理。如果是中英混合内容还可以用CHARINDEX替代LIKE做更细粒度的位置判断SELECT title FROM mydb WHERE title LIKE 关键字% OR (title NOT LIKE 关键字% AND CHARINDEX(关键字, title) 1);CHARINDEX(关键字, title)返回的是关键字在标题中的起始位置大于等于 1 就说明出现过。用区分的是出现在开头、结尾还是中间——开头用 LIKE 前缀中间用 CHARINDEX合在一起覆盖了所有位置。这两种写法在结果上等价但 CHARINDEX 表达语义更清晰尤其在排查“为什么这个标题查不到”时一眼就看出问题出在匹配位置还是匹配字符。从那以后我每次设计搜索功能时都会强制自己先回答三个问题用户输入的是完整词还是片段是否需要前缀命中是否需要拼音支持答案不同SQL 写法完全不同。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑