《SQL 必知必会》第 4 版

豆瓣:https://book.douban.com/subject/24250054/

SQL 文件下载地址:https://forta.com/books/0672336073/

1 - 了解 SQL

数据库

保存有组织的数据的容器(通常是一个文件或多个文件)。

数据库软件应称为数据库管理系统(DBMS).

某种特定类型数据的结构化清单。

表中的一个子段。所有表都是由一个或多个列组成的。

主键:

表中的任何列都可以作为主键,只要它满足以下条件:

  • 任意两行都不具备有相同的主键值;
  • 每一行都必须具有一个主键值(主键列不允许 NULL 值);
  • 主键列中的值不允许修改或更新;
  • 主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)。

2 - 检索数据

查询语句 SELECT

检索一个或多个列。

SELECT 列名 FROM 表名;
SELECT * FROM 表名;

注释

在大多数数据库软件中,支持 # -- /**/ 等单行和多行注释。

结果集去重 DISTINCT

SELECT DISTINCT 列名 
FROM 表名;

DISTINCT 关键字限制所有的列,如果后接多列,除非两列内容完全相同,否则都会被纳入结果集。

限制结果 LIMIT

大多用于分页显示:

-- 从第 0 行开始,显示 5 个。第一页:
SELECT * FROM 表名 LIMIT 5 OFFSET 0;
-- 从第 5 行开始,现实 5 个。第二页:
SELECT * FROM 表名 LIMIT 5 OFFSET 5;

MySQL、MariaDB、SQLite 简化语法:

# 从 n 起,取 m 行。 n 可省略,默认为 0。
SELECT 1, 2 FROM  LIMIT n, m; 

-- 示例:
-- 第一页:
SELECT * FROM 表名 LIMIT 0, 5;
-- 第二页:
SELECT * FROM 表名 LIMIT 5, 5;
-- 第三页
SELECT * FROM 表名 LIMIT 10, 5;

3 - 排序检索数据

ASC 默认,升序,全称 ascending。

DESC 降序,全称 descending。

排序数据 ORDER BY

SELECT 列名 
FROM 表名
ORDER BY 列名;

ORDER BY 子句中用非检索的列排序也是合法的。

按多个列排序

写在前面的优先级高。

SELECT 列名 
FROM 表名
ORDER BY 列名1, 列名2;

按列位置排序,可以使用列名的书写位置,不推荐:

SELECT 列名1, 列名2, 列名3
FROM 表名
ORDER BY 2, 3;

降序排列

ORDER BY 默认为升序排列。(从上至下,越来越大(0→100,a→z))

DESC 只对前面那一列生效,如果想多列使用降序排序,必须对每一列指定 DESC 关键字。

示例:(prod_price 列降序排序,prod_name 仍按升序排序)

SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC, prod_name

4 - 过滤数据

WHERE

SELECT 列名
FROM 表名
WHERE 条件;

操作符

常见操作符:< > = <= >= != 。。。

BTEWEEN 操作符,左闭右闭。

IS NULL

5 - 高级数据过滤

AND OR

AND 优先级大于 OR

使用小括号,不依赖优先级顺序,可以消除歧义。

IN

指定范围,集合中的每个条件都可以进行匹配。

SELECT * 
FROM 表名
WHERE xxx_id IN (no1, no2);

加强版的 OR,使逻辑更清晰。

NOT

否定紧跟的条件。

6 - 用通配符进行过滤

LIKE

LIKE 指示 DBMS 后跟的搜索模式利用通配符而不是简单的相等匹配进行比较。

% 通配符

% 能匹配 0 个、1 个或多个字符。

示例:

SELECT * FROM Products WHERE prod_name LIKE "fish%";

_ 通配符

_ 总是刚好匹配一个字符,不能多也不能少。

示例:(与 % 的区别与对比)

SELECT prod_name FROM Products WHERE prod_name LIKE "% inch teddy bear";
-- 8 inch teddy bear
-- 12 inch teddy bear
-- 18 inch teddy bear

