SQL必知必会笔记(下)

第14课 组合查询

组合查询

多数SQL查询只包含从一个或多个表中返回数据的单条 SELECT 语句。可是,SQL 也容许执行多个查询(多条 SELECT 语句),并将结果做为一个查询结果集返回。这些组合查询一般称为并(union)或复合查询(compound query)。程序员

主要有两种状况须要使用组合查询:sql

  • 在一个查询中从不一样的表返回结构数据
  • 对一个表执行多个查询,按一个查询返回数据

建立组合查询

能够用 UNION 操做符来组合数条 SQL 查询。数据库

使用 UNION

使用 UNION 很简单,所要作的只是给出每条 SELECT 语句,在各条语句之间放上关键字 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';
复制代码

使用多条WHERE子句而不是UNION的相同查询:服务器

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

对于较复杂的过滤条件,或者从多个表(而不是一个表)中检索数据的情形,使用 UNION 可能会使处理更简单。数据库设计

UNION 规则

  • UNION 必须由两条或两条以上的 SELECT 语句组成,语句之间用关键字 UNION 分隔(所以,若是组合四条 SELECT 语句,将要使用三个UNION关键字)。
  • UNION 中的每一个查询必须包含相同的列、表达式或汇集函数(不过,各个列不须要以相同的次序列出)。
  • 列数据类型必须兼容:类型没必要彻底相同,但必须是DBMS能够隐含转换的类型(例如,不一样的数值类型或不一样的日期类型)。

包含或取消重复的行

UNION 从查询结果集中自动去除了重复的行;换句话说,它的行为与一条 SELECT 语句中使用多个 WHERE 子句条件同样。函数

若是想返回全部的匹配行,可以使用 UNION ALL 而不是 UNION。工具

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
复制代码

UNION ALL 为 UNION 的一种形式,它完成 WHERE 子句完成不了的工做。布局

对组合查询结果排序

在用 UNION 组合查询时,只能使用一条 ORDER BY 子句,它必须位于最后一条 SELECT 语句以后。性能

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'
ORDER BY cust_name, cust_contact;
复制代码

第15课 插入数据

数据插入

INSERT 用来将行插入(或添加)到数据库表。插入有几种方式:

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

插入及系统安全:使用 INSERT 语句可能须要客户端/服务器 DBMS 中的特定安全权限。在你试图使用 INSERT 前,应该保证本身有足够的安全权限。

插入完整的行

把数据插入表中的最简单方法是使用基本的 INSERT 语法,它要求指定表名和插入到新行中的值。

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

存储到表中每一列的数据在 VALUES 子句中给出,必须给每一列提供一个值。若是某列没有值,则应该使用 NULL 值(假定表容许对该列指定空值)。各列必须以它们在表定义中出现的次序填充。

虽然这种语法很简单,但并不安全,应该尽可能避免使用。上面的 SQL 语句高度依赖于表中列的定义次序,还依赖于其容易得到的次序信息。即便能够获得这种次序信息,也不能保证各列在下一次表结构变更后保持彻底相同的次序。所以,编写依赖于特定列次序的 SQL 语句是很不安全的,这样作早晚会出问题。

编写 INSERT 语句的更安全(不过更繁琐)的方法以下:

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);
复制代码

由于提供了列名,VALUES 必须以其指定的次序匹配指定的列名,不必定按各列出如今表中的实际次序。其优势是,即便表的结构改变,这条 INSERT 语句仍然能正确工做。

给出列名的状况下,以不一样的次序填充仍然正确:

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

当心使用 VALUES:无论使用哪一种 INSERT 语法,VALUES 的数目都必须正确。若是不提供列名,则必须给每一个表列提供一个值;若是提供列名,则必须给列出的每一个列一个值。不然,就会产生一条错误消息,相应的行不能成功插入。

插入部分行

使用 INSERT 的推荐方法是明确给出表的列名。使用这种语法,还能够省略列,这表示能够只给某些列提供值,给其余列不提供值。

INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country)
VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA');
复制代码

省略的列必须知足如下条件:

  • 该列定义为容许 NULL 值(无值或空值)
  • 在表定义中给出默认值。这表示若是不给出值,将使用默认值。

插入检索出的数据

