SQL语句要点一文速览

news2024/11/16 17:52:07

以下内容参考《SQL必知必会(第4版)》

了解 SQL

数据库(database):保存有组织的数据的容器(通常是一个文件或一组文件)。最简单的办法是将数据库想象为一个文件柜。这个文件柜是一个存放数据的物理位置,不管数据是什么,也不管数据是如何组织的。
表(table):某种特定类型数据的结构化清单。存储在表中的数据是同一种类型的数据或清单。决不应该将顾客的清单与订单的清单存储在同一个数据库表中,否则以后的检索和访问会很困难。应该创建两个表,每个清单一个表。
列(column):表中的一个字段。所有表都是由一个或多个列组成的。理解列的最好办法是将数据库表想象为一个网格,就像个电子表格那样。网格中每一列存储着某种特定的信息。
数据类型:所允许的数据的类型。每个表列都有相应的数据类型,它限制(或允许)该列中存储的数据。
行(row):表中的一个记录。
主键(primary key):一列(或一组列),其值能够唯一标识表中每一行。表中的任何列都可以作为主键,只要它满足以下条件:
任意两行都不具有相同的主键值;
每一行都必须具有一个主键值(主键列不允许 NULL 值);
主键列中的值不允许修改或更新;
主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)。
SQL是 Structured Query Language(结构化查询语言)的缩写。SQL 是一种专门用来与数据库沟通的语言。

检索数据

检索单个列

SELECT prod_name 
FROM Products;

利用 SELECT 语句从 Products 表中检索一个名为 prod_name的列。所需的列名写在 SELECT 关键字之后,FROM 关键字指出从哪个表中检索数据。
检索多个列:必须在 SELECT 关键字后给出多个列名,列名之间必须以逗号分隔。在选择多个列时,一定要在列名之间加上逗号,但最后一个列名后不加。

SELECT prod_id, prod_name, prod_price 
FROM Products;

检索所有列:在实际列名的位置使用星号(*)通配符可以做到这点

SELECT * 
FROM Products;

如果给定一个通配符( * ),则返回表中所有列。一般而言,除非你确实需要表中的每一列,否则最好别使用*通配符。检索不需要的列通常会降低检索和应用程序的性能。
检索不同的值:使用 DISTINCT 关键字,指示数据库只返回不同的值。

SELECT DISTINCT vend_id 
FROM Products;

SELECT DISTINCT vend_id 告诉 DBMS 只返回不同(具有唯一性)的vend_id 行。如果使用 DISTINCT 关键字,它必须直接放在列名的前面。不能部分使用 DISTINCT,DISTINCT 关键字作用于所有的列,不仅仅是跟在其后的那一列。例如,你指定 SELECT DISTINCT vend_id, prod_price,除非指定的两列完全相同,否则所有的行都会被检索出来。
限制结果: SQL Server 和 Access 中使用 SELECT 时,可以使用 TOP 关键字来限制最多返回多少行,如下所示:

SELECT TOP 5 prod_name 
FROM Products;

如果使用的是 DB2:

SELECT prod_name 
FROM Products 
FETCH FIRST 5 ROWS ONLY;

如果使用 Oracle,需要基于 ROWNUM(行计数器)来计算行:

SELECT prod_name 
FROM Products 
WHERE ROWNUM <=5;

如果使用 MySQL、MariaDB、PostgreSQL 或者 SQLite,需要使用 LIMIT子句:

SELECT prod_name 
FROM Products 
LIMIT 5;

为了得到后面的 5 行数据,需要指定从哪儿开始以及检索的行数,像这样:

SELECT prod_name 
FROM Products 
LIMIT 5 OFFSET 5;

LIMIT 5 OFFSET 5 指示 MySQL 等 DBMS 返回从第 5 行起的 5 行数据。第一个数字是指从哪儿开始,第二个数字是检索的行数。
使用注释
行内注释:

SELECT prod_name -- 这是一条注释 
FROM Products;
# 这是一条注释
SELECT prod_name 
FROM Products;

多行注释:

/* SELECT prod_name, vend_id 
FROM Products; */ 
SELECT prod_name 
FROM Products;

排序检索数据

排序数据:为了明确地排序用 SELECT 语句检索出的数据,可使用 ORDER BY 子句。ORDER BY 子句取一个或多个列的名字,据此对输出进行排序。

SELECT prod_name 
FROM Products 
ORDER BY prod_name;

按多个列排序: 经常需要按不止一个列进行数据排序。例如,如果要显示雇员名单,可能希望按姓和名排序(首先按姓排序,然后在每个姓中再按名排序)。要按多个列排序,简单指定列名,列名之间用逗号分开即可

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

仅在多个行具有相同的 prod_price 值时才对产品按 prod_name 进行排序。如果 prod_price 列中所有的值都是唯一的,则不会按 prod_name 排序。
按列位置排序: ORDER BY 还支持按相对列位置进行排序。

SELECT prod_id, prod_price, prod_name 
FROM Products 
ORDER BY 2, 3;

这一技术的主要好处在于不用重新输入列名。但它也有缺点。首先,不明确给出列名有可能造成错用列名排序。其次,在对 SELECT 清单进行更改时容易错误地对数据进行排序(忘记对 ORDER BY 子句做相应的改动)。最后,如果进行排序的列不在 SELECT 清单中,显然不能使用这项技术。
指定排序方向: 数据排序不限于升序排序(从 A 到 Z),这只是默认的排序顺序。还可以使用 ORDER BY 子句进行降序(从 Z 到 A)排序。为了进行降序排序,必须指定 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;

prod_price 列以降序排序,而 prod_name 列(在每个价格内)仍然按标准的升序排序。如果想在多个列上进行降序排序,必须对每一列指定 DESC 关键字。与 DESC相对的是 ASC(或 ASCENDING),在升序排序时可以指定它。但实际上,ASC 没有多大用处,因为升序是默认的(如果既不指定 ASC 也不指定DESC,则假定为 ASC)

过滤数据

使用 WHERE 子句: SELECT 语句中,数据根据 WHERE 子句中指定的搜索条件进行过滤。WHERE 子句在表名(FROM 子句)之后给出,如下所示:

SELECT prod_name, prod_price 
FROM Products 
WHERE prod_price = 3.49;

