评价
这本书可以说是能让技术人员用最快速度入门SQL的一本书,对于很多一上来不知道SQL语法觉得概念很抽象的人来说也能对与SQL领域有一个大致的了解,当然作为一个过来人当初觉得SQL真的挺神奇的。
这本书当然只适合新手,对于老手来说更多是快速回顾和查漏补缺,所以这一篇读书笔记将会简单提炼一些忽视的部分记录,以及工作实践之后对于书中一些建议的补充和解释。
个人已经看过非常老的小绿本的版本,在看到出到第五本之后再看看这本书还挺感慨的。
最后千万不要认为SQL很简单,其实越是看似简单的东西越是复杂。
组合查询
组合查询一般指的是union
查询,对于union查询我们会疑问是使用union all
还是单纯的使用 union
,对于这两种用法的主要区别是:
- union:会对组合的列结果自动进行去重和排序。
- unioin all:不对结果进行排序,只是简单的把结果拼在一起,但是拼在一起的结果不保证顺序。
通过以上对比发现,我们需要注意union内部的操作带来的性能影响,比如Mysql就会在内部会自动去重排序,同时会产生中间表(分为内存中间表或者磁盘中间,视中间结果集数据量而定),而中间表通常意味着 索引失效。
下面是组合查询的案例:
SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_state IN ('IL','IN','MI') UNION SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_name = 'Fun4All';
使用 UNION 组合 SELECT 语句的数目,SQL没有标准限制
下面是书中记录的union的规则,对于加了重点标记,这里更加建议根据具体的数据库查看相关的设计限制:
- UNION 必须由两条或两条以上的 SELECT 语句组成,语句之间用关键字 UNION 分隔(因此,如果组合四条 SELECT 语句,将要使用三个 UNION 关键字)。
- UNION 中的每个查询必须包含相同的列、表达式或聚集函数(不过, 各个列不需要以相同的次序列出)。
- 列数据类型必须兼容:类型不必完全相同,但必须是 DBMS可以隐含 转换的类型(例如,不同的数值类型或不同的日期类型)。
其他类型的 UNION: 部分数据库支持一些特殊的组合查询方式,当然都可以使用连接查询替代,所以如果有可能出现换库的可能性,就不要写一些针对某款数据库的SQL, 以免增加阅读难度和迁移难度。 下面是一些特殊的UINON操作:
- EXCEPT (有时称为 MINUS )可用来检索只在第一个表中存在而在第二个表中不存在的行
- INTERSECT:可用来检索两个表中都存在的行
插入数据
插入语句的写法是insert into table values (xxx,xxx)
或者insert into table (xxx,xx) values (xxx,xxx)
,在编写插入语句的时候建议指定插入列,因为一旦新增字段如果插入列的SQL没有更新会直接导致业务报错。
INSERT INTO Customers(cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country) SELECT cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country FROM CustNew;
更新和删除数据
对于更新和删除的动作记住一条铁律:做任何更新和删除操作之前先查询一遍确认影响和操作的行符合预期的效果,这里特别强调需要注意where条件中对于 NULL 值的列数据匹配问题。
另外通常管理比较严格的公司或者项目一般都不会给更新或者删除权限,而是需要经过运维或者DBA的审查之后进行特殊环境的操作才能完成整个操作,所以需要在更新操作之前确保是否又足够的用户权限。
部分数据库支持通过查询的结果进行更新,比如Postgresql就支持下面的写法:
update tables set tables.name = tmp.name from (select id,name from User where name = 'xxx') tmp where tmp.id = tables.id;
当然这样的写法并不是所有的数据库支持,但是使用之后体验还是非常不错的,如果不需要关注数据库移植问题可以放心大胆的使用,体验一条SQL包罗万象的感觉。
删除数据
在使用 DELETE 时一定要细心不要漏了where条件,一旦缺少这个条件......可不是等着被请去喝茶那么简单。
如果想从表中删除所有行,不要使用 DELETE 。可使用 TRUNCATE TABLE。这个指令不会记录相关日志直接删除数据,代价是一旦删错后果自负。
另外删除数据在BTree结构的底层并不是真的删除,以MYSQL为例,在删除的时候只是把当前记录的行标示位标记未“已删除”,然后后续在后台进程启动的时候会自动回收这些不可用使用页。从另一个角度来说,删除数据容易导致数据页间隙之间的碎片可用空间很多,这也是使用TRUNCATE TABLE的原因之一。
更新和删除原则
- 除非确实打算更新和删除每一行,否则绝对不要使用不带 WHERE 子句 的 UPDATE 或 DELETE 语句。
- 保证每一个表都有主键。
- 先SELECT后DELETE或者UPDATE 永远是一个好习惯。
- 对于ORM框架来说,建议编写插件或者拦截方式防止不带where条件的update或者delete执行。
创建和操纵表
大多数时候我们使用navicat这样的工具设计表结构比较多,使用原生SQL的方式则根据不同的数据库实现会有不同,主要的差别点在数据类型和语法的差别上,所以可视化软件也算是帮我们省去了细节的不同差别。
创建表的最简单案例如下:
CREATE TABLE Products ( prod_id CHAR(10) NOT NULL, vend_id CHAR(10) NOT NULL, prod_name CHAR(254) NOT NULL, prod_price DECIMAL(8,2) NOT NULL, prod_desc VARCHAR(1000) NULL );
NULL 值和空字符串 注意这两者有本质的区别,NULL值在数据库中是一个特殊值,在聚合函数统计的时候常常会被排除在统计范围内(除了count()函数较为特别之外)。而空字符串是一个具体的值,并不是没有值。
更多情况下使用defualt
代替null
或者not null
的情况比较多,因为过多的not null
不是很好造数据,还有一种方式是放弃数据库做not null
限制,而是在业务代码中通过设置默认值防止null值出现也是一种思路。
注意事项
- 不要在表中包含数据时对其进行更新,数据量较小的时候可能看不出问题,但是一旦数据量上百万级别,修改一个字段的时候数据库通常会加上元数据锁,同时由于需要调整底层数据结构,通常需要耗费非常多的时间。
- 设计表的时候不建议使用"预留字段"的方式在后续扩展的时候进行兼容,这种预留字段看似很有用,但是实际使用的时候会发现给的预留字段往往无法满足业务要求,还是需要另外加新的字段,当然修改字段还是要小心第一条提到的警告。
- 多数 DBMS允许重新命名表中的列。
- 所有的 DBMS都允许给现有的表增加列,不过对所增加列的数据类型 (以及 NULL 和 DEFAULT 的使用)有所限制。
大表新增字段:
如上面的建议所说,书中对于复杂的表结构更改一般需要手动删除过程提出以下步骤:
(1) 用新的列布局创建一个新表;
(2) 使用 INSERT SELECT 语句(关于这条语句的详细介绍,请参阅第 15 课)从旧表复制数据到新表。有必要的话,可以使用转换函数和计算 字段;
(3) 检验包含所需数据的新表;
(4) 重命名旧表(如果确定,可以删除它);
(5) 用旧表原来的名字重命名新表;
(6) 根据需要,重新创建触发器、存储过程、索引和外键
经验和技巧:对于快速了解业务的时候,通过在自己本地重命名这张表观察业务会出现什么变化,就能快速的掌握一张表干什么时候,这对于字段特别特别多的表来说是非常好用的一条规则,当然 仅仅本地能这么干
使用 ALTER TABLE 要极为小心,应该在进行改动前做完整的备份(表 结构和数据的备份)。因为数据库表的更改不能撤销,如果增加了不需要的 列,也许无法删除它们,比如删除某一列之后,会导致当前数据库的当前列所有数据丢失并且无法找回。
删除表
DROP TABLE CustCopy;
这条语句的使用也要极为小心,删除之前先查询几遍反复确认没有删除错误,另外不要在精神不好的时候特别是熬夜干活的时候干这种事情。
重命名
所有重命名操作的基本语法都要求指定旧表名和新表名
视图
虽然创建视图的语法类似但是很可惜的是视图不仅在不同的数据库实现细节差别很大,在同一个数据库的不同版本也可能会有很大差别,所以建议根据自己所用的数据库官方问文档确认具体有哪些限制。
视图通常分为两种:逻辑视图和物化视图。逻辑视图通常指的是数据库根据优化器的优化查询树生成一张虚拟表,这张表不占用实际的存储空间,而物化视图则会占用实际的存储空间。
下面是创建视图的一个案例:
CREATE VIEW OrderItemsExpanded AS SELECT order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price FROM OrderItems
为什么使用视图
- 重用SQL,这一点很关键,视图主要目的就是为了简化大量重复操作。
- 对于经常只读的数据使用视图可以简化大量重复操作,同时可以简化复杂的SQL语句。
- 保护原表数据同时视图可以自由定义返回数据的格式,不需要受到底层数据表的字段数据类型限制。
- 可以只使用表的一部分而不是整个表的数据。
视图性能问题 因为视图使用的是根据查询优化树去查询数据,所以其实视图并不包含数据,所以每次使用视图时,都必须处理查询执行时。
视图限制
对于视图的限制不同数据库供应商差别较大,所以下面的条例并不是完全适用所有数据库。
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造视图。
- 一些数据库实现不允许ORDER BY。
- 如果列是计算字段通常需要重命名。
- 部分数据库实现的视图只是一个只读列表,不能通过修改视图修改底层数据。
- 视图不能索引,也不能有关联的触发器或默认值。
- 与表一样,视图必须唯一命名
- 视图的创建需要遵循SELECT的限制和规则。
如果在视图中加入where条件,则会自动合并where条件然后返回结果,如果难以理解可以认为是下面的情况:
select (view info) where xx = xxx