INSERT 还存在另外一种形式,能够利用它将 SELECT 语句的结果插入表中,这就是所谓的INSERT SELECT。顾名思义,它是由一条 INSERT语句和一条 SELECT语句组成的。

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;
复制代码

若是 CustNew 这个表确实有数据,则全部的数据将被插入到 Customers 表。

从一个表复制到另外一个表

有一种数据插入不使用 INSERT 语句。要将一个表的内容复制到一个全新的表(运行中建立的表),可使用 SELECT INTO 语句。

SELECT *
INTO CustCopy
FROM Customers;
复制代码

要想只复制部分的列,能够明确给出列名,而不是使用 * 通配符。

在使用SELECT INTO时,须要知道一些事情:

  • 任何SELECT选项和子句均可以使用,包括 WHERE和 GROUP BY
  • 可利用联结从多个表插入数据
  • 无论从多少个表中检索数据,数据都只能插入到一个表中

第16课 更新和删除数据

更新数据

更新(修改)表中的数据,可使用 UPDATE 语句。有两种使用 UPDATE 的方式:

  • 更新表中的特定行
  • 更新表中的全部行

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

  • 要更新的表
  • 列名和它们的新值
  • 肯定要更新那些行的过滤条件
UPDATE Customers
SET cust_email = 'kim@thetoystore.com WHERE cust_id = '1000000005'; 复制代码

不要省略 WHERE 子句:在使用 UPDATE 时必定要细心。由于稍不注意,就会更新表中的全部行。

更新更多列的语法稍有不一样:

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

要删除某个列的值,可设置它为 NULL(假如表定义容许 NULL 值)。

UPDATE Customers
SET cust_email = NULL
WHERE cust_id = '1000000005'
复制代码

其中 NULL 用来去除 cust_email 列中的值。这与保存空字符串很不一样(空字符串用''表示,是一个值),而 NULL 表示没有值。

删除数据

从一个表中删除(去掉)数据,使用 DELETE 语句。有两种使用 DELETE 的方式:

  • 从表中删除特定的行
  • 从表中删除全部行
DELETE FROM Customers
WHERE cust_id = '1000000006';
复制代码

不要省略 WHERE 子句:在使用 DELETE 时必定要细心。由于稍不注意,就会错误地删除表中全部行。

友好的外键:使用外键确保引用完整性的一个好处是,DBMS 一般能够防止删除某个关系须要用到的行。例如,要从 Products 表中删除一个产品,而这个产品用在 OrderItems 的已有订单中,那么 DELETE 语句将抛出错误并停止。这是总要定义外键的另外一个理由。

更新和删除的指导原则

若是省略了 WHERE 子句,则 UPDATE 或 DELETE 将被应用到表中全部的行。所以许多 SQL 程序员使用 UPDATE 或 DELETE 时须要遵循如下原则:

  • 除非确实打算更新和删除每一行,不然绝对不要使用不带 WHERE子句的 UPDATE 或 DELETE 语句。
  • 保证每一个表都有主键(若是忘记这个内容,请参阅第 12课),尽量像 WHERE 子句那样使用它(能够指定各主键、多个值或值的范围)。
  • 在 UPDATE 或 DELETE 语句使用 WHERE 子句前,应该先用 SELECT 进行测试,保证它过滤的是正确的记录,以防编写的 WHERE 子句不正确。
  • 使用强制实施引用完整性的数据库(关于这个内容,请参阅第12课),这样 DBMS 将不容许删除其数据与其余表相关联的行。
  • 有的 DBMS 容许数据库管理员施加约束,防止执行不带 WHERE 子句的 UPDATE 或 DELETE 语句。若是所采用的 DBMS 支持这个特性,应该使用它。

第17课 建立和操纵表

建立表

通常有两种建立表的方法:

  • 多数 DBMS 都具备交互式建立和管理数据库表的工具
  • 表也能够直接用 SQL 语句操纵 用程序建立表,可使用 SQL 的 CREATE TABLE 语句。须要注意的是,使用交互式工具时实际上就是使用 SQL 语句。这些语句不是用户编写的,界面工具会自动生成并执行相应的 SQL 语句(更改已有的表时也是这样)。

表建立基础

利用 CREATE TABLE 建立表,必须给出下列信息:

  • 新表的名字,在关键字 CREATE TABLE 以后给出
  • 表列的名字和定义,用逗号分隔
  • 有的 DBMS 还要求指定表的位置
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
);
复制代码