只返回prod_price 值为 3.49 的行。
在同时使用 ORDER BY 和 WHERE 子句时,应该让 ORDER BY 位于WHERE 之后,否则将会产生错误
WHERE 子句操作符:
在这里插入图片描述
检查单个值:
列出所有价格小于 10 美元的产品:

SELECT prod_name, prod_price 
FROM Products 
WHERE prod_price < 10;

检索所有价格小于等于 10 美元的产品:

SELECT prod_name, prod_price 
FROM Products 
WHERE prod_price <= 10;

不匹配检查: 列出所有不是供应商 DLL01 制造的产品:

SELECT vend_id, prod_name 
FROM Products 
WHERE vend_id <> 'DLL01';

单引号用来限定字符串。如果将值与字符串类型的列进行比较,就需要限定引号。用来与数值列进行比较的值不用引号。下面是相同的例子,其中使用!=而不是<>操作符:

SELECT vend_id, prod_name 
FROM Products 
WHERE vend_id != 'DLL01';

范围值检查: 要检查某个范围的值,可以使用 BETWEEN 操作符。
检索价格在 5 美元和 10美元之间的所有产品。

SELECT prod_name, prod_price 
FROM Products 
WHERE prod_price BETWEEN 5 AND 10;

空值检查: 确定值是否为 NULL,不能简单地检查是否= NULL。SELECT 语句有一个特殊的 WHERE 子句,可用来检查具有 NULL 值的列。这个 WHERE 子句就是 IS NULL 子句。其语法如下:

SELECT prod_name
FROM Products 
WHERE prod_price IS NULL;

高级数据过滤

AND操作符: 要通过不止一个列进行过滤,可以使用 AND 操作符给 WHERE 子句附加条
件。

SELECT prod_id, prod_price, prod_name
FROM Products 
WHERE vend_id = 'DLL01' AND prod_price <= 4;

检索由供应商 DLL01 制造且价格小于等于 4美元的所有产品的名称和价格。
OR操作符: OR 操作符与 AND 操作符正好相反,它指示 DBMS 检索匹配任一条件的行。

SELECT prod_name, prod_price 
FROM Products 
WHERE vend_id = 'DLL01' OR vend_id = ‘BRS01’;

OR 操作符告诉 DBMS 匹配任一条件而不是同时匹配两个条件。
求值顺序: 。假如需要列出价格为10美元及以上,且由DLL01或BRS01制造的所有产品。下面的 SELECT 语句使用组合的 AND 和 OR 操作符建立了一个 WHERE 子句:

SELECT prod_name, prod_price 
FROM Products 
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' 
 AND prod_price >= 10;

SQL在处理 OR 操作符前,优先处理 AND 操作符。当 SQL 看到上述 WHERE 子句时,它理解为:由供应商 BRS01 制造的价格为 10美元以上的所有产品,以及由供应商 DLL01 制造的所有产品,而不管其价格如何。换句话说,由于 AND 在求值过程中优先级更高,操作符被错误地组合了。此问题的解决方法是使用圆括号对操作符进行明确分组。

SELECT prod_name, prod_price 
FROM Products 
WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') 
 AND prod_price >= 10;

任何时候使用具有 AND 和 OR 操作符的 WHERE 子句,都应该使用圆括号明确地分组操作符。
IN 操作符: IN 操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN 取一组由逗号分隔、括在圆括号中的合法值。

SELECT prod_name, prod_price 
FROM Products 
WHERE vend_id IN ( 'DLL01', 'BRS01' ) 
ORDER BY prod_name;

与OR有相同功能

SELECT prod_name, prod_price 
FROM Products 
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' 
ORDER BY prod_name;

优点如下:
在有很多合法选项时,IN 操作符的语法更清楚,更直观。
在与其他 AND 和 OR 操作符组合使用 IN 时,求值顺序更容易管理。
IN 操作符一般比一组 OR 操作符执行得更快
IN 的最大优点是可以包含其他 SELECT 语句,能够更动态地建立WHERE 子句。
NOT 操作符: WHERE 子句中的 NOT 操作符有且只有一个功能,那就是否定其后所跟的任何条件。

SELECT prod_name 
FROM Products 
WHERE NOT vend_id = 'DLL01' 
ORDER BY prod_name;

列出除 DLL01 之外的所有供应商制造的产品。上面的例子也可以使用<>操作符来完成,如下所示。

SELECT prod_name 
FROM Products 
WHERE vend_id <> 'DLL01' 
ORDER BY prod_name;

用通配符进行过滤

为在搜索子句中使用通配符,必须使用 LIKE 操作符。LIKE指示 DBMS,后跟的搜索模式利用通配符匹配而不是简单的相等匹配进行比较。
百分号(%)通配符: 最常使用的通配符是百分号(%)。在搜索串中,%表示任何字符出现任意次数。例如,为了找出所有以词 Fish 起头的产品,可发布以下 SELECT 语句:

SELECT prod_id, prod_name 
FROM Products 
WHERE prod_name LIKE 'Fish%';

通配符可在搜索模式中的任意位置使用,并且可以使用多个通配符。下面的例子使用两个通配符,它们位于模式的两端:

SELECT prod_id, prod_name 
FROM Products 
WHERE prod_name LIKE '%bean bag%';

搜索模式’%bean bag%'表示匹配任何位置上包含文本 bean bag 的值,不论它之前或之后出现什么字符。
通配符也可以出现在搜索模式的中间,下面的例子找出以 F 起头、以 y 结尾的所有产品:

SELECT prod_name 
FROM Products 
WHERE prod_name LIKE 'F%y';

通配符%看起来像是可以匹配任何东西,但有个例外,这就是 NULL。子句 WHERE prod_name LIKE '%'不会匹配产品名称为 NULL 的行。
下划线(_)通配符: 下划线的用途与%一样,但它只匹配单个字符,而不是多个字符。

SELECT prod_id, prod_name 
FROM Products 
WHERE prod_name LIKE '_ _ inch teddy bear';

这个 WHERE 子句中的搜索模式给出了后面跟有文本的两个通配符。
方括号([ ])通配符: 方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。例如,找出所有名字以 J 或 M 起头的联系人,可进行如下查询:

