读《编写高质量SQL语句的61个有效方法》有感

本文探讨了数据库设计、可编程性与索引设计、过滤与查找数据等关键SQL优化策略,涵盖数据库规范化、索引创建、视图使用、聚合操作、子查询优化及执行计划分析等内容,旨在提升SQL查询性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、数据库设计

确保所有表都有主键
避免存储冗余数据
  • 数据库规范化的目标是消除冗余数据,并在处理数据时最小化资源消耗
  • 通过消除冗余数据,避免插入、更新和删除时出现异常
  • 通过消除冗余数据,尽量减少数据的不一致性
消除重复数据组
每列只存储一个属性

当表包含多列时,搜索和分组即使可能做到,也会极其困难

理解为什么存储计算列通常有害无益
  • 许多数据库系统允许在创建表时定义计算列,但应该注意性能影响,特别是在使用非确定性表达式和函数的时候
  • 可以像定义普通列一样定义计算列,然后使用触发器来维护,但编写出触发器代码可能会很复杂,且影响性能
  • 计算列会对数据库系统产生额外的开销,只有利大于弊的时候才考虑使用它
  • 希望在计算列上创建一个索引,以小号更多的存储空间和较慢的更新作为交换,获得一些便利性
  • 当不能使用索引时,使用视图来做计算通常可以替代表里创建计算列
定义外键以确保引用完整性
  • 外键明显有助于保证相关表之间的数据完整性
  • 表中存在违反约束的数据,向表中添加约束将会失败
  • 部分数据库创建外键会自动创建索引,提高连接查询性能
确保表间关系的合理性
当第三范式不够用时,采用更多范式

2、可编程性与索引设计

创建索引时空值的影响
  • 如果要搜索空值,但列中的大多数值都可能为NULL,最好不要对列进行索引
  • 每种数据库处理索引中的NULL方法都不同,在可能包含NULL的列上创建索引时要考虑数据库系统
创建索引时谨慎以最小化索引和数据扫描
  • 创建正确的索引以提高性能
  • 确保创建的索引都被使用
索引不只是过滤
  • WHERE子句中的列是否包含在索引中会影响查询性能
  • SELECT子句中的列是否被索引也会影响查询效率
  • 连接查询的列是否被索引可能会影响查询效率
  • 索引也可能对ORDER BY子句的效率产品影响
  • 多个索引的存在会对写入操作产生性能影响
不要过度使用触发器
  • 触发器可以适用场景
    维护重复或派生数据
    复杂列的约束
    复杂的默认值:可以使用触发器根据其他列、记录或表中的数据生成默认值
    数据库间引用完整性
  • 触发器不可移植
  • 仅在绝对必要时才使用触发器,如果可能,确保触发器是幂等
使用过滤索引包含或排除数据子集
使用声明式约束替代编码校验
  • NOT NULL 确保不为空
  • UNION 确保指定字段的值唯一
  • PRIMARY KEY 唯一标识
  • FOREIGH KEY 外键
  • CHECK
    单个字段:只能在该字段中存储指定的值
    表:可以对某些字段中的值基于同一行中的其他字段中的值进行限制
  • DEFAULT 定义默认值
了解数据库使用的SQL方言并编写相应的代码

不同的数据库,对空值的排序、限制结果集、boolean数据类型、SQL函数、UNIQUE约束都可能不同

了解何时在索引中使用计算结果

3、当你不能改变设计时

使用视图来简化不能更改的内容
  • 提炼特定的数据
  • 简化或清晰列的名称
  • 将数据从不同的表汇总起来
  • 简化数据操作
  • 保护敏感数据
  • 提供向后兼容
  • 自定义数据
  • 提供数据汇总
  • 注意:尽量不要在视图上创建视图
使用ETL将非关系数据转换为有用信息
创建汇总表并维护
使用UNION语句将非规范化数据列转行
  • UNION查询的SELECT必须具有相同的列
  • 每列的数据类型必须兼容
  • UNION会移除重复行

4、过滤与查找数据

了解关系代数及其如何在SQL中实现
  • 选择(限制)——在FROM子句中定义所需的数据及源头,然后使用WHERE或HAVING子句过滤返回的记录
  • 投影——使用SELECT子句(包括聚合函数)和GROUP BY子句来定义数据库系统返回哪些列
  • 连接——在FROM子句中使用JOIN关键字来执行连接。可以指定INNER JOIN 或OUTER JOIN来扩展此功能
  • 交集——必须具有相同列的两个集合上执行相交操作:INTERSECT
  • 笛卡儿积——将第一个集合中所有行与第二个集合中的所有行组合的结果:CROSS JOIN
  • 并集——合并两个具有相同列的集合,添加UNION关键字。可以指定UNION ALL不删除重复记录
  • 差集——减去另一个数据及,关键字为EXCEPT
查找不匹配或缺失的记录
  • 使用NOT EXISTS 操作符通常比NOT IN 运算符快
  • 使用DBMS查询分析器来确定哪种方式最适合
了解何时使用CASE解决问题
  • 需要解决IF…THEN…ELSE这类问题时
  • 可以使用简单的CASE来执行相等判断和基于搜索的CASE以使用复杂的条件
  • 可以使用值表达式的地方都可以使用CASE,包括作为SELECT子句中的列定义或作为WHERE或HAVING子句中条件的一部分
了解解决多条件查询的技术
如需完美匹配,先对数据进行除操作
如何按时间范围正确地过滤日期和时间的列
  • 不要依赖隐式日期转换;使用显式转换函数来处理日期字符
  • 不要将函数应用于日期和时间列,否则查询将不能使用索引
  • 舍入误差可能导致日期和时间值不正确;使用>=和<替代BETWEEN