替换现有的表:在建立新的表时,指定的表名必须不存在,不然会出错。防止意外覆盖已有的表,SQL 要求首先手工删除该表(请参阅后面的内容),而后再重建它,而不是简单地用建立表语句覆盖它。

使用 NULL 值

在插入或更新行时,该列必须有值。每一个表列要么是 NULL列,要么是 NOT NULL 列,这种状态在建立时由表的定义规定。

CREATE TABLE Orders
(
	order_num	INTEGER		NOT NULL,
	order_date	DATETIME	NOT NULL,
	cust_id		CHAR(10)		NOT NULL
);
复制代码

这三列都须要,所以每一列的定义都含有关键字 NOT NULL。这就会阻止插入没有值的列。若是插入没有值的列,将返回错误,且插入失败。

CREATE TABLE Vendors
(
	vend_id			CHAR(10)		NOT NULL,
	vend_name		CHAR(50)	NOT NULL,
	vend_address		CHAR(50)	,
	vend_city			CHAR(50)	,
	vend_state		CHAR(5)		,
	vend_zip			CHAR(10)		,
	vend_country		CHAR(50)
);
复制代码

NULL 为默认设置,若是不指定 NOT NULL,就认为指定的是 NULL。

主键和 NULL 值:主键是其值惟一标识表中每一行的列。只有不容许 NULL 值的列可做为主键,容许 NULL 值的列不能做为惟一标识。

指定默认值

CREATE TABLE OrderItems
(
	order_num		INTEGER			NOT NULL,
	order_item		INTEGER			NOT NULL,
	prod_id			CHAR(10)			NOT NULL,
	quantity			INTEGER			NOT NULL		DEFAULT 1,
	item_price		DECIMAL(8, 2)		NOT NULL
);
复制代码

默认值常常用于日期或时间戳列。

更新表

更新表定义,可使用 ALTER TABLE 语句。如下是使用 ALTER TABLE 时须要考虑的事情。

  • 理想状况下,不要在表中包含数据时对其进行更新。应该在表的设计过程当中充分考虑将来可能的需求,避免从此对表的结构作大改动
  • 全部的 DBMS 都容许给现有的表增长列,不过对所增长列的数据类型(以及 NULL 和 DEFAULT 的使用)有所限制
  • 许多 DBMS 不容许删除或更改表中的列
  • 多数 DBMS 容许从新命名表中的列
  • 许多 DBMS 限制对已经填有数据的列进行更改,对未填有数据的列几乎没有限制

使用 ALTER TABLE 更改表结构,必须给出下面的信息:

  • 在 ALTER TABLE 以后给出要更改的表名(该表必须存在,不然将出错)
  • 列出要作哪些更改
ALTER TABLE Vendors
ADD vend_phone CHAR(20);
复制代码

更改或删除列、增长约束或增长键,这些操做也使用相似的语法:

ALTER TABLE Vendors
DROP COLUMN vend_phone;
复制代码

复杂的表结构更改通常须要手动删除过程,它涉及如下步骤:

(1) 用新的列布局建立一个新表; (2) 使用 INSERT SELECT 语句(关于这条语句的详细介绍,请参阅第 15课)从旧表复制数据到新表。有必要的话,可使用转换函数和计算字段; (3) 检验包含所需数据的新表; (4) 重命名旧表(若是肯定,能够删除它); (5) 用旧表原来的名字重命名新表; (6) 根据须要,从新建立触发器、存储过程、索引和外键。

当心使用 ALTER TABLE:使用 ALTER TABLE 要极为当心,应该在进行改动前作完整的备份(表结构和数据的备份)。数据库表的更改不能撤销,若是增长了不须要的列,也许没法删除它们。相似地,若是删除了不该该删除的列,可能会丢失该列中的全部数据。

删除表

DROP TABLE CustCopy;
复制代码

删除表没有确认,也不能撤销,执行这条语句将永久删除该表。

使用关系规则防止意外删除:许多 DBMS 容许强制实施有关规则,防止删除与其余表相关联的表。在实施这些规则时,若是对某个表发布一条 DROP TABLE 语句,且该表是某个关系的组成部分,则DBMS将阻止这条语句执行,直到该关系被删除为止。若是容许,应该启用这些选项,它能防止意外删除有用的表。

重命名表