SELECT cust_contact
FROM Customers 
WHERE cust_contact LIKE '[JM]%' 
ORDER BY cust_contact;

此语句的 WHERE 子句中的模式为’[JM]%'。这一搜索模式使用了两个不同的通配符。[JM]匹配方括号中任意一个字符,它也只能匹配单个字符。因此,任何多于一个字符的名字都不匹配。[JM]之后的%通配符匹配第一个字符之后的任意数目的字符,返回所需结果。
此通配符可以用前缀字符^(脱字号)来否定。例如,下面的查询匹配以J 和 M 之外的任意字符起头的任意联系人名(与前一个例子相反):

SELECT cust_contact 
FROM Customers 
WHERE cust_contact LIKE '[^JM]%' 
ORDER BY cust_contact;

可以使用 NOT 操作符得出类似的结果。^的唯一优点是在使用多个 WHERE 子句时可以简化语法。

SELECT cust_contact 
FROM Customers 
WHERE NOT cust_contact LIKE '[JM]%' 
ORDER BY cust_contact;

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

创建计算字段

计算字段并不实际存在于数据库表中。计算字段是运行时在 SELECT 语句内创建的。
拼接(concatenate):将值联结到一起(将一个值附加到另一个值)构成单个值。解决办法是把两个列拼接起来。在 SQL 中的 SELECT 语句中,可使用一个特殊的操作符来拼接两个列。根据你所使用的 DBMS,此操作符可用加号(+)或两个竖杠(||)表示。

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;

可以使用 SQL 的 RTRIM()函数去掉空格。大多数 DBMS 都支持 RTRIM()(去掉字符串右边的空格)、LTRIM()(去掉字符串左边的空格)以及 TRIM()(去掉字符串左右两边的空格)。

SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' 
FROM Vendors 
ORDER BY vend_name;

使用别名:别名(alias)是一个字段或值的替换名。别名用 AS 关键字赋予。

SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' 
 AS vend_title 
FROM Vendors 
ORDER BY vend_name;

计算字段的另一常见用途是对检索出的数据进行算术计算。

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

在这里插入图片描述

使用函数处理数据

常用的文本处理函数:
在这里插入图片描述
常用数值处理函数:
在这里插入图片描述
日期和时间处理函数:
日期和时间采用相应的数据类型存储在表中,每种 DBMS 都有自己的特殊形式。日期和时间值以特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。应用程序一般不使用日期和时间的存储格式,因此日期和时间函数总是用来读取、统计和处理这些值。由于这个原因,日期和时间函数在 SQL中具有重要的作用。遗憾的是,它们很不一致,可移植性最差。不同 DBMS 的日期时间处理函数可能不同。关于具体 DBMS 支持的日期时间处理函数,请参阅相应的文档

汇总数据

聚集函数: 经常需要汇总数据而不用把它们实际检索出来。这种类型的检索例子有:
 确定表中行数(或者满足某个条件或包含某个特定值的行数);
 获得表中某些行的和;
 找出表列(或所有行或某些特定的行)的最大值、最小值、平均值。
在这里插入图片描述
AVG(): 通过对表中行数计数并计算其列值之和,求得该列的平均值。AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。

SELECT AVG(prod_price) AS avg_price 
FROM Products;
SELECT AVG(prod_price) AS avg_price 
FROM Products 
WHERE vend_id = 'DLL01';

注意:只用于单个列。AVG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个 AVG()函数。AVG()函数忽略列值为 NULL 的行。
COUNT()函数: COUNT()函数进行计数。可利用 COUNT()确定表中行的数目或符合特定条件的行的数目。COUNT()函数有两种使用方式:
使用 COUNT(*)对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值。
使用 COUNT(column)对特定列中具有值的行进行计数,忽略 NULL 值。

SELECT COUNT(*) AS num_cust 
FROM Customers;
SELECT COUNT(cust_email) AS num_cust 
FROM Customers;

如果指定列名,则 COUNT()函数会忽略指定列的值为空的行,但如果COUNT()函数中用的是星号(*),则不忽略。
MAX()函数: MAX()返回指定列中的最大值。MAX()要求指定列名

SELECT MAX(prod_price) AS max_price 
FROM Products;

MAX()函数忽略列值为 NULL 的行。在用于文本数据时,MAX()返回按该列排序后的最后一行。
MIN()函数: MIN()的功能正好与 MAX()功能相反,它返回指定列的最小值。与 MAX()一样,MIN()要求指定列名

SELECT MIN(prod_price) AS min_price 
FROM Products;

MIN()函数忽略列值为 NULL 的行。在用于文本数据时,MIN()返回该列排序后最前面的行。
SUM()函数: SUM()用来返回指定列值的和(总计)

SELECT SUM(quantity) AS items_ordered 
FROM OrderItems 
WHERE order_num = 20005;

SUM()也可以用来合计计算值。在下面的例子中,合计每项物品的item_price*quantity,得出总的订单金额:

SELECT SUM(item_price*quantity) AS total_price 
FROM OrderItems 
WHERE order_num = 20005;

SUM()函数忽略列值为 NULL 的行。

以上 5 个聚集函数都可以如下使用。
 对所有行执行计算,指定 ALL 参数或不指定参数(因为 ALL 是默认行为)。
 只包含不同的值,指定 DISTINCT 参数。
下面的例子使用 AVG()函数返回特定供应商提供的产品的平均价格。它与上面的 SELECT 语句相同,但使用了 DISTINCT 参数,因此平均值只考虑各个不同的价格:

SELECT AVG(DISTINCT prod_price) AS avg_price 
FROM Products 
WHERE vend_id = 'DLL01';

如果指定列名,则 DISTINCT 只能用于 COUNT()。DISTINCT 不能用于 COUNT(*)。类似地,DISTINCT 必须使用列名,不能用于计算或表达式。

目前为止的所有聚集函数例子都只涉及单个函数。但实际上,SELECT 语句可根据需要包含多个聚集函数。请看下面的例子:

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;

分组数据

如何分组数据,以便汇总表内容的子集。这涉及两个新SELECT 语句子句:GROUP BY 子句和 HAVING 子句。
分组是使用 SELECT 语句的 GROUP BY 子句建立的。

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

