再谈连接
外连接
之前的范例都是没有考虑条件可能无法为表中的所有行匹配的问题
左外连接与右外连接
SELECT a.account_id, a.cust_id, b.name FROM account a LEFT OUTER JOIN business b ON a.cust_id = b.cust_id;
外连接包括第一个表的所有行,但仅仅包含第二个表中那些匹配行的数据。关键字 left 指出连接左边的表决定结果集的行数,而右边只负责提供与之匹配的列值。因此,如果想要通过表 A 和 B 外连接得到结果为 A 中的所有行和 B 中匹配列的额外数据,则可以指定 A left outer join B 或者 B right outer join A。
三路外连接
SELECT a.account_id, a.product_cd, CONCAT(i.fname, ' ', i.lname) AS person_name, b.name AS business_name
FROM account a LEFT OUTER JOIN individual i ON a.cust_id = i.cust_id
LEFT OUTER JOIN business b ON a.cust_id = b.cust_id;
--利用子查询限制查询中连接的数目
SELECT account_ind.account_id, account_ind.product_cd, account_ind.person_name, b.name AS business_name FROM
(SELECT a.account_id, a.product_cd, a.cust_id, CONCAT(i.fname, ' ', i.lname) AS person_name FROM account a
LEFT OUTER JOIN individual i ON a.cust_id = i.cust_id) account_ind LEFT OUTER JOIN business b ON account_ind.cust_id = b.cust_id;
--这两个的结果一样
自外连接
SELECT e.fname, e.lname, e_mgr.fname AS mgr_fname, e_mgr.lname AS mgr_lname FROM employee e LEFT OUTER JOIN employee e_mgr
ON e.superior_emp_id = e_mgr.emp_id;
SELECT e.fname, e.lname, e_mgr.fname AS mgr_fname, e_mgr.lname AS mgr_lname FROM employee e RIGHT OUTER JOIN employee e_mgr
ON e.superior_emp_id = e_mgr.emp_id;
上面的这两个例子所展示的结果明显不同,这仅仅是由改变一个关键字引起的,因此,我们在使用外连接时要确定到底使用左外连接还是右外连接。
交叉连接
如果想生成两个表的笛卡儿积,则需要指定交叉连接。
select pt.name, p.product_cd, p.name from product p, product_type pt;
或 select pt.name, p.product_cd, p.name from product p CROSS JOIN product_type pt;(官方建议的标准写法)
自然连接
所谓自然连接,是指依赖多表交叉时的相同列名来推断正确的连接条件。
例如,account 表包含了一个名为 cust_id 的列,它是来自 customer 表的外键,也是 customer 表的主键,我们可以使用自然连接编写一个查询来连接这两个表:
SELECT a.account_id, a.cust_id, c.cust_type_cd, c.fed_id FROM account a NATURAL JOIN customer c;
由于指定了自然连接,因此服务器检查表的定义并给两个表的连接添加了连接条件 a.cust_id = c.cust_id。
条件逻辑
简单的说,条件逻辑是程序执行时从多个路径中选取其一的能力。
case 表达式
查找型 case 表达式
语法如下:
CASE
WHEN C1 THEN E1
WHEN C2 THEN C2
...
WHEN CN THEN EN
[ELSE ED]
END
case 表达式可以返回任意类型的表达式,甚至包括子查询。
SELECT c.cust_id, c.fed_id,
CASE
WHEN c.cust_type_cd = 'I' THEN
(SELECT CONCAT(i.fname, ' ', i.lname) FROM individual i WHERE i.cust_id = c.cust_id)
WHEN c.cust_type_cd = 'B' THEN
(SELECT b.name FROM business b WHERE b.cust_id = c.cust_id)
ELSE 'Unknown'
END name
FROM customer c;
这个查询也可以用外连接,不过使用这个版本的话,服务器只有在需要时才从 individual 和 business 表中读取数据,而不会总是连接所有的表。
简单 case 表达式
语法:
CASE V0
WHEN V1 THEN E1
WHEN V2 THEN E2
...
WHEN VN THEN EN
[ELSE ED]
END
其实就是编程语言里面的 case 语法了。
事务
锁:锁是数据库服务器用来控制数据资源被并行使用的一种机制。当数据库的一些内容被锁定时,任何打算修改(或者可能是读取)这个数据的用户必须等到锁被释放。
锁的粒度:
- 表锁:阻止多用户同时修改同一个表的数据。
- 页锁:阻止多用户同时修改某表中同一页(一页通常是一段2~16KB的内存空间)的数据。
- 行锁:阻止多用户同时修改某表中同一行的数据。
事务:它是一种将多条 SQL 语句聚集到一起,并且能够实现要么所有语句都执行,要么一个都不执行(这个属性称为原子性)。
启动事务
数据库服务器以下面两种方法之一创建事务:
- 一个活跃事务总是和数据库会话相联系,所以没有必要,也没有什么方法能够显示地启动一个事务。当前事务结束时,服务器自动为会话启动一个新的事务。
- 如果不显示地启动一个会话,单个的 SQL 语句会被独立于其他语句自动提交。启动一个事务之前需先提交一个命令。
结束事务
一旦事务启动,不管是通过 start transaction 命令显示地启动还是由数据库服务器隐式地启动,为了持久化数据变化都必须显示地结束事务。可以通过 commit 指令解决这个问题,该指令命令服务器将变化标记为永久性的,进而释放事务中使用的任何资源(也就是页锁或者行锁)。
如果打算撤销自事务启动时所发生的一切变化,读者必须提交 rollback 指令,它命令服务器将数据返回到处理前的状态。同样,会话使用的任何资源都会在 rollback 完成后被释放。
除了提交 commit 或 rollback 指令,结束事务还可以由其他情景触发,要么作为活动的间接结果,要么作为意外的结果:
- 服务器宕机,在这种情况下,服务器重启时事务将会被自动回滚;
- 提交一个SQL模式语句,比如 alter table,这将会引起当前事务提交和一个新事务的启动;
- 提交另一个 start transaction 命令,将会引起前一个事务提交;
- 如果服务器检测到一个死锁并且确定当前事务就是罪魁祸首,那么服务器就会提前结束当前的事务。这种情况下,事务将会被回滚,同时释放错误信息。
索引与约束
索引是寻找资源中特定项目的一种机制。
- 创建索引:ALTER TABLE department ADD INDEX dept_name_idx (name);
- 查看索引:SHOW INDEX FROM department;
- 删除索引:ALTER TABLE department DROP INDEX dept_name_idx;
- 唯一索引:ALTER TABLE department ADD UNIQUE dept_name_idx (name); --这里不需要为主键列创建索引,因为服务器已经为主键检查唯一性。
- 多列索引:ALTER TABLE employee ADD INDEX emp_names_idx (lname, fname); --创建多列索引时,必须仔细考虑哪一列作为第一列,哪一列作为第二列等。
- 索引类型:B树索引,位图索引,文本索引
如何使用索引
服务器通常首先利用索引快速定位特定表中的行,之后再访问相关表提取用户请求的补充信息。不过,如果索引包含满足查询的所有内容,那么服务器就不必访问相关表了。
索引的不足
每个索引事实上都是一个表,因此每次在对表添加或者删除行时,表中的索引必须被修改;当更新行时,受到影响的列的任何索引也必须被修改。因此,索引越多,服务器就需要做越多的工作来保持所有模式对象最新,这将会拖慢服务器处理任务的速度。另外,索引需要磁盘空间,同时也需要管理员耗费一些精力去管理它们。
约束
创建约束
CREATE TABLE product
(product_cd VARCHAR(10) NOT NULL,
name VARCHAR(50) NOT NULL,
product_type_cd VARCHAR(10) NOT NULL,
date_offered DATE,
date_retired DATE,
CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd) REFERENCES product_type (product_type_cd),
CONSTRAINT pk_product PRIMARY KEY (product_cd)
);
上面的这个表里包含了两个约束:一个指定了 product_cd 列作为表的主键,另一个指定了 product_type_cd 列作为来自 product_type 表的外键。
我们也可以先创建表再添加约束:
ALTER TABLE product
ADD CONSTRAINT pk_product PRIMARY KEY (product_cd);
ALTER TABLE product
ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd) REFERENCES product_type (product_type_cd);
删除主键约束或者外键约束:
ALTER TABLE product
DROP PRIMARY KEY;
ALTER TABLE product
DROP FOREIGN KEY fk_product_type_cd;
约束与索引
创建约束有时可能导致自动创建一个索引,不过,数据库服务器对于约束和索引的关系有着不同的处理原则。在实施主键约束,外键约束和唯一约束时,MySQL 会生成新索引;SQL Server 只为主键约束和唯一约束生成新索引,不管外键约束。
级联约束
有了合适的外键约束后,如果想在子表插入新行或者修改行而导致父表中的外键列并无可匹配值,那么服务器会抛出一个错误;如果想删除或者修改一个父行,也会因为外键约束而抛出错误。那么这个时候,我们可以用级联约束命令服务器帮助自己将变化传播到所有子行,这样就保证了数据的完整性。on update cascade
用来指定级联更新,on delete cascade
用来指定级联删除。
ALTER TABLE product
ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd)
REFERENCES product_type (product_type_cd)
ON UPDATE CASCADE
ON DELETE CASCADE;
有了这个版本的约束,当 product_type 表中的一行被更新时,服务器将更新 product 表中的子行,同样,如果 product_type 表中的行被删除,product 表中的子行也将被删除。