SELECT prod_name FROM Products WHERE prod_name LIKE "__ inch teddy bear"; -- 两个下划线
-- 12 inch teddy bear
-- 18 inch teddy bear

SELECT prod_name FROM Products WHERE prod_name LIKE "_ inch teddy bear"; -- 一个下划线
-- 8 inch teddy bear

注意

  • 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
  • 在确实需要使用通配符时,也尽量不要把它们用在搜索模式的开始处,搜索起来是最慢的。
  • 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

7 - 创建计算字段

拼接字段

MySQL 和 MariaSQL 使用 CONCAT() 函数拼接字符串;

使用 TRIM()LTRIM()RTRIM 去掉字符串两边、左、右的空格。

示例:

SELECT
	CONCAT(
		TRIM( vend_name ),
		'\t\t(',
		TRIM( vend_country ),
		')' 
	) 
FROM
	Vendors;

AS 别名

计算字段

对检索出的数据进行算术计算:

SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM OrderItems 
WHERE order_num = 20008;

8 - 使用函数处理数据

函数

  • 文本处理函数
  • 日期和时间处理函数
  • 数值处理函数

需要参考相应 DBMS 文档。

9 - 汇总数据

聚集函数

对某些行运行的函数,计算并返回一个值。

AVG() 平均值

只适用于单列,忽略值为 NULL 的行。

COUNT() 计数

COUNT(*) 对表中行的数目进行计算,不管表列中是否包含 NULL 值。

COUNT(列名) 会忽略值为 NULL 的行。

MAX() 最大值

MIN() 最小值

SUM() 求和

聚集不同值

对以上 5 个聚集函数都可以使用如下参数:

  • ALL 默认,对所有行执行计算。
  • DISTINCT 只包含不同的值。(不能用于 COUNT(*),只能用于列名。)

例:

SELECT AVG(DISTINCT salary) FROM employee;

会忽略工资相同的员工工资,来计算平均值。

10 - 分组数据

GROUP BY 分组

示例:

SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;

-- vend_id	num_prods
-- BRS01	3
-- DLL01	4
-- FNG01	2
  • GROUP BY 子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
    • 如果嵌套了分组,数据将在最后指定的分组上进行汇总。
  • GROUP BY 子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。
  • 除聚集计算语句外,SELECT 语句中的每一列都必须在 GROUP BY 子句中给出。
  • 如果分组列中包含 NULL 值的行,则 NULL 将作为一个分组返回。如果列中有多行 NULL 只,它们将分为一组。

HAVING 过滤分组

示例:(列出具有 2 个以上产品且其价格大于等于 4 的供应商)

SELECT vend_id, COUNT(*)
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 2;

-- vend_id	COUNT(*)
-- BRS01	3
-- FNG01	2

HAVING 支持所有 WHERE 的操作符。

紧接 GROUP BY,不能单独存在。

聚合函数只能与具体数据做条件判断,不能与字段做条件判断。(可用表连接实现)

WHEREHAVING 的差别:

WHERE 不能使用聚合函数,HAVING 可以在分组后使用聚合函数。

WHERE 在数据分组前进行过滤,HAVING 在数据分组后进行过滤。

所以 WHERE 排除的行不包括在分组中,这可能会改变计算值,从而影响 HAVING 子句中基于这些值过滤掉的分组。

ORDER BY 分组和排序

一般在使用 GROUP BY 子句时,应该也给出 ORDER BY 子句。这时保证数据正确排序的唯一方法。千万不要尽依赖 GROUP BY 排序数据。

SELECT 子句书写顺序

子句 说明 是否必须使用
SELECT 要返回的列或表达式
FROM 从中检索数据的表 仅在从表选择数据时使用
WHERE 行级过滤
GROUP BY 组级过滤 仅在按组计算聚集时使用
HAVING 组级过滤
ORDER BY 输出排序顺序

11 - 使用子查询

利用子查询进行过滤