上面的 SELECT 语句指定了两个列:vend_id 包含产品供应商的 ID,num_prods 为计算字段(用 COUNT(*)函数建立)。GROUP BY 子句指示DBMS 按 vend_id 排序并分组数据。这就会对每个 vend_id 而不是整个表计算 num_prods 一次。
输出:

vend_id num_prods 
------- --------- 
BRS01 3 
DLL01 4 
FNG01 2

在使用 GROUP BY 子句前,需要知道一些重要的规定。
 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 还允许过滤分组,规定包括哪些分组,排除哪些分组。WHERE过滤行,而 HAVING 过滤分组,WHERE 子句都可以用 HAVING 来替代。

SELECT cust_id, COUNT(*) AS orders 
FROM Orders 
GROUP BY cust_id 
HAVING COUNT(*) >= 2;

过滤 COUNT(*) >= 2(两个以上订单)的那些分组。
WHERE 在数据分组前进行过滤,HAVING 在数据分组后进行过滤。这是一个重要的区别,WHERE 排除的行不包括在分组中。这可能会改变计算值,从而影响 HAVING 子句中基于这些值过滤掉的分组。

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

这条语句中,第一行是使用了聚集函数的基本 SELECT 语句,WHERE 子句过滤所有 prod_price 至少为 4 的行,然后按 vend_id
分组数据,HAVING 子句过滤计数为 2 或 2 以上的分组。
使用 HAVING 时应该结合 GROUP BY 子句,而 WHERE 子句用于标准的行级过滤。
分组和排序: ORDER BY与GROUP BY的区别:
在这里插入图片描述
一般在使用 GROUP BY 子句时,应该也给出 ORDER BY 子句。这是保证数据正确排序的唯一方法。千万不要仅依赖 GROUP 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子句及其顺序:
在这里插入图片描述

使用子查询

子查询,即嵌套在其他查询中的查询。
订单存储在两个表中。每个订单包含订单编号、客户 ID、订单日期,在 Orders 表中存储为一行。各订单的物品存储在相关的
OrderItems 表中。Orders 表不存储顾客信息,只存储顾客 ID。顾客的实际信息存储在 Customers 表中。现在,假如需要列出订购物品 RGAN01 的所有顾客,应该怎样检索?下面列出具体的步骤。
(1) 检索包含物品 RGAN01 的所有订单的编号。
(2) 检索具有前一步骤列出的订单编号的所有顾客的 ID。
(3) 检索前一步骤返回的所有顾客 ID 的顾客信息。
上述每个步骤都可以单独作为一个查询来执行。可以把一条 SELECT 语句返回的结果用于另一条 SELECT 语句的 WHERE 子句。

SELECT order_num 
FROM OrderItems 
WHERE prod_id = 'RGAN01';

输出:

order_num 
----------- 
20007 
20008
SELECT cust_id 
FROM Orders 
WHERE order_num IN (20007,20008);

结合这两个查询,把第一个查询(返回订单号的那一个)变为子查询。

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

SELECT 语句中,子查询总是从内向外处理。
输出:

cust_id 
---------- 
1000000004 
1000000005

下一步是检索这些顾客ID 的顾客信息。检索两列的 SQL 语句为:

SELECT cust_name, cust_contact 
FROM Customers 
WHERE cust_id IN ('1000000004','1000000005');

结合一下

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 语句只能查询单个列。企图检索多个列将返回错误。
作为计算字段使用子查询: 假如需要显示 Customers 表中每个顾客的订单总数。订单与相应的顾客 ID 存储在 Orders 表中。
执行这个操作,要遵循下面的步骤:
(1) 从 Customers 表中检索顾客列表;
(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;

联结表

SQL 最强大的功能之一就是能在数据查询的执行中联结(join)表。

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

两列(prod_name 和 prod_price)在一个表中,第三列(vend_name)在另一个表中。没有 WHERE子句,第一个表中的每一行将与第二个表中的每一行配对,而不管它们逻辑上是否能配在一起。由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
内联结: 基于两个表之间的相等测试。这种联结也称为内联结。

SELECT vend_name, prod_name, prod_price 
FROM Vendors INNER JOIN Products 
 ON Vendors.vend_id = Products.vend_id;

联结多个表:

SELECT prod_name, vend_name, prod_price, quantity 
FROM OrderItems, Products, Vendors 
WHERE Products.vend_id = Vendors.vend_id 
 AND OrderItems.prod_id = Products.prod_id 
 AND order_num = 20007;

之前的例子:

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 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';

创建高级联结

SQL 除了可以对列名和计算字段使用别名,还允许给表名起别名。这样做有两个主要理由:
 缩短 SQL 语句;
 允许在一条 SELECT 语句中多次使用相同的表。

SELECT cust_name, cust_contact 
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';

Oracle 不支持 AS 关键字。要在 Oracle 中使用别名,可以不用 AS,简单地指定列名即可(因此,应该是 Customers C,而不是 Customers AS C)。
使用不同类型的联结: 自联结(self-join)、自然联结(natural join)和外联结(outer join)。
自联结:使用表别名的一个主要原因是能在一条 SELECT 语句中不止一次引用相同的表。
假如要给与 Jim Jones 同一公司的所有顾客发送一封信件。这个查询要求首先找出 Jim Jones 工作的公司,然后找出在该公司工作的顾客。下面是解决此问题的一种方法:

SELECT cust_id, cust_name, cust_contact 
FROM Customers 
WHERE cust_name = (SELECT cust_name 
 				   FROM Customers 
 				   WHERE cust_contact = 'Jim Jones');

现在来看使用联结的相同查询:

SELECT c1.cust_id, c1.cust_name, c1.cust_contact 
FROM Customers AS c1, Customers AS c2 
WHERE c1.cust_name = c2.cust_name 
 AND c2.cust_contact = 'Jim Jones';

自然联结:无论何时对表进行联结,应该至少有一列不止出现在一个表中(被联结的列)。标准的联结返回所有数据,相同的列甚至多次出现。自然联结排除多次出现,使每一列只返回一次。自然联结要求你只能选择那些唯一的列,一般通过对一个表使用通配符(SELECT *),而对其他表的列使用明确的子集来完成。下面举一个例子:

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 语句给出了一个简单的内联结。它检索所有顾客及其订单:

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;

在使用 OUTER JOIN 语法时,必须使用 RIGHT 或 LEFT 关键字指定包括其所有行的表(RIGHT 指出的是 OUTER JOIN 右边的表,而 LEFT 指出的是 OUTER JOIN左边的表)。上面的例子使用 LEFT OUTER JOIN 从 FROM 子句左边的表(Customers 表)中选择所有行。为了从右边的表中选择所有行,需要使用 RIGHT OUTER JOIN,还存在另一种外联结,就是全外联结(full outer join),它检索两个表中的所有行并关联那些可以关联的行。与左外联结或右外联结包含一个表的不关联的行不同,全外联结包含两个表的不关联的行。全外联结的语法如下:

SELECT Customers.cust_id, Orders.order_num 
FROM Orders FULL OUTER JOIN Customers 
 ON Orders.cust_id = Customers.cust_id;

组合查询

SQL 允许执行多个查询(多条 SELECT 语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
主要有两种情况需要使用组合查询:
 在一个查询中从不同的表返回结构数据;
 对一个表执行多个查询,按一个查询返回数据。
利用 UNION,可给出多条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';

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

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

UNION规则:
 UNION 必须由两条或两条以上的 SELECT 语句组成,语句之间用关键
字UNION分隔(因此,如果组合四条SELECT语句,将要使用三个UNION关键字)。
 UNION 中的每个查询必须包含相同的列、表达式或聚集函数(不过,各个列不需要以相同的次序列出)。
 列数据类型必须兼容:类型不必完全相同,但必须是 DBMS 可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)

UNION 从查询结果集中自动去除了重复的行,如果想返回所有的匹配行,可使用 UNION ALL 而不是 UNION。在用 UNION 组合查询时,只能使用一条 ORDER BY 子句,它必须位于最后一条 SELECT 语句之后。

插入数据

INSERT 用来将行插入(或添加)到数据库表。插入有几种方式:
 插入完整的行;
 插入行的一部分;
 插入某些查询的结果。

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

这种语法很简单,但并不安全,应该尽量避免使用。上面的 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);