全部重命名操做的基本语法都要求指定旧表名和新表名。不过,存在 DBMS 实现差别。关于具体的语法,请参阅相应的 DBMS 文档。

第18课 使用视图

视图

视图是虚拟的表。与包含数据的表不同,视图只包含使用时动态检索 数据的查询。

用下面的 SELECT 语句从三个表中检索数据:

SELECT cust_name, cust_contact
FROM Customers, Orders, OrderItems
WHERE Customer.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num
AND prod_id = 'RGAN01';
复制代码

如今,假如能够把整个查询包装成一个名为 ProductCustomers 的虚拟表,则能够以下轻松地检索出相同的数据:

SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
复制代码

这就是视图的做用。ProductCustomers 是一个视图,它不包含任何列或数据,包含的是一个查询(与上面用以正确联结表的查询相同)。

为何使用视图

下面是视图的一些常见应用:

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

建立视图以后,能够用与表基本相同的方式使用它们。能够对视图执行 SELECT 操做,过滤和排序数据,将视图联结到其余视图或表,甚至添加和更新数据(添加和更新数据存在某些限制,关于这个内容稍后作介绍)。

重要的是,要知道视图仅仅是用来查看存储在别处数据的一种设施。视图自己不包含数据,所以返回的数据是从其余表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。

性能问题:由于视图不包含数据,因此每次使用视图时,都必须处理查询执行时须要的全部检索。若是你用多个联结和过滤建立了复杂的视图或者嵌套了视图,性能可能会降低得很厉害。所以,在部署使用了大量视图的应用前,应该进行测试。

视图的规则和限制

关于视图建立和使用的一些最多见的规则和限制:

  • 与表同样,视图必须惟一命名(不能给视图取与别的视图或表相同的名字)
  • 对于能够建立的视图数目没有限制。
  • 建立视图,必须具备足够的访问权限。这些权限一般由数据库管理人员授予。
  • 视图能够嵌套,便可以利用从其余视图中检索数据的查询来构造视图。所容许的嵌套层数在不一样的DBMS中有所不一样(嵌套视图可能会严重下降查询的性能,所以在产品环境中使用以前,应该对其进行全面测试)。
  • 许多 DBMS 禁止在视图查询中使用 ORDER BY 子句。
  • 有些 DBMS 要求对返回的全部列进行命名,若是列是计算字段,则须要使用别名(关于列别名的更多信息,请参阅第7课)。
  • 视图不能索引,也不能有关联的触发器或默认值。
  • 有些 DBMS 把视图做为只读的查询,这表示能够从视图检索数据,但不能将数据写回底层表。详情请参阅具体的 DBMS 文档。
  • 有些 DBMS 容许建立这样的视图,它不能进行致使行再也不属于视图的插入或更新。例若有一个视图,只检索带有电子邮件地址的顾客。若是更新某个顾客,删除他的电子邮件地址,将使该顾客再也不属于视图。这是默认行为,并且是容许的,但有的 DBMS 可能会防止这种状况发生。

建立视图

视图用 CREATE VIEW 语句来建立。删除视图能够用 DROP VIEW 。

利用视图简化复杂的联结

CREATE VIEW ProductCustomers AS
SELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num;
复制代码

在以上视图中进行检索:

SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
复制代码

用视图从新格式化检索出的数据

SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors
ORDER BY vend_name;
复制代码

把此语句转换为视图:

CREATE VIEW VendorLocations AS
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors;
复制代码

再检索数据:

SELECT *
FROM VendorLocations;
复制代码

用视图过滤不想要的数据

CREATE VIEW CustomerEmailList AS
SELECT cust_id, cust_name, cust_email
FROM Customers
WHERE cust_email IS NOT NULL;
复制代码

再检索数据:

SELECT *
FROM CustomerEMailList;
复制代码

使用视图和计算字段

SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
复制代码

将以上查询转成视图:

CREATE VIEW OrderItemsExpanded AS 
SELECT order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM OrderItems;
复制代码

再检索数据:

SELECT *
FROM OrderItemsExpanded
WHERE order_num = 20008;
复制代码

第19课 使用存储过程

存储过程

简单来讲,存储过程就是为之后使用而保存的一条或多条 SQL 语句。可将其视为批文件,虽然它们的做用不只限于批处理。

为何要使用存储过程