作为子查询的 SELECT 语句只能查询单个列。

示例,列出订购物品 RGAN01 的所有顾客:

1:检索包含物品 RGAN01 的所有订单的编号

SELECT order_num
FROM OrderIterms
WHERE prod_id = 'RGAN01';

2:检索具有前一步骤列出的订单编号的所有顾客的 ID

SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
                    FROM OrderIterms
                    WHERE prod_id = 'RGAN01');

3:检索前一步骤返回的所有顾客 ID 的顾客信息

SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN (SELECT cust_id
                  FROM Orders
                  WHERE order_num IN (SELECT order_num
                                      FROM OrderIterms
                                      WHERE prod_id = 'RGAN01'));

内连接写法:(第 12 课)

-- 列出订购物品 RGAN01 的所有顾客:
SELECT
	cust_name,
	cust_contact 
FROM
	OrderItems,
	Orders,
	Customers 
WHERE
	OrderItems.prod_id = 'RGAN01' 
	AND OrderItems.order_num = Orders.order_num 
	AND Orders.cust_id = Customers.cust_id;

作为计算字段使用子查询

示例,现实 Customers 表中每个顾客的订单总数:

1:从 Customers 表中检索顾客列表;

SELECT COUNT(*) AS orders
FROM Orders
WHERE cust_id = '1000000001';

2:对于检索出的每个顾客,统计其在 Orders 表中的订单数目