INSERT 还存在另一种形式,可以利用它将 SELECT 语句的结果插入表中,这就是所谓的INSERT SELECT。顾名思义,它是由一条 INSERT 语句和一条 SELECT语句组成的
假如想把另一表中的顾客列合并到 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;

从一个表复制到另一个表: 要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用 SELECT INTO 语句。

SELECT * 
INTO CustCopy 
FROM Customers;

这条 SELECT 语句创建一个名为 CustCopy 的新表,并把 Customers 表的整个内容复制到新表中。因为这里使用的是 SELECT * ,所以将在CustCopy 表中创建(并填充)与 Customers 表的每一列相同的列。要想只复制部分的列,可以明确给出列名,而不是使用*通配符。

更新和删除数据

更新(修改)表中的数据,可以使用 UPDATE 语句。有两种使用 UPDATE的方式:
 更新表中的特定行;
 更新表中的所有行。
注意:不要省略 WHERE 子句

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';

删除数据: 从一个表中删除(去掉)数据,使用 DELETE 语句。有两种使用 DELETE的方式:
 从表中删除特定的行;
 从表中删除所有行。
注意:不要省略 WHERE 子句.稍不注意,就会错误地删除表中所有行

DELETE FROM Customers 
WHERE cust_id = '1000000006';

下面是许多 SQL 程序员使用 UPDATE 或 DELETE 时所遵循的重要原则。
 除非确实打算更新和删除每一行,否则绝对不要使用不带 WHERE 子句的 UPDATE 或 DELETE 语句。
 保证每个表都有主键,尽可能像 WHERE 子句那样使用它(可以指定各主键、多个值或值的范围)。
 在 UPDATE 或 DELETE 语句使用 WHERE 子句前,应该先用 SELECT 进行测试,保证它过滤的是正确的记录,以防编写的 WHERE 子句不正确。
 使用强制实施引用完整性的数据库,这样 DBMS 将不允许删除其数据与其他表相关联的行。
 有的 DBMS 允许数据库管理员施加约束,防止执行不带 WHERE 子句的 UPDATE 或 DELETE 语句。如果所采用的 DBMS 支持这个特性,应该使用它。

创建和操纵表

利用 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 
);

指定默认值

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 Vendors 
ADD vend_phone CHAR(20);

更改或删除列、增加约束或增加键,这些操作也使用类似的语法(注意,下面的例子并非对所有 DBMS 都有效):

ALTER TABLE Vendors 
DROP COLUMN vend_phone;

复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
(1) 用新的列布局创建一个新表;
(2) 使用 INSERT SELECT 语句从旧表复制数据到新表。有必要的话,可以使用转换函数和计算字段;
(3) 检验包含所需数据的新表;
(4) 重命名旧表(如果确定,可以删除它);
(5) 用旧表原来的名字重命名新表;
(6) 根据需要,重新创建触发器、存储过程、索引和外键。
使用 ALTER TABLE 要极为小心,应该在进行改动前做完整的备份。
删除表(删除整个表而不是其内容)非常简单,使用 DROP TABLE 语句即可:

DROP TABLE CustCopy;

重命名表:DB2、MariaDB、MySQL、Oracle 和 PostgreSQL 用户使用 RENAME语句,SQL Server 用户使用 sp_rename 存储过程,SQLite 用户使用 ALTER TABLE 语句。

使用视图

视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
为什么使用视图:
 重用 SQL 语句。
 简化复杂的 SQL 操作。在编写查询后,可以方便地重用它而不必知道其基本查询细节。
 使用表的一部分而不是整个表。
 保护数据。可以授予用户访问表的特定部分的权限,而不是整个表的访问权限。
 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