理由不少,下面给出一些主要的:

  • 经过把处理封装在一个易用的单元中,能够简化复杂的操做(如前面例子所述)。
  • 因为不要求反复创建一系列处理步骤,于是保证了数据的一致性。若是全部开发人员和应用程序都使用同一存储过程,则所使用的代码都是相同的。这一点的延伸就是防止错误。须要执行的步骤越多,出错的可能性就越大。防止错误保证了数据的一致性。
  • 简化对变更的管理。若是表名、列名或业务逻辑(或别的内容)有变化,那么只须要更改存储过程的代码。使用它的人员甚至不须要知道这些变化。 这一点的延伸就是安全性。经过存储过程限制对基础数据的访问,减小了数据讹误(无心识的或别的缘由所致使的数据讹误)的机会。
  • 由于存储过程一般以编译过的形式存储,因此 DBMS 处理命令所需的工做量少,提升了性能。
  • 存在一些只能用在单个请求中的 SQL 元素和特性,存储过程可使用它们来编写功能更强更灵活的代码。

换句话说,使用存储过程有三个主要的好处,即简单、安全、高性能。

执行存储过程

存储过程的执行远比编写要频繁得多,所以咱们先介绍存储过程的执行。执行存储过程的 SQL 语句很简单,即 EXECUTE。EXECUTE 接受存储过程名和须要传递给它的任何参数。

EXECUTE AddNewProduct('JTS01', 'Stuffed Eiffel Tower', 6.49, 'Plush stuffed toy with the text La Tour Eiffel in red white and blue'0;
复制代码

这里执行一个名为 AddNewProduct 的存储过程,将一个新产品添加到 Products 表中。AddNewProduct 有四个参数,分别是:供应商 ID(Vendors 表的主键)、产品名、价格和描述。这 4个参数匹配存储过程当中4个预期变量(定义为存储过程自身的组成部分)。此存储过程将新行添加到 Products 表,并将传入的属性赋给相应的列。

在 Products表中还有另外一个须要值的列 prod_id列,它是这个表的主键。为何这个值不做为属性传递给存储过程?要保证恰当地生成此 ID,最好是使生成此 ID 的过程自动化(而不是依赖于最终用户的输入)。

如下是存储过程所完成的工做:

  • 验证传递的数据,保证全部4个参数都有值;
  • 生成用做主键的惟一ID;
  • 将新产品插入 Products 表,在合适的列中存储生成的主键和传递的数据。

建立存储过程

Oracle 版本:

CREATE PROCEDURE MailingListCount (
	ListCount OUT INTEGER
)
IS
v_rows INTEGER;
BEGIN
	SELECT COUNT(*) INTO v_rows
	FROM Customers
	WHERE NOT cust_email IS NULL;
	ListCount := v_rows;
END;
复制代码

这个存储过程有一个名为 ListCount 的参数。此参数从存储过程返回一个值而不是传递一个值给存储过程。关键字 OUT 用来指示这种行为。Oracle支持 IN(传递值给存储过程)、OUT(从存储过程返回值,如这里)、INOUT (既传递值给存储过程也从存储过程传回值)类型的参数。存储过程的代码括在 BEGIN 和 END 语句中,这里执行一条简单的 SELECT 语句,它检索具备邮件地址的顾客。而后用检索出的行数设置 ListCount(要传递的输出参数)。

第20课 管理事务处理

事务处理

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

关系数据库把数据存储在多个表中,使数据更容易操纵、维护和重用。不用深究如何以及为何进行关系数据库设计,在某种程度上说,设计良好的数据库模式都是关联的。

事务处理是一种机制,用来管理必须成批执行的 SQL 操做,保证数据库不包含不完整的操做结果。利用事务处理,能够保证一组操做不会中途中止,它们要么彻底执行,要么彻底不执行(除非明确指示)。若是没有错误发生,整组语句提交给(写到)数据库表;若是发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态。

下面是关于事务处理须要知道的几个术语:

  • 事务(transaction)指一组 SQL 语句;
  • 回退(rollback)指撤销指定 SQL 语句的过程;
  • 提交(commit)指将未存储的 SQL 语句结果写入数据库表;
  • 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),能够对它发布回退(与回退整个事务处理不一样)。

能够回退哪些语句:事务处理用来管理 INSERT、UPDATE 和 DELETE 语句。不能回退 SELECT 语句(回退 SELECT 语句也没有必要),也不能回退 CREATE 或 DROP 操做。