书写可参数化搜索的查询以确保引擎使用索引
  • 可参数化运算符:=、>、<、>=、<=、BETWEEN、LIKE(不包括前导通配符)、IS (NOT) NULL
  • 可能是参数化,但性能提升可能没帮助:<>、IN、OR、NOT IN、 NOT EXISTS、NOT LIKE
  • 避免使用不可参数化搜索的操作符
  • 不要在WHERE子句条件中对一个或者多个字段使用函数(函数必须检查每一条记录,除非索引本身包含相同的函数)
  • 不要对WHERE子句中的字段执行算术计算
  • 使用LIKE 操作符时,只能在字符串末尾使用通配符LIKE ‘some%’,而不是 ‘%some%’、 LIKE ‘%some’
正确地定义左连接的右侧
  • 在SQL中使用OUTER JOIN执行差集操作
  • 当你对外部WHERE子句中的左连接加入右侧数据时使用过滤,将无法获得所需结果,反之亦然
  • 要正确地过滤数据子集,必须在数据库执行外连接之前使用过滤

5、聚合

理解GROUP BY 的工作原理
  • COUNT() 总数
  • SUM() 和
  • AVG() 平均值
  • MIN() 最小的值
  • MAX() 最大的值
  • HAVING 过滤取决于聚合的结果
简化GROUP BY 子句
  • GROUP BY 过多的列对查询性能有影响
  • 对于同时需要聚合和详细的查询,先查询聚合再连接查询明细
利用GROUP BY或HAVING解决复杂的问题

分组前使用WHERE过滤,分组后使用HAVING过滤

避免使用GROUP BY来查找最大值或最小值
使用OUTER JOIN时避免获取错误的COUNT()
测试HAVING COUNT(x)<某数时包含零值记录
使用DISTINCT获取不重复的计数
知道如何使用窗口函数
创建行号与排名
  • ROW_NUMBER():row_number() over(partition by 分组列 order by 排序列 desc)
  • RANK():rank() over(partition by 分组列 order by 排序列 desc)
  • 如果不想排名上有差距,可以使用DENSE_RANK()
创建可移动聚合函数

6、子查询

了解在何处使用子查询
  • 可以在表、视图或返回表的函数及过程的位置使用子查询
select e.* from (select Id,Name from Employee) e
where Id>2
  • 为IN或NOT IN条件提供一个列表的位置,使用返回单列的子查询
select * from Employee
where ManagerId in (select Id from Manager where IsDelete=0)
  • 在任何使用列名的位置使用标量子查询,如在SELECT 语句中
select Id,Name,ManagerId,
(select Name from Manager where Id= e.ManagerId) as ManegerName from Employee e
了解关联和非关联查询的差异
尽可能使用公共表表达式而不是子查询
  • CET(公共表达式):WITH table AS ()
with T1 as(
select * from Employee
where Id>2
)
selec * from T1
  • CET可以简化多次使用相同子查询的复杂查询
  • CET允许直接嵌入到另一个查询中的子查询
  • 可以使用递归CET遍历层次关系,并以有意义的方式进行展示
--生成一个1~100的数字列表
with SeqNumT as(
	select 1 as Num
	union all
	select Num+1
	from SeqNumT
	where Num<100
)
select Num from SeqNumT
使用连接而非子查询创建更高效的查询
  • 不要认为按顺序解决问题是首选方法。SQL语句最适合按集合,而不是按行运行
  • 了解DBMS优化器多种处理方式的特征,从而决定首选的解决方案
  • 确保为任何连接都建立了适当的索引

7、获取与分析元数据

了解如何使用系统的查询分析器
  • 了解如何获取DBMS的执行计划
  • 了解如何读取其生成的执行计划
学习获取数据库的元数据
理解执行计划的工作原理
  • 分析是否存在未使用的索引,定位未使用的原因
  • 分析所使用的操作是否适合正在使用的查询
  • 不要因为一个查询就加上索引来改善执行计划
  • 数据分布不均的数据对统一查询会需要不同的优化。当执行计划被缓存和重用时(通常是存储过程或客户端处理过的语句),这个问题就特别严重

8、笛卡尔积

生成两张表所有航的组合并标示一张表中间接关联的领一张表的列
  • 使用笛卡尔积产生升两个表之间的各种组合
理解如何以等分量排名
知道如何对表中的行进行配对
理解如何列出类别与前三偏好

9、计数表

根据计数表内定义的参数生成空行
使用计数表和窗口函数生成序列
根据计数表内定义的范围生成行
根据计数表定义的值范围转换某个表中的值
使用计数表行转列

10、层次数据建模

从邻接列表模型开始
  • 邻表连接只是在表中添加一个自引用表的主键的一个外键
  • 始终使用邻接列表模型构建一致的层次模型
    在这里插入图片描述
对不常更新的数据使用嵌套集以提升查询性能
使用存储路径简化设置与搜索
  • 存储路径的好处是容易理解和处理,如:1/3/8
  • 必须对层次结构加上限制避免产生问题
  • 存储路径的搜索仅在一个方向上有效,因为在开始或谓词中有通配符不能创建索引
使用祖先遍历闭包做复杂搜索

示例SQL源码
以上便是读《编写高质量SQL语句的61个有效方法》有感,很多自己不熟悉的也不深入的,如祖先遍历闭包等,便没有多写,有兴趣的可以自己买书看看或者去了解相关的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值