创建视图之后,可以用与表基本相同的方式使用它们。可以对视图执行SELECT 操作,过滤和排序数据,将视图联结到其他视图或表,甚至添加和更新数据(添加和更新数据存在某些限制)。视图本身不包含数据,因此返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。
下面是关于视图创建和使用的一些最常见的规则和限制。
 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)
 对于可以创建的视图数目没有限制。
 创建视图,必须具有足够的访问权限。这些权限通常由数据库管理人员授予。
 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造视图。所允许的嵌套层数在不同的 DBMS 中有所不同(嵌套视图可能会严重降低查询的性能,因此在产品环境中使用之前,应该对其进行全面测试)。
 许多 DBMS 禁止在视图查询中使用 ORDER BY 子句。
 有些 DBMS 要求对返回的所有列进行命名,如果列是计算字段,则需要使用别名。
 视图不能索引,也不能有关联的触发器或默认值。
 有些 DBMS 把视图作为只读的查询,这表示可以从视图检索数据,但不能将数据写回底层表。
 有些 DBMS 允许创建这样的视图,它不能进行导致行不再属于视图的插入或更新。例如有一个视图,只检索带有电子邮件地址的顾客。如果更新某个顾客,删除他的电子邮件地址,将使该顾客不再属于视图。这是默认行为,而且是允许的,但有的 DBMS 可能会防止这种情况发生。
视图用 CREATE VIEW 语句来创建。一个最常见的视图应用是隐藏复杂的 SQL,这通常涉及联结。删除视图,可以使用 DROP 语句,其语法为 DROP VIEW viewname;。

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;

检索订购了产品 RGAN01 的顾客,可如下进行:

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;

用视图过滤不想要的数据:可以定义CustomerEMailList 视图,过滤没有电子邮件地址的顾客。

CREATE VIEW CustomerEMailList AS 
SELECT cust_id, cust_name, cust_email 
FROM Customers 
WHERE cust_email IS NOT NULL;

使用视图与计算字段

CREATE VIEW OrderItemsExpanded AS 
SELECT order_num, 
 prod_id, 
 quantity,
 item_price, 
 quantity*item_price AS expanded_price 
FROM OrderItems;

视图为虚拟的表。它们包含的不是数据而是根据需要检索数据的查询。视图提供了一种封装 SELECT 语句的层次,可用来简化数据处理,重新格式化或保护基础数据。

使用存储过程

存储过程就是为以后使用而保存的一条或多条 SQL 语句,可将其视为批文件。
为什么要使用存储过程:
 通过把处理封装在一个易用的单元中,可以简化复杂的操作。
 由于不要求反复建立一系列处理步骤,因而保证了数据的一致性。如果所有开发人员和应用程序都使用同一存储过程,则所使用的代码都是相同的。这一点的延伸就是防止错误。需要执行的步骤越多,出错的可能性就越大。防止错误保证了数据的一致性。
 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容)有变化,那么只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。这一点的延伸就是安全性。通过存储过程限制对基础数据的访问,减少了数据讹误(无意识的或别的原因所导致的数据讹误)的机会。
 因为存储过程通常以编译过的形式存储,所以 DBMS 处理命令所需的工作量少,提高了性能。
 存在一些只能用在单个请求中的 SQL 元素和特性,存储过程可以使用它们来编写功能更强更灵活的代码。
使用存储过程有三个主要的好处,即简单、安全、高性能。
执行存储过程:

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

创建存储过程: 对邮件发送清单中具有邮件地址的顾客进行计数。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(要传递的输出参数)。
调用 Oracle 例子可以像下面这样:

var ReturnValue NUMBER 
EXEC MailingListCount(:ReturnValue); 
SELECT ReturnValue;

这段代码声明了一个变量来保存存储过程返回的任何值,然后执行存储过程,再使用 SELECT 语句显示返回的值。
下面是另一个例子,这次在 Orders 表中插入一个新订单。此程序仅适用于 SQL Server,但它说明了存储过程的某些用途和技术:

CREATE PROCEDURE NewOrder @cust_id CHAR(10) 
AS
-- Declare variable for order number 
DECLARE @order_num INTEGER 
-- Get current highest order number 
SELECT @order_num=MAX(order_num) 
FROM Orders 
-- Determine next order number 
SELECT @order_num=@order_num+1 
-- Insert new order 
INSERT INTO Orders(order_num, order_date, cust_id) 
VALUES(@order_num, GETDATE(), @cust_id) 
-- Return order number 
RETURN @order_num;

此存储过程在 Orders 表中创建一个新订单。它只有一个参数,即下订单顾客的 ID。订单号和订单日期这两列在存储过程中自动生成。代码首先声明一个局部变量来存储订单号。接着,检索当前最大订单号(使用MAX()函数)并增加 1(使用 SELECT 语句)。然后用 INSERT 语句插入由新生成的订单号、当前系统日期(用 GETDATE()函数检索)和传递的顾客 ID 组成的订单。最后,用 RETURN @order_num 返回订单号(处理订单物品需要它)。请注意,此代码加了注释,在编写存储过程时应该多加注释。

管理事务处理

使用事务处理(transaction processing),通过确保成批的 SQL 操作要么完全执行,要么完全不执行,来维护数据库的完整性。
 事务(transaction)指一组 SQL 语句;
 回退(rollback)指撤销指定 SQL 语句的过程;
 提交(commit)指将未存储的 SQL 语句结果写入数据库表;
 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),可以对它发布回退(与回退整个事务处理不同)。
管理事务的关键在于将 SQL 语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该回退。
有的 DBMS要求明确标识事务处理块的开始和结束。如在 SQL Server中,标识如下:

BEGIN TRANSACTION 
... 
COMMIT TRANSACTION

BEGIN TRANSACTION 和 COMMIT TRANSACTION 语句之间的 SQL 必须完全执行或者完全不执行。
MariaDB 和 MySQL 中等同的代码为:

START TRANSACTION 
...

Oracle 使用的语法:

SET TRANSACTION 
...

通常,COMMITT 用于保存更改,ROLLBACK 用于撤销

DELETE FROM Orders; 
ROLLBACK;

在此例子中,执行 DELETE 操作,然后用 ROLLBACK 语句撤销。

SET TRANSACTION 
DELETE OrderItems WHERE order_num = 12345; 
DELETE Orders WHERE order_num = 12345; 
COMMIT;

从系统中完全删除订单 12345。因为涉及更新两个数据库表 Orders 和 OrderItems,所以使用事务处理块来保证订单不被部分删除。最后的 COMMIT 语句仅在不出错时写出更改。如果第一条 DELETE 起作用,但第二条失败,则 DELETE 不会提交。
要支持回退部分事务,必须在事务处理块中的合适位置放置占位符。这样,如果需要回退,可以回退到某个占位符。在 SQL 中,这些占位符称为保留点。在 MariaDB、MySQL 和 Oracle 中创建占位符,可使用 SAVEPOINT 语句。