控制事务处理

管理事务的关键在于将 SQL 语句组分解为逻辑块,并明确规定数据什么时候应该回退,什么时候不该该回退。

MySQL 中的标识:

START TRANSACTION
...
复制代码

事务一直存在,直到被中断。一般,COMMIT 用于保存更改,ROLLBACK 用于撤销,详述以下。

使用 ROLLBACK

DELETE FROM Orders;
ROLLBACK;
复制代码

在此例子中,执行 DELETE 操做,而后用 ROLLBACK 语句撤销。虽然这不是最有用的例子,但它的确可以说明,在事务处理块中,DELETE 操做(与 INSERT 和 UPDATE 操做同样)并非最终的结果。

使用 COMMIT

通常的 SQL 语句都是针对数据库表直接执行和编写的。这就是所谓的隐式提交(implicit commit),即提交(写或保存)操做是自动进行的。

在事务处理块中,提交不会隐式进行。不过,不一样 DBMS 的作法有所不一样。有的 DBMS 按隐式提交处理事务端,有的则不这样。

进行明确的提交,使用COMMIT语句。

Oracle 示例:

SET TRANSACTION
DELETE OrderItems WHERE order_num = 12345;
DELETE OrderItems WHERE order_num = 12345;
COMMIT;
复制代码

使用保留点

使用简单的 ROLLBACK 和 COMMIT 语句,就能够写入或撤销整个事务。可是,只对简单的事务才能这样作,复杂的事务可能须要部分提交或回退。

要支持回退部分事务,必须在事务处理块中的合适位置放置占位符。这样,若是须要回退,能够回退到某个占位符。

MySQL 和 Oracle 示例:

ROLLBACK TO delete1;
复制代码

第21课 使用游标

游标

有时,须要在检索出来的行中前进或后退一行或多行,这就是游标的用途所在。游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了游标以后,应用程序能够根据须要滚动或浏览其中的数据。

不一样的 DBMS 支持不一样的游标选项和特性。常见的一些选项和特性以下:

  • 可以标记游标为只读,使数据能读取,但不能更新和删除
  • 能控制能够执行的定向操做(向前、向后、第1、最后、绝对位置、相对位置等)
  • 能标记某些列为可编辑的,某些列为不可编辑的。
  • 规定范围,使游标对建立它的特定请求(如存储过程)或对全部请求可访问。
  • 指示 DBMS 对检索出的数据(而不是指出表中活动数据)进行复制,使数据在游标打开和访问期间不变化。

使用游标

使用游标有几个明确的步骤:

  • 在使用游标前,必须声明(定义)它。这个过程实际上没有检索数据,它只是定义要使用的SELECT语句和游标选项。
  • 一旦声明,就必须打开游标以供使用。这个过程用前面定义的 SELECT 语句把数据实际检索出来。
  • 对于填有数据的游标,根据须要取出(检索)各行。
  • 在结束游标使用时,必须关闭游标,可能的话,释放游标(有赖于具体的DBMS)。

建立游标

使用 DECLARE 语句建立游标,这条语句在不一样的 DBMS 中有所不一样。DECLARE 命名游标,并定义相应的 SELECT 语句,根据须要带 WHERE 和其余子句。

DECLARE CustCursor CURSOR
FOR
SELECT * FROM Customers
WHERE cust_email IS NULL;
复制代码

使用游标

OPEN CURSOR CustCursor;
复制代码

如今能够用 FETCH 语句访问游标数据了。FETCH 指出要检索哪些行,从何处检索它们以及将它们放于何处(如变量名)。

使用 Oracle 语法从游标中检索一行:

DECLARE TYPE CustCursor IS REF CURSOR RETURN Customers%ROWTYPE
DECLARE CustRecord Customers%ROWTYPE;
BEGIN
	OPEN CustCursor;
	FETCH CustCursor INTO CustRecord;
	CLOSE CustCursor;
END;
复制代码

关闭游标

CLOSE 语句用来关闭游标。一旦游标关闭,若是再也不次打开,将不能使 用。第二次使用它时不须要再声明,只需用 OPEN 打开它便可。

CLOSE CustCursor
复制代码

第22课 高级SQL特性

约束

约束(constraint):管理如何插入或处理数据库数据的规则。

