本文来自本·福达的《SQL必知必会(第5版)》的学习总结。
如果之前学习过SQL相关知识,这篇文档将很有用。
开始:
检索单个列:
SELECT prod_name FROM Products;
检索多个列:
SELECT prod_id, prod_name, prod_price FROM Products;
检索所有列:
SELECT * FROM Products
注意:除非你确实需要表中的每一列,否则最好别使用*通配符。
使用通配符有一个大优点。由于不明确指定列名(因为星号检索每一列),所以能检索出名字未知的列。
SELECT DISTINCT vend_id FROM Products;
指示数据库只返回不同的值,可用于去重。
DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列。
DISTINCT关键字是检查所有列组合的不同。
例如,你指定SELECT DISTINCT vend_id, prod_price,则指定的两列组合起来所有不同的结果都将被检测出来。
SELECT TOP 5 prod_name FROM Products;
在DB2中返回前几行:
SELECT prod_name FROM Products FETCH FIRST 5 ROWS ONLY;
在Oracle中返回前几行:
SELECT prod_name FROM Products WHERE ROWNUM <=5;
在MySQL、MariaDB、PostgreSQL或者SQLite中返回前几行
SELECT prod_name FROM Products LIMIT 5;
注意:第0行第一个被检索的行是第0行,而不是第1行。
SELECT prod_name -- 这是一条注释
FROM Products;
注释使用–(两个连字符)嵌在行内。
下面是另一种形式的行内注释(有些DBMS不支持这种形式):
# 这是一条注释
SELECT prod_name FROM Products;
多行注释,注释可以在脚本的任何位置停止和开始。
/* SELECT prod_name, vend_id FROM Products; */
SELECT prod_name
FROM Products
ORDER BY prod_name;
注意:ORDER BY子句的位置在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出错。
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price, prod_name;
ORDER BY支持按相对列位置进行排序:
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY 2, 3;
可混合使用实际列名和相对列位置。
为了进行降序排序,必须指定DESC关键字:
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC;
下面的例子以降序排序产品(最贵的在最前面),再加上产品名:
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC, prod_name;
DESC关键字只应用到直接位于其前面的列名,如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。
DESC是DESCENDING的缩写,这两个关键字都可以使用。
与DESC相对的是ASC(或ASCENDING),在升序排序时可以指定它。
SELECT prod_name, prod_price
FROM Products
WHERE prod_price = 3.49;
在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后,否则将会产生错误
SELECT prod_name, prod_price
FROM Products
WHERE prod_price < 10;
SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10;
SELECT prod_name FROM Products WHERE prod_price IS NULL;
SELECT prod_name FROM Products WHERE prod_price IS NOT NULL;
NULL无值(no value),它与字段包含0、空字符串或仅仅包含空格不是一回事。
要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件:
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
OR操作符指示DBMS检索匹配任一条件的行,第一个条件得到满足,就不再计算第二个条件:
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';
WHERE子句可以包含任意数目的AND和OR操作符。
SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符。
最好使用圆括号对操作符进行明确分组
SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ('DLL01','BRS01')
ORDER BY prod_name;
IN操作符一般比一组OR操作符执行得更快。
IN的最大优点是可以包含其他SELECT语句,能够更动态地建立WHERE子句。
NOT关键字可以用在要过滤的列前,而不仅是在其后。
大多数DBMS允许使用NOT否定任何条件。
LIKE是谓词而不是操作符
通配符(wildcard)用来匹配值的一部分的特殊字符。
搜索模式(search pattern)由字面值、通配符或两者组合构成的搜索条件。
通配符搜索只能用于文本字段(字符串),非文本数据类型字段不能使用通配符搜索。
在搜索串中,%表示任何字符出现任意次数:
SELECT prod_id, prod_name FROM Products
WHERE prod_name LIKE 'Fish%';
上面的SQL语句将检索任意以Fish起头的词。%告诉DBMS接受Fish之后的任意字符,不管它有多少字符。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '%bean bag%';
上面的SQL语句中,搜索模式’%bean bag%’表示匹配任何位置上包含文本bean bag的值,不论它之前或之后出现什么字符。
SELECT prod_name
FROM Products
WHERE prod_name LIKE 'F%y';
%还能匹配0个字符。%代表搜索模式中给定位置的0个、1个或多个字符。
通配符%不能匹配NULL。
只匹配单个字符(DB2不支持通配符):
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';
找出所有名字以J或M起头的联系人,可进行如下查询:
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%'
ORDER BY cust_contact;
可以用前缀字符^(脱字号)来否定:
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[^JM]%'
ORDER BY cust_contact;
上面的查询匹配以J和M之外的任意字符起头的任意联系人名,也可以使用NOT操作符得出类似的结果:
SELECT cust_contact
FROM Customers
WHERE NOT cust_contact LIKE '[JM]%'
ORDER BY cust_contact;
不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
在确实需要使用通配符时,也尽量不要把它们用在搜索模式的开始处。把通配符置于开始处,搜索起来是最慢的。
计算字段
在SQL语句内可完成的许多转换和格式化工作都可以直接在客户端应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户端中完成要快得多。
拼接字段
把两个列拼接起来。在SELECT语句中,可拼接两个列。根据所用的DBMS,可用加号(+)或两个竖杠(||)表示。在MySQL和MariaDB中,必须使用特殊的函数:
SELECT vend_name + '(' + vend_country + ')'
FROM Vendors ORDER BY vend_name;
SELECT vend_name || '(' || vend_country || ')'
FROM Vendors ORDER BY vend_name;
下面是使用MySQL或MariaDB时需要使用的语句:
SELECT Concat(vend_name, ' (', vend_country, ')')
FROM Vendors ORDER BY vend_name;
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
FROM Vendors
ORDER BY vend_name;
RTRIM()(去掉字符串右边的空格)、LTRIM()(去掉字符串左边的空格)以及TRIM()(去掉字符串左右两边的空格)。
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors ORDER BY vend_name;
现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。在很多DBMS中,AS关键字是可选的,不过最好使用它,这被视为一条最佳实践。
SELECT prod_id, quantity, item_price
FROM OrderItems
WHERE order_num = 20008;
汇总物品的价格(单价乘以订购数量):
SELECT prod_id, quantity, item_price,
quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
虽然SELECT通常用于从表中检索数据,但是省略了FROM子句后就是简单地访问和处理表达式,例如SELECT 3*2;将返回6,SELECT Trim(’ abc ');将返回abc,SELECT Curdate();使用Curdate()函数返回当前日期和时间。
使用函数:
SELECT vend_name, UPPER(vend_name) AS vend_name_upcase
FROM Vendors
ORDER BY vend_name;
SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。SOUNDEX考虑了类似的发音字符和音节,使得能对字符串进行发音比较而不是字母比较。
下面的语句匹配所有发音类似于Michael Green的联系名:
SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
返回:
cust_name cust_contact
Kids Place Michelle Green
SELECT order_num
FROM Orders
WHERE DATEPART(yy, order_date) = 2020;
SQL Server中不起作用,因为它不支持to_date()函数。
SELECT AVG(prod_price) AS avg_price FROM Products;
输出:
avg_price
6.823333
AVG()也可以用来确定特定列或行的平均值:
SELECT AVG(prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
获得多个列的平均值,必须使用多个AVG()函数。
SELECT COUNT(*) AS num_cust FROM Customers;
❑ 使用COUNT(column)对特定列中有值的行进行计数,忽略NULL值:
SELECT COUNT(cust_email) AS num_cust FROM Customers;
这条SELECT语句使用COUNT(cust_email)对cust_email列中有值的行进行计数。
SELECT SUM(quantity) AS items_ordered
FROM OrderItems
WHERE order_num = 20005;
也可以用来合计计算值:
SELECT SUM(item_price*quantity) AS total_price
FROM OrderItems
WHERE order_num = 20005;
SUM()函数忽略列值为NULL的行。
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
注意:如果指定列名,则DISTINCT能用于COUNT()。
DISTINCT不能用于COUNT(*)。
类似地,DISTINCT必须使用列名,不能用于计算或表达式。
SELECT COUNT(*) AS num_items,
MIN(prod_price) AS price_min,
MAX(prod_price) AS price_max,
AVG(prod_price) AS price_avg
FROM Products;
输出如下:
num_items price_min price_max price_avg
9 3.4900 11.9900 6.823333
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
❑ GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
❑ 如果在GROUP BY子句中嵌套了分组,数据将在最后指定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
❑ GROUP BY子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。
❑ 大多数SQL实现不允许GROUP BY列带有长度可变的数据类型(如文本或备注型字段)。
❑ 除聚集计算语句外,SELECT语句中的每一列都必须在GROUP BY子句中给出。
❑ 如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。❑ GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
过滤分组
除了能用GROUP BY分组数据外,SQL还允许过滤分组,规定包括哪些分组,排除哪些分组。例如,你可能想要列出至少有两个订单的所有顾客。为此,必须基于完整的分组而不是个别的行进行过滤。
HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是,WHERE过滤行,而HAVING过滤分组:
下面的例子列出具有两个以上产品且其价格大于等于4的供应商:
SELECT vend_id, COUNT(*) AS num_prods
FROM Products WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 2;
使用HAVING时应该结合GROUP BY子句,而WHERE子句用于标准的行级过滤。
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3;
按订购物品的数目排序输出,需要添加ORDER BY子句:
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3
ORDER BY items, order_num;
输出:
order_num items
20006 3
20009 3
20007 5
20008 5
SELECT cust_id
FROM Orders
WHERE order_num
IN (SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01');
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN
(SELECT cust_id
FROM Orders
WHERE order_num IN
(SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01'));
对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。
注意:只能是单列作为子查询的SELECT语句只能查询单个列。企图检索多个列将返回错误。
SELECT COUNT(*) AS orders
FROM Orders
WHERE cust_id = 1000000001;
要对每个顾客执行COUNT(*),应该将它作为一个子查询。请看下面的代码:
SELECT cust_name, cust_state,
(SELECT COUNT(*) FROM Orders WHERE Orders.cust_id = Customers.cust_id)
AS orders
FROM Customers
ORDER BY cust_name;
返回三列:cust_name、cust_state和orders。
下面的WHERE子句告诉SQL,比较Orders表中的cust_id和当前正从Customers表中检索的cust_id: WHERE Orders.cust_id = Customers.cust_id
如果在SELECT语句中操作多个表,就应使用完全限定列名来避免歧义。
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
指定的两列(prod_name和prod_price)在一个表中,而第一列(vend_name)在另一个表中
数据库表的定义中没有指示DBMS如何对表进行联结的内容。
返回笛卡儿积的联结,也称叉联结(cross join)
等值联结(equijoin),基于两个表之间的相等测试,也称为内联结(inner join)。
ANSI SQL规范首选INNER JOIN语法:
SELECT vend_name, prod_name, prod_price
FROM Vendors
INNER JOIN Products ON Vendors.vend_id = Products.vend_id;
SQL不限制一条SELECT语句中可以联结的表的数目,但许多DBMS都有限制。创建联结的基本规则也相同。不要联结不必要的表。联结的表越多,性能下降越厉害。
SELECT C.*, O.order_num, O.order_date, OI.prod_id, OI.quantity, OI.item_price
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
SELECT Customers.cust_id, Orders.order_num
FROM Customers
INNER JOIN Orders ON Customers.cust_id = Orders.cust_id;
外联结:
SELECT Customers.cust_id, Orders.order_num
FROM Customers
LEFT OUTER 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
在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)
上面的例子使用LEFT OUTER JOIN从FROM子句左边的表(Customers表)中选择所有行。
SELECT Customers.cust_id, Orders.order_num
FROM Customers
FULL OUTER JOIN Orders ON Customers.cust_id = Orders.cust_id;
SELECT Customers.cust_id, COUNT(Orders.order_num) AS num_ord
FROM Customers
INNER JOIN Orders ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
聚集函数也可以方便地与其他联结一起使用。请看下面的例子:
SELECT Customers.cust_id, COUNT(Orders.order_num) AS num_ord
FROM Customers
LEFT OUTER JOIN Orders ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
组合查询
SQL也允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
有两种情况需要使用组合查询:
❑ 在一个查询中从不同的表返回结构数据;
❑ 对一个表执行多个查询,按一个查询返回数据。
创建组合查询:给出每条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')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers WHERE cust_name = 'Fun4All';
UNION规则:
❑ 语句之间用关键字UNION分隔(因此,如果组合四条SELECT语句,将要使用三个UNION关键字)。
❑ UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过,各个列不需要以相同的次序列出)。
❑ 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)。
如果一条语句是SELECT prod_name,而另一条语句是SELECT productname,那么它会返回第一个名字,举的这个例子就会返回prod_name,而不管第二个不同的名字。这也意味着你可以对第一个名字使用别名,因而返回一个你想要的名字。
这种行为带来一个有意思的副作用。由于只使用第一个名字,那么想要排序也只能用这个名字。拿我们的例子来说,可以用ORDER BY prod_name对结果排序,如果写成ORDER BY productname就会出错,因为查询结果里没有叫作productname的列。
UNION从查询结果集中自动去除了重复的行;换句话说,它的行为与一条SELECT语句中使用多个WHERE子句条件一样。使用UNION时,重复的行会被自动取消。
如果想返回所有的匹配行,不取消重复的行,可使用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返回的结果进行排序:
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;
虽然ORDER BY子句似乎只是最后一条SELECT语句的组成部分,但实际上DBMS将用它来排序所有SELECT语句返回的所有结果。
INSERT INTO Customers
VALUES(1000000006, 'Toy Land', '123 Any Street',
'NewYork', 'NY', '11111', 'USA', NULL, NULL);
这个例子将一个新顾客插入到Customers表中。存储到表中每一列的数据在VALUES子句中给出,必须给每一列提供一个值。如果某列没有值,如上面的cust_contact和cust_email列,则应该使用NULL值(假定表允许对该列指定空值)。各列必须以它们在表定义中出现的次序填充。
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);
这样即使表的结构改变,这条INSERT语句仍然能正确工作。使用这种语法,还可以省略列,这表示可以只给某些列提供值,给其他列不提供值。
如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某个条件。
❑ 该列定义为允许NULL值(无值或空值)。
❑ 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
想把另一表中的顾客列合并到Customers表中,不需要每次读取一行再将它用INSERT插入,可以如下进行:
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;
INSERT通常只插入一行。要插入多行,必须执行多个INSERT语句。INSERT SELECT是个例外,它可以用一条INSERT插入多行,不管SELECT语句返回多少行,都将被INSERT插入。
SELECT * INTO CustCopy FROM Customers;
❑ 任何SELECT选项和子句都可以使用,包括WHERE和GROUP BY;
❑ 可利用联结从多个表插入数据;
❑ 不管从多少个表中检索数据,数据都只能插入到一个表中。
UPDATE Customers
SET cust_email = '[email protected]'
WHERE cust_id = 1000000005;
UPDATE Customers
SET cust_contact = 'Sam Roberts',
cust_email = '[email protected]'
WHERE cust_id = 1000000006;
要删除某个列的值,可设置它为NULL(假如表定义允许NULL值)
UPDATE Customers
SET cust_email = NULL
WHERE cust_id = 1000000005;
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 );
这条语句在绝大多数DBMS中有效,但对于DB2,必须从最后一列中去掉NULL。
在创建新的表时,指定的表名必须不存在,否则会出错。防止意外覆盖已有的表,SQL要求首先手工删除该表,然后再重建它,而不是简单地用创建表语句覆盖它。
NULL为默认设置,如果不指定NOT NULL,就认为指定的是NULL。
某些DBMS要求指定关键字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);
quantity列为订单中每个物品的数量。在这个例子中,这一列的描述增加了DEFAULT 1,指示DBMS,如果不给出数量则使用数量1。默认值经常用于日期或时间戳列。例如,通过指定引用系统日期的函数或变量,将系统日期用作默认日期。
获得系统日期
许多数据库开发人员喜欢使用DEFAULT值而不是NULL列,对于用于计算或数据分组的列更是如此。
ALTER TABLE Vendors ADD vend_phone CHAR(20);
下面的例子并非对所有DBMS都有效:
ALTER TABLE Vendors DROP COLUMN vend_phone;
复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
(1) 用新的列布局创建一个新表;
(2) 使用INSERT SELECT语句从旧表复制数据到新表。有必要的话,可以使用转换函数和计算字段;
(3) 检验包含所需数据的新表;
(4) 重命名旧表(如果确定,可以删除它);
(5) 用旧表原来的名字重命名新表;
(6) 根据需要,重新创建触发器、存储过程、索引和外键。
使用ALTER TABLE要极为小心,应该在进行改动前做完整的备份(表结构和数据的备份)。数据库表的更改不能撤销,如果增加了不需要的列,也许无法删除它们。类似地,如果删除了不应该删除的列,可能会丢失该列中的所有数据。
SELECT cust_name, cust_contact
FROM Customers, Orders, OrderItems
WHERE Customers.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操作。
❑ 使用表的一部分而不是整个表。
❑ 保护数据。可以授予用户访问表的特定部分的权限,而不是整个表的访问权限。
❑ 更改数据格式和表示。
❑ 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
❑ 对于可以创建的视图数目没有限制。
❑ 创建视图,必须具有足够的访问权限。这些权限通常由数据库管理人员授予。
❑ 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造视图。所允许的嵌套层数在不同的DBMS中有所不同(嵌套视图可能会严重降低查询的性能,因此在产品环境中使用之前,应该对其进行全面测试)。❑ 许多DBMS禁止在视图查询中使用ORDER BY子句。
❑ 有些DBMS要求对返回的所有列进行命名,如果列是计算字段,则需要使用别名(关于列别名的更多信息,请参阅第7课)。
❑ 视图不能索引,也不能有关联的触发器或默认值。❑ 有些DBMS把视图作为只读的查询,这表示可以从视图检索数据,但不能将数据写回底层表。详情请参阅具体的DBMS文档。
❑ 有些DBMS允许创建这样的视图,它不能进行导致行不再属于视图的插入或更新。例如有一个视图,只检索带有电子邮件地址的顾客。如果更新某个顾客,删除他的电子邮件地址,将使该顾客不再属于视图。这是默认行为,而且是允许的,但有的DBMS可能会防止这种情况发生。
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';
视图极大地简化了复杂SQL语句的使用。利用视图,可一次性编写基础的SQL,然后根据需要多次使用。
视图的另一常见用途是重新格式化检索出的数据。
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors
ORDER BY vend_name;
下面是相同的语句,但使用了||语法 :
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;
使用||语法的相同语句:
CREATE VIEW VendorLocations AS SELECT RTRIM(vend_name) || ' (' || RTRIM(vend_country) || ')' AS vend_title
FROM Vendors;
CREATE VIEW CustomerEMailList AS
SELECT cust_id, cust_name, cust_email
FROM Customers
WHERE cust_email IS NOT NULL;
现在,可以像使用其他表一样使用视图CustomerEMailList。
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;
EXECUTE AddNewProduct('JTS01', 'Stuffed Eiffel Tower', 6.49, 'Plush stuffed toy with the text La Tour Eiffel in red white and blue');
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(要传递的输出参数)。
var ReturnValue NUMBER
EXEC MailingListCount(:ReturnValue);
SELECT ReturnValue;
SQL Server版本:
CREATE PROCEDURE MailingListCount
AS
DECLARE @cnt INTEGER
SELECT @cnt = COUNT(*)
FROM Customers
WHERE NOT cust_email IS NULL;
RETURN @cnt;
此存储过程没有参数。调用程序检索SQL Server的返回代码提供的值。其中用DECLARE语句声明了一个名为@cnt的局部变量(SQL Server中所有局部变量名都以@起头);然后在SELECT语句中使用这个变量,让它包含COUNT()函数返回的值;最后,用RETURN @cnt语句将计数返回给调用程序。
调用SQL Server:
DECLARE @ReturnValue INT
EXECUTE ReturnValue=MailingListCount;
SELECT @ReturnValue;
另一个SQL Server例子:
CREATE PROCEDURE NewOrder @cust_id CHAR(10) AS
-- 为订单号声明一个变量
DECLARE @order_num INTEGER
-- 获取当前最大订单号
SELECT @order_num=MAX(order_num) FROM Orders
-- 决定下一个订单号
SELECT @order_num=@order_num+1
-- 插入新订单
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(@order_num, GETDATE(), @cust_id)
-- 返回订单号
RETURN @order_num;
CREATE PROCEDURE NewOrder @cust_id CHAR(10) AS
-- 插入新订单
INSERT INTO Orders(cust_id) VALUES(@cust_id)
-- 返回订单号
SELECT order_num = @@IDENTITY;
BEGIN TRANSACTION
...
COMMIT TRANSACTION
在这个例子中,BEGIN TRANSACTION和COMMIT TRANSACTION语句之间的SQL必须完全执行或者完全不执行。
DELETE FROM Orders;
ROLLBACK;
一般的SQL语句都是针对数据库表直接执行和编写的。这就是所谓的隐式提交(implicit commit),即提交(写或保存)操作是自动进行的。
BEGIN TRANSACTION
DELETE OrderItems WHERE order_num = 12345
DELETE Orders WHERE order_num = 12345
COMMIT TRANSACTION
SAVE TRANSACTION delete1;
ROLLBACK TRANSACTION delete1;
BEGIN TRANSACTION
INSERT INTO Customers(cust_id, cust_name)
VALUES(1000000010, 'Toys Emporium');
SAVE TRANSACTION StartOrder;
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20100,'2001/12/1',1000000010);
IF @@ERROR <> 0 ROLLBACK TRANSACTION StartOrder;
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20100, 1, 'BR01', 100, 5.49);
IF @@ERROR <> 0 ROLLBACK TRANSACTION StartOrder;
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20100, 2, 'BR03', 100, 10.99);
IF @@ERROR <> 0 ROLLBACK TRANSACTION StartOrder;
COMMIT TRANSACTION
OPEN CURSOR CustCursor;
SQL Server语法:
DECLARE @cust_id CHAR(10),
@cust_name CHAR(50),
@cust_address CHAR(50),
@cust_city CHAR(50),
@cust_state CHAR(5),
@cust_zip CHAR(10),
@cust_country CHAR(50),
@cust_contact CHAR(50),
@cust_email CHAR(255)
OPEN CustCursor
FETCH NEXT FROM CustCursor
INTO @cust_id,
@cust_name, @cust_address,
@cust_city, @cust_state, @cust_zip,
@cust_country, @cust_contact, @cust_email
...
WHILE @@FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM CustCursor
INTO @cust_id,
@cust_name, @cust_address,
@cust_city, @cust_state, @cust_zip, @cust_country, @cust_contact, @cust_email
...
END
CLOSE CustCursor
游标在使用完毕时需要关闭:
CLOSE CustCursor DEALLOCATE CURSOR CustCursor
CLOSE语句用来关闭游标。一旦游标关闭,如果不再次打开,将不能使用。第二次使用它时不需要再声明,只需用OPEN打开它即可。
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);
外键是表中的一列,其值必须列在另一表的主键中。外键是保证引用完整性的极其重要部分。
唯一约束用来保证一列(或一组列)中的数据是唯一的。
它们类似于主键,但存在以下重要区别。
❑ 表可包含多个唯一约束,但每个表只允许一个主键。
❑ 唯一约束列可包含NULL值。
❑ 唯一约束列可修改或更新。
❑ 唯一约束列的值可重复使用。
❑ 与主键不一样,唯一约束不能用来定义外键。
唯一约束既可以用UNIQUE关键字在表定义中定义,也可以用单独的CONSTRAINT定义。
检查约束用来保证一列(或一组列)中的数据满足一组指定的条件。
带限制其值为M或F(对于未知值或许还允许NULL)
索引
索引用来排序数据以加快搜索和排序操作的速度
CREATE INDEX prod_name_ind ON Products (prod_name);
CREATE TRIGGER customer_state ON Customers
FOR INSERT, UPDATE
AS
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = inserted.cust_id;
一般来说,约束的处理比触发器快,因此在可能的时候,应该尽量使用约束。