SAVEPOINT delete1;
ROLLBACK TO delete1;

下面是一个完整的 SQL Server 例子:

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

这里的事务处理块中包含了 4 条 INSERT 语句。在第一条 INSERT 语句之后定义了一个保留点,因此,如果后面的任何一个 INSERT 操作失败,事务处理能够回退到这里。在 SQL Server 中,可检查一个名为@@ERROR的变量,看操作是否成功。(其他 DBMS 使用不同的函数或变量返回此信息。)如果@@ERROR 返回一个非 0 的值,表示有错误发生,事务处理回退到保留点。如果整个事务处理成功,发布 COMMIT 以保留数据。

高级 SQL 特性

主键: 主键是一种特殊的约束,用来保证一列(或一组列)中的值是唯一的,而且永不改动。换句话说,表中的一列(或
多个列)的值唯一标识表中的每一行。
表中任意列只要满足以下条件,都可以用于主键。
 任意两行的主键值都不相同。
 每行都具有一个主键值(即列中不允许 NULL 值)。
 包含主键值的列从不修改或更新。
 主键值不能重用。如果从表中删除某一行,其主键值不分配给新行。

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 
);

给表的 vend_id 列定义添加关键字 PRIMARY KEY,使其成为主键。

ALTER TABLE Vendors 
ADD CONSTRAINT PRIMARY KEY (vend_id);

外键: 外键是表中的一列,其值必须列在另一表的主键中。
Orders 表将录入到系统的每个订单作为一行包含其中。顾客信息存储在Customers 表中。Orders 表中的订单通过顾客 ID 与 Customers 表中的特定行相关联。顾客 ID 为 Customers 表的主键,每个顾客都有唯一的ID。订单号为 Orders 表的主键,每个订单都有唯一的订单号。Orders 表中顾客 ID 列的值不一定是唯一的。如果某个顾客有多个订单,则有多个行具有相同的顾客 ID(虽然每个订单都有不同的订单号)。同时,Orders 表中顾客 ID 列的合法值为 Customers 表中顾客的 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) 
);

索引: 索引用来排序数据以加快搜索和排序操作的速度。主键数据总是排序的,这是 DBMS 的工作。因此,按主键检索特定行总是一种快速有效的操作。但是,搜索其他列中的值通常效率不高。例如,如果想搜索住在某个州的客户,怎么办?因为表数据并未按州排序,DBMS必须读出表中所有行(从第一行开始),看其是否匹配。这就像要从没有索引的书中找出词汇一样。
解决方法是使用索引。可以在一个或多个列上定义索引,使 DBMS 保存其内容的一个排过序的列表。在定义了索引后,DBMS 以使用书的索引类似的方法使用它。DBMS 搜索排过序的索引,找出匹配的位置,然后检索这些行。
在开始创建索引前,应该记住以下内容。
 索引改善检索操作的性能,但降低了数据插入、修改和删除的性能。在执行这些操作时,DBMS 必须动态地更新索引。
 索引数据可能要占用大量的存储空间。
 并非所有数据都适合做索引。取值不多的数据(如州)不如具有更多可能值的数据(如姓或名),能通过索引得到那么多的好处。
 索引用于数据过滤和数据排序。如果你经常以某种特定的顺序排序数据,则该数据可能适合做索引。
 可以在索引中定义多个列(例如,州加上城市)。这样的索引仅在以州加城市的顺序排序时有用。如果想按城市排序,则这种索引没有用处。

CREATE INDEX prod_name_ind 
ON Products (prod_name);

索引必须唯一命名。这里的索引名 prod_name_ind 在关键字 CREATE INDEX 之后定义。ON 用来指定被索引的表,而索引中包含的列(此例中仅有一列)在表名后的圆括号中给出
触发器: 触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。触发器可以与特定表上的 INSERT、UPDATE 和 DELETE 操作(或组合)相关联。与存储过程不一样(存储过程只是简单的存储 SQL 语句),触发器与单个的表相关联。
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;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/419990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】算法的时间复杂度和空间复杂度(下)(附leetcode练习题)

☃️个人主页&#xff1a;fighting小泽 &#x1f338;作者简介&#xff1a;目前正在学习C语言和数据结构 &#x1f33c;博客专栏&#xff1a;数据结构 &#x1f3f5;️欢迎关注&#xff1a;评论&#x1f44a;&#x1f3fb;点赞&#x1f44d;&#x1f3fb;留言&#x1f4aa;&…

【Linux】system V 共享内存

文章目录system V1. 共享内存原理第一阶段原理第二阶段原理2. 直接写代码--编写代码进行原理介绍shmget函数ftok函数key值用法1. 创建key值2. 创建共享内存 获取共享内存3. 将自己和共享内存关联起来4. 将自己和共享内存取消关联5. 删除共享内存用指令删除调用系统调用完整代码…

数据库管理-第六十六期 SQL Domain(20230413)

数据库管理 2023-04-13第六十六期 SQL Domain1 基本介绍2 Domain的表达式和条件3 语法4 语义5 示例总结第六十六期 SQL Domain 上一期一笔带过了部分Oracle 23c的新特性&#xff0c;这一期重点讲一下SQL Domain新特性。 【https://docs.oracle.com/en/database/oracle/oracle-…

【提升效率神器】Python简单批量生成PDF文档(详细做法)

文章目录前言一、准备二、基本使用三、批量生成PDF总结前言 日常办公中&#xff0c;经常会使用PDF文档&#xff0c;难免需要对PDF文档进行编辑&#xff0c;有时候PDF文档中的大部分内容都是一样的&#xff0c;只是发送对象不同。 这种模板套用的场景下&#xff0c;使用Python…

BI 知识大全,值得收藏的干货

01、什么是商业智能BI&#xff1f; 商业智能BI可以实现业务流程和业务数据的规范化、流程化、标准化&#xff0c;打通ERP、OA、CRM等不同业务信息系统&#xff0c;整合归纳企业数据&#xff0c;利用数据可视化满足企业不同人群对数据查询、分析和探索的需求&#xff0c;从而为…