SELECT cust_name, 
       cust_state,
	   (SELECT COUNT(*)
		FROM Orders
		WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;

12 - 连接表

连接

为什么使用连接?

如果数据存储在多个表中,怎么用一条 SELECT 语句就检索出数据呢?

答案是使用连接。

内连接(inner join)

内连接,又称等值连接(equijoin),它基于两个表之间的相等测试。

示例:

SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;

MySQL 中的多种写法:

SELECT ... FROM 1, 2 WHERE 连接条件;
SELECT ... FROM 1 [INNER] JOIN 2 ON 连接条件;
SELECT ... FROM 1 [INNER] JOIN 2 WHERE 连接条件; (在使用外连接时,使用 WHERE 会出问题)

笛卡尔积:(叉连接)(cross join)

没有连接条件的表关系返回的结果为笛卡尔积,检索出的行数将是第一个表中的行数乘以第二个表中的行数。

连接多个表

-- 显示订单 20007 中的物品。
SELECT
	prod_name,
	vend_name,
	prod_price,
	quantity 
FROM
	OrderItems,
	Products,
	Vendors 
WHERE
	order_num = 20007 
	AND OrderItems.prod_id = Products.prod_id 
	AND Products.vend_id = Vendors.vend_id;
-- 列出订购物品 RGAN01 的所有顾客:
SELECT
	cust_name,
	cust_contact 
FROM
	OrderItems,
	Orders,
	Customers 
WHERE
	OrderItems.prod_id = 'RGAN01' 
	AND OrderItems.order_num = Orders.order_num 
	AND Orders.cust_id = Customers.cust_id;

13 - 创建高级连接

使用表别名

除了之前的给列起别名,也可以在 FROM 子句中给表起别名,以供在 SELECTWHERE 子句中使用。

可以缩短 SQL 语句。允许在一条 SELECT 语句中方便多次使用相同的表。

自连接(self-join)

内连接的一种,连接的是同一个表。

示例:

-- 给 Jim Jones 同一公司的所有顾客发送一封信件。
SELECT c1.cust_name, c1.cust_contact, c1.cust_email
FROM Customers AS c1, Customers AS c2
WHERE c2.cust_contact = 'Jim Jones'
AND c1.cust_name = c2.cust_name;
-- cust_name	cust_contact		cust_email
-- Fun4All		Jim Jones			jjones@fun4all.com
-- Fun4All		Denise L. Stephens	dstephens@fun4all.com

自连接比子查询性能更好。

自然连接(natural join)

自然连接排除多次出现,使每一列只返回一次。

SELECT * FROM Customers NATURAL JOIN Orders;
-- 两个表中都有 cust_id 列,但只返回了一个。

*感觉没什么用啊。。。除非真的要查询这么多字段。

外连接(outer join)

  • 内连接只返回符合条件的记录,外连接还返回不符合条件的记录。
  • 左外连接即保留左表的所有记录,与右表连接;
  • 右外连接即保留右表的所有记录,与左表连接。
  • 左外连接(left outer join)与右外连接(right outer join)可以互相转换。(SQLite 即便不支持右外连接,也可以用左外连接代替)

内外连接的区别,示例:(查询每个顾客的订单计数,外连接可以查询出尚未下过订单的顾客)

-- 内连接,只查询出条件关联的行
-- 查询不到顾客 1000000002,因为他没有下过订单
SELECT Customers.cust_id, Orders.order_num
FROM Customers JOIN Orders
ON Customers.cust_id = Orders.cust_id;
-- cust_id		order_num
-- 1000000001	20005
-- 1000000001	20009
-- 1000000003	20006
-- 1000000004	20007
-- 1000000005	20008

-- 外连接,保留顾客表中所有顾客的记录,去外连接订单表
-- 可以查询到 1000000002
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT JOIN Orders
ON Customers.cust_id = Orders.cust_id;
-- cust_id		order_num
-- 1000000001	20005
-- 1000000001	20009
-- 1000000002	(NULL)
-- 1000000003	20006
-- 1000000004	20007
-- 1000000005	20008

-- 完善:外连接+分组计数
-- 带聚集函数的连接
SELECT Customers.cust_id, COUNT(Orders.order_num)
FROM Customers LEFT JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
-- cust_id		COUNT(Orders.order_num)
-- 1000000001	2
-- 1000000002	0
-- 1000000003	1
-- 1000000004	1
-- 1000000005	1

其他外连接示例:

-- 列出所有产品以及订购数量,包括没有人订购的产品
SELECT Products.prod_id, SUM(IFNULL(OrderItems.quantity,0))
FROM Products LEFT JOIN OrderItems
ON Products.prod_id = OrderItems.prod_id
GROUP BY Products.prod_id;

全外连接(full outer join)

MySQL、MariaSQL、SQLite、Access、Open Office Base 不支持全外连接。

全外连接检索两个表中的所有行并关联那些可以关联的行。

14 - 组合查询

组合查询( UNION

SQL 允许执行多个查询(多条 SELECT 语句),并将结果作为一个查询结果返回。

主要有两种情况需要使用组合查询:

  • 在一个查询中从不同的表返回结果数据;
  • 对一个表执行多个查询,按一个查询返回数据。

提示:多数情况下,组合相同表的两个查询,通过具有多个 WHERE 子句的 SELECT 语句都可以作为一个组合查询。

创建组合查询

示例:(需要 Illinois、Indiana、Michigan 等美国几个州的所有顾客的报表,还想包括不管位于哪个州的所有的 Fun4All)

单条语句 1:

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI');
-- cust_name		cust_contact	cust_email
-- Village Toys		John Smith		sales@villagetoys.com
-- Fun4All			Jim Jones		jjones@fun4all.com
-- The Toy Store	Kim Howard		(NULL)

单条语句 2:

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
-- cust_name	cust_contact		cust_email
-- Fun4All		Jim Jones			jjones@fun4all.com
-- Fun4All		Denise L. Stephens	dstephens@fun4all.com

UNION 组合:

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';
-- cust_name		cust_contact			cust_email
-- Village Toys		John Smith				sales@villagetoys.com
-- Fun4All			Jim Jones				jjones@fun4all.com
-- The Toy Store	Kim Howard				(NULL)
-- Fun4All			Denise 	L. Stephens		dstephens@fun4all.com

使用 WHERE,效果相同:

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
OR cust_name = 'Fun4All';

包含或取消重复的行:

上面例子中,Fun4All Jim Jones jjones@fun4all.com 这一行在两个 SELECT 语句中均被查询出,但 UNION 取消了重复的行。

UNION ALL 可以让每个条件的匹配行全部出现。

排序:

使用 UNION 组合查询时,只能使用一条 ORDER BY 子句进行排序,它必须位于最后一条 SELECT 语句之后。

UNION 规则

  • UNION 必须由两条或两条以上的 SELECT 语句组成,语句之间用关键字 UNION 分割。

  • UNION 中的每个查询必须包含相同数量的列、表达式或聚集函数。

  • 列数据类型必须兼容:类型不必完全相同但必须兼容(如不同长度的字符串)

15 - 插入数据

数据插入

INTERT 语句:用来将行插入(或添加)到数据库表,插入有几种方式:

  • 插入完整的行;
  • 插入行的一部分;
  • 插入某些查询的结果。

插入完整的行:(MySQL 中可以省略 INTO

INSERT INTO Customers
VALUES('1000000006',
		'Toy Land',
		'123 Any Street',
		'New York',
		'NY',
		'11111',
		'USA',
		NULL,
		NULL);

这种插入方法必须完整的按顺序提供列名所对应的数据。

由于较为依赖于表中列的定义次序,在表结构变动后会变得不安全或更改更麻烦。

更安全的方法 or 插入部分字段:

INSERT INTO Customers(cust_id,
					  cust_name,
					  cust_address,
					  cust_city,
					  cust_state,
					  cust_zip,
					  cust_country,
					  cust_contact,
					  cust_email)
VALUES('1000000006',
		'Toy Land',
		'123 Any Street',
		'New York',
		'NY',
		'11111',
		'USA',
		NULL,
		NULL);

这样 DBMS 将用 VALUES 列表中的值按次序一一对应填入列表中的对应项。

也可以省略部分字段,只要这些省略的字段允许 NULL 值或提供默认值。

一次插入多行数据

# 单条数据
INSERT INTO 表名[(字段1, 字段2...)] VALUES(1, 2...);
# 多条数据
INSERT INTO 表名[(字段1, 字段2...)] VALUES(1, 2...), (1, 2...) ...;

插入检索出的数据

INSERT SELECT 语句可以插入检索出的行:

INSERT INTO Customers(cust_id,
					  cust_name,
					  cust_address,
					  cust_city,
					  cust_state,
					  cust_zip,
					  cust_country,
					  cust_contact,
					  cust_email)
SELECT cust_id,
	   cust_name,
	   cust_address,
	   cust_city,
	   cust_state,
	   cust_zip,
	   cust_country,
	   cust_contact,
	   cust_email
FROM CustNew;

插入行数取决于 SELECT 语句查询出多少行。

DBMS 不在乎 SELECT 返回的列名,会将查询出的字段按 INSERT 语句给出的次序一一对应。

从一个表复制到另一个表

SQL 语法:

SELECT *
INTO CustCopy
FROM Customers;

MySQL、MariaDB、SQLite、Oracle、PostgreSQL 语法:

CREATE TABLE CustCopy AS
SELECT * FROM Customers;

message:

There is no primary key here. Update will only use exact matching of the old values of the columns here. Thus, it may update more than one record.

*MySQL 在复制时没有复制主键约束。

16 - 更新和删除数据

在更新和删除数据时,如果不指定 WHERE 条件,将会更新或删除所有行。

更新数据(UPDATE

基本的 UPDATE 语句由三部分组成:

  • 要更新的表;
  • 列名和它们的新值;
  • 确定要更新哪些行的过滤条件。

更新一列:

UPDATE Customers
SET cust_email = 'kim@thetoystore.com'
WHERE cust_id = '1000000005';

更新多列:

UPDATE Customers
SET cust_contact = 'Sam Roberts',
	cust_email = 'sam@toyland.com'
WHERE cust_id = '1000000006';

UPDATE 语句中进行表连接

# 把ALLEN调往RESEARCH部门,职务调整为ANALYST
UPDATE t_emp e 
JOIN t_dept d ON e.ename = 'ALLEN' AND d.dname = 'RESEARCH'
SET e.deptno = d.deptno, e.job = 'ANALYST', d.loc = '北京';

支持内连接另一个结果集:

# 把底薪低于公司平均底薪的员工,底薪增加150元
UPDATE t_emp e
JOIN (SELECT AVG(sal) AS avg FROM t_emp) e2 ON e.sal < e2.avg
SET sal = sal + 150;

同样支持外连接。

删除数据(DELETE

关于删除所有的行:

不指定 WHERE 条件时,DELETE 将会删除所有的行,但如果你明确要这么做,使用 TRUNCATE TABLE 可以更快的删除整个表的数据(因为不记录数据的变动(没有事务))。

删除行:

DELETE FROM Customers
WHERE cust_id = '1000000006';

同样支持表连接:

DELETE 1, ...
FROM 1 JOIN 2 ON 条件
[WHERE 条件 ...];

可以一次性删除多个表的内容:

# 删除SALES部门和该部门的全部员工记录
DELETE e,d
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
WHERE d.dname="SALES";

同样支持外连接。

17 - 创建和操纵表

创建表:

CREATE TABLE 表名 (
    列名1 数据类型 [约束] [COMMENT],
    列名2 数据类型 [约束] [COMMENT],
    ...;
)[COMMENT = 注释];

删除表:

# 删除
DROP TABLE 表名;
# 清空
TRUNCATE TABLE 表名;

添加列:

ALTER TABLE 表名
ADD 列名 数据类型 [约束] [COMMENT],
ADD 列名 数据类型 [约束] [COMMENT],
...;

修改列类型:

ALTER TABLE	表名
MODIFY 列名 数据类型 [约束] [COMMENT],
MODIFY 列名 数据类型 [约束] [COMMENT],
...;

修改列名:

ALTER TABLE 表名
CHANGE 列名 新列名 数据类型 [约束] [COMMENT],
CHANGE 列名 新列名 数据类型 [约束] [COMMENT],
...;

删除列

ALTER TABLE 表名
DROP 1,
DROP 2,
...;

18 - 使用视图

视图提供了一种封装 SELECT 语句的层次,可用来简化数据处理,重新格式化或保护基础数据。

为什么使用视图

  • 重用 SQL 语句;
  • 简化复杂的 SQL 操作。在编写查询后,可以方便的重用它而不必知道其基本查询细节。
  • 使用表的一部分而不是整个表;
  • 保护数据。可以授予用户访问表的特定部分的权限,而不是整个表的访问权限。
  • 更改数据格式和表示。视图可以返回与底层表的表示和格式不同的数据。

视图仅仅是用来查看存储在别处数据的一种设施。视图本身不包含数据,因此返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。

性能问题:每次使用视图时,都必须处理查询执行时需要的所有检索。检索过于复杂,性能可能会下降的很厉害。

视图的规则和限制

  • 视图必须唯一命名(视图与表也不能重名);
  • 视图不能索引,也不能有关联的触发器或默认值。
  • 。。。

使用视图

-- 创建视图
CREATE VIEW 视图名 AS
SELECT ...;

-- 删除视图
DROP VIEW 视图名;

视图重命名:覆盖或更新视图,必须先删除它,然后再重新创建。

示例:(利用视图简化复杂的连接)

-- 查询已订购任意产品的所有顾客的列表
SELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND Orders.order_num = OrderItems.order_num;

-- 为上面的 SQL 语句创建视图
CREATE VIEW ProductCustomers AS
SELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND Orders.order_num = OrderItems.order_num;

-- 使用视图
SELECT * FROM ProductCustomers;

-- 删除视图
DROP VIEW ProductCustomers;

从视图检索数据时如果使用了一条 WHERE 语句,则两组句子(一组在视图中,另一组是传递给视图的)将自动组合。

示例:

-- 用 ProductCustomers 视图查询订购了 RGAN01 的顾客
SELECT * FROM ProductCustomers
WHERE prod_id = 'RGAN01';

19 - 使用存储过程

一组可编程的函数,是为了完成特定功能的 SQL 语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。

优点(为什么要用存储过程?):

  1. 将重复性很高的一些操作,封装到一个存储过程中,简化了对这些SQL的调用
  2. 批量处理:SQL+循环,减少流量,也就是“跑批”
  3. 统一接口,确保数据的安全

相对于 Oracle 数据库来说,MySQL的存储过程相对功能较弱,使用较少。

—— MySQL存储过程的创建及调用

20 - 管理事务处理

使用事务处理(transaction processing),通过确保成批的 SQL 操作要么完全执行,要么完全不执行,来维护数据库的完整性。

事务处理用来管理 INSERTUPDATEDELETE 语句。不能回退 SELECT 语句(回退 SELECT 语句也没有必要)。

也不能回退 CREATEDROP 操作。事务处理中可以使用这些语句,但进行回退时,这些操作也不撤销。

具体语法应参考相应的 DBMS 文档。

21 - 使用游标

有时需要在检索出来的行中前进或后退一行或多行,这就是游标的用途所在。

游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。

具体语法应参考相应的 DBMS 文档。

22 - 高级 SQL 特性

约束

管理如何插入或处理数据库数据的规则。

  • 主键(PRIMARY KEY
  • 外键(REFERENCES
  • 唯一约束(UNIQUE
  • 检查约束(CHECK

索引

索引用来排序数据以加快搜索和排序操作的速度。

可以在一个或多个列上定义索引,使 DBMS 保存其内容的一个排过序的列表。

  • 索引改善检索操作的性能,但降低了数据插入、修改、删除的性能。在执行这些操作时,DBMS 必须动态地更新索引。
  • 索引数据可能要占用大量的存储空间。
  • 并非所有数据都适合做索引。取值不多的数据(如省)不如具有更多可能值的数据(如姓、名)能通过索引得到更大的提升。
  • 索引用于数据过滤和数据排序。如果你经常以某种特定的顺序排序数据,则该数据可能适合做索引。
  • 可能在索引中定义多个列(如州+城市)。这样索引仅在以州+城市的顺序排序时有用。如果想按城市排序,则这种索引没有用处。

触发器

触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。

触发器可以与特定表上的 INSERTUPDATEDELETE 操作(或组合)相关联。

与存储过程不一样(存储过程只是简单的存储 SQL 语句),触发器与单个的表相关联。(例如:与 Orders 表上的 INSERT 操作相关联的触发器只在 Orders 表中插入行时执行。类似的,Customers 表上的 INSERTUPDATE 操作的触发器只在表上出现这些操作时执行。)

触发器内的代码具有一下数据库的访问权:

  • INSERT 操作中的所有新数据;
  • UPDATE 操作中的所有新数据和旧数据;
  • DELETE 操作中删除的数据。

根据所使用的 DBMS 不同,触发器可在特定操作执行之前或之后执行。

触发器的常见用途:

  • 保证数据一致。(如:在 INSERTUPDATE 操作中将所有州名转换为大写)
  • 基于某个表的变动在其他表上执行活动。(如:每当更新或删除一行时将审计跟踪记录写入某个日志表)
  • 进行额外的验证并根据需要回退数据。(如:保证某个顾客的可用资金不超限定,如果已经超出,则阻塞插入)
  • 计算计算列的值或更新时间戳。

不同 DBMS 的触发器创建于法差异很大。

数据库安全

一般来说,需要保护的操作有:

  • 对数据库管理功能的访问(创建表、更改或删除已存在的表等);
  • 对特定数据库或表的访问;
  • 访问的类型(制度、对特定列的访问等);
  • 仅通过视图或存储过程对表进行访问;
  • 创建多层次的安全措施,从而允许多种基于登录的访问和控制;
  • 闲置管理用户账号的能力。