主键

主键是一种特殊的约束,用来 一组列)中的值是惟一的,并且永不改动。换句话说,表中的一列(或 多个列)的值惟一标识表中的每一行。这方便了直接或交互地处理表中的行。没有主键,要安全地 UPDATE 或 DELETE 特定行而不影响其余行会 很是困难。

表中任意列只要知足如下条件,均可以用于主键。

  • 任意两行的主键值都不相同。
  • 每行都具备一个主键值(即列中不容许 NULL 值)。
  • 包含主键值的列从不修改或更新。(大多数 DBMS 不容许这么作,但 若是你使用的 DBMS 容许这样作,好吧,千万别!)
  • 主键值不能重用。若是从表中删除某一行,其主键值不分配给新行。

一种定义主键的方法是建立它:

CREATE TABLE Vendors
(
	vend_id			CHAR(10)		NOT NULL PRIMARY KEY,
	vend_name		CHAR(50)	NOT NULL,
	vend_address		CHAR(50)	NULL,
	vend_city			CHAR(50)	NULL,
	vend_state		CHAR(5)		NULL,
	vend_zip			CHAR(10)		NULL,
	vend_country		CHAR(50)	NULL
);
复制代码

另外一种方法:

ALTER TABLE Vendors
ADD CONSTRAINT PRIMARY KEY (vend_id);
复制代码

外键

外键是表中的一列,其值必须列在另外一表的主键中。外键是保证引用完 整性的极其重要部分。

定义外键的方法:

CREATE TABLE Orders
(
	order_num	INTEGER		NOT NULL PRIMARY KEY,
	order_date	DATETIME	NOT NULL,
	cust_id		CHAR(10)		NOT NULL REFERENCES Customers(cust_id)
);
复制代码

也能够用 CONSTRAINT 来完成:

ALTER TABLE Orders
ADD CONSTRAINT
FOREIGN KEY (cust_id) REFERENCES Customers (cust_id)
复制代码

外键有助防止意外删除:除帮助保证引用完整性外,外键还有另外一个重要做用。在定义外键后,DBMS 不容许删除在另外一个表中具备关联行的行。

惟一约束

惟一约束用来保证一列(或一组列)中的数据是惟一的。它们相似于主 键,但存在如下重要区别。

  • 表可包含多个惟一约束,但每一个表只容许一个主键。
  • 惟一约束列可包含 NULL 值。
  • 惟一约束列可修改或更新。
  • 惟一约束列的值可重复使用。
  • 与主键不同,惟一约束不能用来定义外键。

惟一约束的语法相似于其余约束的语法。惟一约束既能够用 UNIQUE 关 键字在表定义中定义,也能够用单独的 CONSTRAINT 定义。

检查约束

检查约束用来保证一列(或一组列)中的数据知足一组指定的条件。检 查约束的常见用途有如下几点:

  • 检查最小或最大值。例如,防止 0 个物品的订单(即便 0 是合法的数)。
  • 指定范围。例如,保证发货日期大于等于今天的日期,但不超过今天 起一年后的日期。
  • 只容许特定的值。例如,在性别字段中只容许 M 或 F 。

检查 约束在数据类型内又作了进一步的限制,这些限制极其重要,能够确保插 入数据库的数据正是你想要的数据。不须要依赖于客户端应用程序或用户 来保证正确获取它,DBMS 自己将会拒绝任何无效的数据。

施加检查约束:

CREATE TABLE OrderItems
(
	order_num	INTEGER		NOT NULL,
	order_item	INTEGER		NOT NULL,
	prod_id		CHAR(10)		NOT NULL,
	quantity		INTEGER		NOT NULL CHECK (quantity > 0),
	item_price	MONEY		NOT NULL
);
复制代码

检查名为 gender 的列只包含 M 或 F,可编写以下的 ALTER TABLE 语句:

ADD CONSTRAINT CHECK (gender LIKE '[MF]')
复制代码

索引

索引用来排序数据以加快搜索和排序操做的速度。想像一本书后的索引 (如本书后的索引),能够帮助你理解数据库的索引。

假如要找出本书中全部的“数据类型”这个词,简单的办法是从第 1 页 开始,浏览每一行。虽然这样作能够完成任务,但显然不是一种好的办法。浏览少数几页文字可能还行,但以这种方式浏览整部书就不可行了。 随着要搜索的页数不断增长,找出所需词汇的时间也会增长。