OpenCV实例(三)答题卡识别

OpenCV实例&#xff08;三&#xff09;答题卡识别1.答题卡识别概述2.单道题目的识别2.1基本流程及原理2.2代码实例&#xff1a;作者&#xff1a;Xiou 1.答题卡识别概述 随着信息化的发展&#xff0c;计算机阅卷已经成为一种常规操作。在大型考试中&#xff0c;客观题基本不再…

重整网站。。。。。。。。。

重整网站 写好回复的人 “ xxxxxxxx”通知栏&#xff0c;并且快速跳转到需要的页面。个人页面&#xff0c;记录自己发送的消息与回复的信息。以css 上传的图片防止被拉伸拉坏。 下拉的选择下拉的分页的好处。 评论功能的那一栏中的一个小的评论&#xff0c;如果手机端的话&a…

RabbitMQ 保证消息不丢失的几种手段

文章目录1.RabbitMQ消息丢失的三种情况2.RabbitMQ消息丢失解决方案2.1 针对生产者2.1.1 方案1 &#xff1a;开启RabbitMQ事务2.1.2 方案2&#xff1a;使用confirm机制2.2 Exchange路由到队列失败2.3 RabbitMq自身问题导致的消息丢失问题解决方案2.3.1 消息持久化2.3.2 设置集群…

无废话硬核分享:Linux 基础知识点总结很详细,全的很,吐血奉献

Linux 的学习对于一个程序员的重要性是不言而喻的。前端开发相比后端开发&#xff0c;接触 Linux 机会相对较少&#xff0c;因此往往容易忽视它。但是学好它却是程序员必备修养之一。 Linux 基础 操作系统 操作系统Operating System简称OS&#xff0c;是软件的一部分&#x…

【0基础学爬虫】爬虫基础之数据存储

大数据时代&#xff0c;各行各业对数据采集的需求日益增多&#xff0c;网络爬虫的运用也更为广泛&#xff0c;越来越多的人开始学习网络爬虫这项技术&#xff0c;K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章&#xff0c;为实现从易到难全方位覆盖&#xff0c;特设【0基础学…

物联网时代的网络安全

近年来&#xff0c;物联网 (IoT) 彻底改变了我们的生活和工作方式。从智能家居到自动驾驶汽车&#xff0c;物联网设备在我们的日常生活中变得越来越普遍。 根据 Statista 的一份报告&#xff0c;到 2025 年将有超过 750 亿个物联网 (IoT) 设备投入使用。 然而&#xff0c;这…

c++之STl容器-string

目录 容器的分类 string string的概念 string的初始化 string的遍历 string的一些基本操作 char*类型和string类型互转 字符串的连接 字符串的查找和替换 string的截断和删除 容器的分类 在实际的开发过程中&#xff0c;数据结构本身的重要性不会逊于操作于数据结构的算…

SpringMVC03-文件上传、异常处理、拦截器

SpringMVC03 SpringMVC的文件上传 一 、文件上传的前端必要前提 form 表单的 entcype取值必须是&#xff1a;multipart/form-data。默认值&#xff1a;application/x-www-form-urlencoded&#xff0c;是表单请求正文的类型method 属性取值必须是 post提供一个文件选择域 二…

利用ChatGPT,一分钟制作思维导图

大家好&#xff0c;我是易安&#xff01; 今天我来教你如何使用ChatGPT&#xff0c;一分钟制作出一份思维导图 大纲选题 想到一个课题&#xff0c;然后人工梳理出内容大纲&#xff0c;是个挺费精力的事情。但利用ChatGPT来做这件事. 5秒就可以搞定啦&#xff01; 例如&#xf…

Python安全攻防之第二章Python语言基础

2.3 Python模块的安装与使用python模块的安装pip3 install 模块名称py -3 -m pip install 模块名称python模块的导入与使用&#xff08;1&#xff09;Import模块名称采用“Import模块名称”方式时&#xff0c;需要在对象前面加上模块名称作为前缀&#xff0c;具体形式为“模块名…

Nextcloud去掉URL中的index.php以及强制https(Win10子系统WSL)

一、Nextcloud去掉URL中的index.php 1、启用相关模块 cd /var/www/nextcloud #进入程序目录sudo chmod -R 777 .htaccess #设置.htaccess文件权限可读写sudo a2enmod envaudo a2enmod rewrite #启用rewrite模块2、修改nextcloud配置文件 vim /var/www/nextcloud/config/…

Redis数据备份与恢复

Redis数据备份与恢复 文章目录Redis数据备份与恢复1. Redis备份的方式2. RDB持久化2.1 什么是RDB&#xff1f;2.2 Fork操作2.3 save VS bgsave2.4 关于RDB备份的一些配置项2.5 RDB的备份与恢复2.6 RDB的自动触发2.7 RDB的优势与劣势3. AOF持久化3.1 什么是AOF&#xff1f;3.2 A…

hypothesis testing假设检验

假设检验是什么 比如一家巧克力工厂生产的巧克力每个1g&#xff0c;一个工人说&#xff0c;机器在维修之后生产的巧克力不是1g&#xff0c;为了验证工人说的是否正确&#xff0c;需进行假设检验。 随机挑选50个巧克力&#xff0c;计算平均重量。 H0&#xff1a;每个巧克力1g H…

Seatunnel-2.3.0源码解析

一、概述 SeaTunnel是一个简单易用的数据集成框架&#xff0c;在企业中&#xff0c;由于开发时间或开发部门不通用&#xff0c;往往有多个异构的、运行在不同的软硬件平台上的信息系统同时运行。数据集成是把不同来源、格式、特点性质的数据在逻辑上或物理上有机地集中&#x…

Spring学习笔记(二)【CGLIB浅拷贝BeanCopier的使用和详解】

CGLIB浅拷贝BeanCopier的使用和详解 一、bean拷贝工具 bean拷贝工具类比较 常用的bean拷贝工具类当中&#xff0c;主要有Apache提供的beanUtils、Spring提供的beanUtils、Cglib提供的beanCopier&#xff0c;性能上分析如下表所示&#xff08;该表来自网上的数据&#xff09; …