这就是书籍要有索引的缘由。索引按字母顺序列出词汇及其在书中的位 置。为了搜索“数据类型”一词,可在索引中找出该词,肯定它出如今 哪些页中。而后再翻到这些页,找出“数据类型”一词。

使索引有用的因素是什么?很简单,就是恰当的排序。找出书中词汇的 困难不在于必须进行多少搜索,而在于书的内容没有按词汇排序。若是 书的内容像字典同样排序,则索引没有必要(所以字典就没有索引)。

数据库索引的做用也同样。主键数据老是排序的,这是 DBMS 的工做。 所以,按主键检索特定行老是一种快速有效的操做。

可是,搜索其余列中的值一般效率不高。例如,若是想搜索住在某个州的客户,怎么办?由于表数据并未按州排序,DBMS 必须读出表中全部行(从第一行开始),看其是否匹配。这就像要从没有索引的书中找出词汇同样。

解决方法是使用索引。能够在一个或多个列上定义索引,使 DBMS 保存 其内容的一个排过序的列表。在定义了索引后,DBMS 以使用书的索引相似的方法使用它。DBMS 搜索排过序的索引,找出匹配的位置,而后检索这些行。

在开始建立索引前,应该记住如下内容:

  • 索引改善检索操做的性能,但下降了数据插入、修改和删除的性能。 在执行这些操做时, DBMS 必须动态地更新索引。
  • 索引数据可能要占用大量的存储空间。
  • 并不是全部数据都适合作索引。取值很少的数据(如州)不如具备更多可能值的数据(如姓或名),能经过索引获得那么多的好处。
  • 索引用于数据过滤和数据排序。若是你常常以某种特定的顺序排序数据,则该数据可能适合作索引。
  • 能够在索引中定义多个列(例如,州加上城市)。这样的索引仅在以州加城市的顺序排序时有用。若是想按城市排序,则这种索引没有用处。

没有严格的规则要求什么应该索引,什么时候索引。大多数 DBMS 提供了可 用来肯定索引效率的实用程序,应该常用这些实用程序。

索引用 CREATE INDEX 语句建立:

CREATE INDEX prod_name_ind
ON Products (prod_name);
复制代码

索引必须惟一命名。

触发器

触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。触发 器能够与特定表上的 INSERT、UPDATE 和 DELETE 操做(或组合)相关联。

与存储过程不同(存储过程只是简单的存储 SQL 语句),触发器与单个的表相关联。与 Orders 表上的 INSERT 操做相关联的触发器只在 Orders 表中插入行时执行。 相似地, Customers 表上的 INSERT 和 UPDATE 操做的触发器只在 Customers 表上出现这些操做时执行。

触发器内的代码具备如下数据的访问权:

  • INSERT 操做中的全部新数据;
  • UPDATE 操做中的全部新数据和旧数据;
  • DELETE 操做中删除的数据。

下面是触发器的一些常见用途:

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

不一样 DBMS 的触发器建立语法差别很大,更详细的信息请参阅相应的文档。

Oracle 版本:

CREATE TRIGGER customer_state
AFTER INSERT OR UPDATE
FOR EACH ROW
BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = :OLD.cust_id
END;
复制代码

数据库安全

对于组织来讲,没有什么比它的数据更重要了,所以应该保护这些数据, 使其不被偷盗或任意浏览。固然,数据也必须容许须要访问它的用户访 问,所以大多数 DBMS 都给管理员提供了管理机制,利用管理机制授予 或限制对数据的访问。

任何安全系统的基础都是用户受权和身份确认。这是一种处理,经过这 种处理对用户进行确认,保证他是有权用户,容许执行他要执行的操做。 有的 DBMS 为此结合使用了操做系统的安全措施,而有的维护本身的用户及密码列表,还有一些结合使用外部目录服务服务器。

通常说来,须要保护的操做有:

  • 对数据库管理功能(建立表、更改或删除已存在的表等)的访问;
  • 对特定数据库或表的访问;
  • 访问的类型(只读、对特定列的访问等);
  • 仅经过视图或存储过程对表进行访问;
  • 建立多层次的安全措施,从而容许多种基于登陆的访问和控制;
  • 限制管理用户帐号的能力。
相关文章
相关标签/搜索