sql教程

SQL 入门
第 1 章 关系数据库和 SQL
第 2 章 组织数据
· 表 · 模式
· 视图· 数据类型

第 3 章 创建表和视图

· 创建表 · 删除数据
· 插入数据 · 创建视图
· 更改数据· 使用视图来处理数据
第 4 章 使用 SQL 语句存取数据
· 连接数据库· 从多个表中选择数据
· 调查错误· 使用子查询
· 选择列· 使用函数
· 选择行· 列函数
· 将行进行排序· 标量函数
· 除去重复行· 分组
· 运算次序· 将 WHERE 子句与 GROUP BY 子句一起使用
· 使用表达式来计算值· 在 GROUP BY 子句之后使用 HAVING 子句
· 给表达式命名 
第 5 章 表达式和子查询
· 标量全查询· 公共表表达式
· 转换数据类型· 相关名
· 条件表达式· 相关子查询
· 表表达式· 实现相关子查询
· 嵌套表表达式 
第 6 章 在查询中使用运算符和谓词
· 用集合运算符组合查询· 使用 IN 谓词
· UNION 运算符· 使用 BETWEEN 谓词
· EXCEPT 运算符· 使用 LIKE 谓词
· INTERSECT 运算符· 使用 EXISTS 谓词
· 谓词· 定量谓词
第 7 章 高级 SQL
· 用约束和触发器实施商业规则· 触发器
· 关键字· 连接
· 唯一约束· 复杂查询
· 参考完整性约束· ROLLUP 和 CUBE 查询
· 表检查约束· 递归查询
第 8 章 定制和增强数据操作
· 用户定义类型·操作大对象(LOB)
·用户定义函数·专用寄存器
·大对象 (LOB) 

第 1 章 关系数据库和 SQL 返回

关系数据库中,数据存储在表中。表是行和列的集合。 结构化查询语言(SQL)通过指定列、表以及它们之间的各种关系来检索或更新数据。

SQL 是在关系数据库中定义和处理数据的标准化语言。SQL 语句由数据库管理程序执行。数据库管理程序是管理数据的计算机程序。

分区关系数据库是在多个分区(也称为节点)上管理数据的关系数据库。本书中我们将把重点集中在单个分区数据库上。

可以使用一个象命令行处理器(CLP)或命令中心那样的界面,通过交互式 SQL 来存取样本数据库并试验本书中的所有示例。

第 2 章 组织数据 返回

本章给出了表、视图以及模式的重要概念说明。该说明是一个高层次概述,显示了关系数据库不同构件之间的连接。最后一节简要讨论了某些重要的和较常用的数据类型。

· 表

表是由定义的列数和可变的行数组成的逻辑结构。列是一组相同数据类型的值。在表中不必对行进行排序。要对结果集进行排序,必须在从表中选择数据的 SQL 语句中显式指定排序。在每个列和行的相交处是一个称为值的特定数据项。在图 1中,'Sanders' 是表中值的一个示例。

基表是用 CREATE TABLE 语句创建的,用于保存用户数据。结果表是一组行,数据库管理程序从一个或多个基表选择或生成这组行以满足查询要求。

图 1说明了表的一部分。列和行已标记。

tb1.gif

· 视图

视图提供了在一个或多个表中查看数据的替代方法。它是表上的一个动态窗口。

视图允许多个用户查看同一数据的不同表示。例如,几个用户可以同时存取一个关于雇员的数据表。一个用户可以查看关于某些雇员而非其他雇员的数据,而另一个用户可以查看关于所有雇员的某些数据而非他们的薪水。这些用户的每一个都在操作一个从该实表派生的视图。每个视图都显示为一个表并有自己的名称。

使用视图的优点是您可以使用它们来控制对敏感数据的存取。所以,不同的人可以存取数据的不同列或行。

· 模式

模式是命名对象的集合,并提供了数据库中对象的逻辑分类。模式可以包含数据库对象,如表和视图等。

模式本身也可以认为是数据库中的一个对象。当创建表或视图时隐式创建了模式。或者,可以使用 CREATE SCHEMA 语句显式创建它。

创建对象时,可以用特定模式的名称来限定对象的名称。命名对象有两部分名称,其中第一部分名称是指定给对象的模式名。如果未指定模式名,则给对象指定其名称是用户执行语句的权限 ID 的缺省模式。对于交互式 SQL,该方法用于执行本书中的示例,权限 ID 为用 CONNECT 语句指定的用户 ID。例如,如果表名为 STAFF,CONNECT 语句中指定的用户 ID 为 USERID,则限定名为 USERID.STAFF。参见连接数据库以获取关于 CONNECT 语句的详情。

某些模式名是系统保留的。例如,当预安装的用户定义函数 属于 SYSFUN 模式时,内部函数处于 SYSIBM 模式。参考 SQL Reference 以获取关于 CREATE SCHEMA 语句的详情。

· 数据类型

数据类型定义常数、列、宿主变量、函数、表达式以及专用寄存器可接受的值。本节描述示例中引用的数据类型。有关其他数据类型的完整列表和说明,参考 SQL Reference。

字符串

字符串为一个字节序列。字符串的长度为序列中的字节数。如果长度为零,则该字符串的值称为空字符串。

定长字符串

CHAR(x)是定长字符串。长度属性 x 必须在 1 和 254 之间,并包括 1 和 254。

变长字符串

变长字符串有三种类型:VARCHAR、LONG VARCHAR 以及 CLOB。 VARCHAR(x)类型是变长字符串,因此,可以将长度为 9 的字符串插入 VARCHAR(15)中,而该字符串的长度将仍然为 9。参见大对象 (LOB)以获取关于 CLOB 的详情。

图形字符串

图形字符串是一个双字节字符数据序列。

定长图形字符串

GRAPHIC(x)是定长字符串。长度属性 x 必须在 1 和 127 之间,并包括 1 和 127。

变长图形字符串

变长图形字符串有三种类型:VARGRAPHIC、LONG VARGRAPHIC 以及 DBCLOB。参见大对象 (LOB)以获取关于 DBCLOB 的详情。

二进制字符串

二进制字符串是一个字节序列。它用于保存非传统数据,如图象等。“二进制大对象”(BLOB)是二进制字符串。参见大对象 (LOB)以了解更多信息。

数字

所有数字都有符号和精度。精度是除符号位以外的位数或数字数。

SMALLINT

SMALLINT(小型整数)是精度为 5 位的两字节整数。

INTEGER

INTEGER(大型整数)是精度为 10 位的四字节整数。

REAL

REAL(单精度浮点数)是实数的 32 位近似值。

DOUBLE

DOUBLE(双精度浮点数)是实数的 64 位近似值。 DOUBLE 也称 FLOAT。

DECIMAL(p,s)

DECIMAL 是一个十进制数。小数点的位置由数字的 精度(p)和小数位(s) 确定。精度是数字的总位数,必须小于 32。小数位是小数部分数字的位数且总是小于或等于精度值。如果未指定精度和小数位,则十进制值的缺省精度为 5,缺省小数位为 0。

日期时间值

日期时间值是日期、时间以及时间戳记的表示。日期时间值可以用于某些算术运算和字符串运算并且与某些字符串是相容的,然而它们既非字符串也非数字。

日期

日期值分为三个部分(年、月以及日)。

时间

时间是用 24 小时制式来指定一天内的时间的值,分为三个部分(小时、分钟以及秒)。

时间戳记

时间戳记为指定日期和时间的值,分为七个部分(年、月、日、小时、分钟、秒以及微秒)。

空值是一个区别于所有非空值的特殊值。它意味着行中的那一列无任何其他值。所有数据类型都存在空值。

下表突出显示示例中所使用的数据类型的特性。所有数字数据类型都定义在某一确定范围内。该数字数据类型范围也包括在此表中。可以使用此表作为正确数据类型用法的快速参考。

数据类型类型特性示例或范围
CHAR(15)定长字符串最大长度为 254'Sunny day '
VARCHAR(15)变长字符  最大长度为 4000 'Sunny day'
SMALLINT数字 长度为2字节 精度为5位范围为-32768 至 32767
INTEGER数字长度为 4 字节 精度为 10 位范围为-2147483648 至 2147483647
REAL数字单精度浮点32 位近似值

范围为-3.402E+38 至-1.175E-37 或

1.175E-37 至-3.402E+38或零

DOUBLE数字双精度浮点 64 位近似值范围为-1.79769E+308 至-2.225E-307
或 2.225E-307 至 1.79769E+308或零
DECIMAL(5,2)数字精度为 5 小数位为 2范围为 -10**31+1 至 10**31-1
DATE日期时间三部分值1991-10-27
TIME日期时间
三部分值13.30.05
TIMESTAMP日期时间七部分值1991-10-27-13.30.05.000000

有关更多信息,参见 SQL Reference 中的数据类型相容性表。

本书中我们沿用日期时间值的 ISO 表示。

第 3 章 创建表和视图 返回

本章描述如何才能在 DB2 通用数据库中创建和操作表和视图。通过图表和示例来探索表和视图的关系。

本章包括:

· "创建表"

使用 CREATE TABLE 语句创建自己的表,指定列名和类型以及约束。约束在用约束和触发器实施商业规则中讨论。

下列语句创建一个命名为 PERS 的表,该表与 STAFF 表类似,但有一个附加列 BIRTH_DATE 。

CREATE TABLE PERS ( ID SMALLINT NOT NULL, NAME VARCHAR(9),
        DEPT SMALLINT WITH DEFAULT 10, JOB CHAR(5),
        YEARS SMALLINT, SALARY DECIMAL(7,2), COMM DECIMAL(7,2), BIRTH_DATE DATE)
此语句创建一个其中无数据的表。下一节描述如何将数据插入新表。

如示例中所示,为每一列都指定名称和数据类型。数据类型在数据类型中讨论。 NOT NULL 是可选的,可以指定它以表示列中不允许有空值。缺省值也是可选的。

可以在 CREATE TABLE 语句中指定许多其他选项,如唯一约束或参考约束。有关所有选项的详情,参见 SQL Reference 中的 CREATE TABLE 语句。

· 插入数据

当创建新表时,新表不包含任何数据。要将新的行输入表中,使用 INSERT 语句。此语句有两种一般格式:

· 一种格式,使用 VALUES 子句来指定一行或多行的列值。下面三个示例使用此一般格式将数据插入表中。

· 另一种格式,指定全查询而非指定 VALUES 来标识来自包含在其他表和/或视图中的行的列。
全查询是 INSERT 或 CREATE VIEW 语句中所使用的选择语句、或者是跟在谓词后面的选择语句。括在括号中的全查询一般称为子查询。

根据创建表时已选择的缺省选项,对于每个插入的行,为每一列提供一个值或者接受一个缺省值。各种数据类型的缺省值在 SQL Reference 中讨论。

下列语句使用 VALUES 子句将一行数据插入 PERS 表中:


INSERT INTO PERS
VALUES (12, 'Harris', 20, 'Sales', 5, 18000, 1000, '1950-1-1')

下列语句使用 VALUES 子句将三行插入其中只有 ID、名称以及工作是已知的 PERS 表中。如果列定义为 NOT NULL 且没有缺省值,则必须为该列指定一个值。 CREATE TABLE 语句中的列定义上的 NOT NULL 子句可以用单词 WITH DEFAULT 扩充。如果某一列定义为 NOT NULL WITH DEFAULT 或常数缺省值(如 WITH DEFAULT 10),并且您未在列列表中指定该列,则缺省值插入至已插入行的该列中。例如,在 CREATE TABLE 语句中,仅为 DEPT 列指定了缺省值并将该值定义为 10。因此,DEPT 设置为 10 而所有其他列都为空。


INSERT INTO PERS (NAME, JOB, ID) VALUES ('Swagerman', 'Prgmr', 500), ('Limoges', 'Prgmr', 510), ('Li', 'Prgmr', 520)
下列语句返回插入的结果:

SELECT * FROM PERS

IDNAMEDEPTJOBYEARSSALARYCOMMBIRTH_DATE
12Harris20Sales518000.001000.0001/01/1950
500Swagerman10Prgmr    
510Limoges10Prgmr    
520Li10Prgmr    

注意:在此情况下,并未给每个列指定值。空值显示为 -。为此,列名列表的次序和数据类型都必须与 VALUES 子句中提供的值对应。如果省略列名列表(如第一个示例中那样),则 VALUES 之后的数据值列表的次序必须与它们所插入至的表中的列次序相同,值的数目必须等于表中列的数目。

每个值必须与它所插入至的列的数据类型相容。如果某列定义为可空,且未指定该列的值,则将空值赋给插入行中的该列。

下列示例将空值插入 YEARS、COMM 和 BIRTH_DATE 中,因为未给行中的那些列指定值。

INSERT INTO PERS (ID, NAME, JOB, DEPT, SALARY) VALUES (410, 'Perna', 'Sales', 20, 20000)
INSERT 语句的第二种格式对于用来自另一表中行的值填充表非常方便。如所述的那样,指定全查询而非指定 VALUES 以标识来自包含在其他表和/或视图中的行中的列。

下列示例从员工 STAFF 表中选择部门 38 的成员的数据,并将它插入 PERS 表中:


INSERT INTO PERS (ID, NAME, DEPT, JOB, YEARS, SALARY)
SELECT ID, NAME, DEPT, JOB, YEARS, SALARY FROM STAFF WHERE DEPT = 38
在此插入之后,下列 SELECT 语句与 INSERT 语句中全查询产生的结果相同。

SELECT ID, NAME, DEPT, JOB, YEARS, SALARY FROM PERS WHERE DEPT = 38

结果为:

IDNAMEDEPTJOBYEARSSALARY
30Marenghi38Mgr 517506.75
40O'Brien38Sales6 18006.00
60Quigley38Sales 16808.30
120Naughton38 Clerk 12954.75
180Abrahams38Clerk312009.75

· 更改数据

使用 UPDATE 语句来更改表中的数据。使用此语句,可以更改满足 WHERE 子句搜索条件的每行中的一列或多列的值。

下列示例更新其 ID 为 410 的雇员的信息:

UPDATE PERS SET JOB='Prgmr', SALARY = SALARY + 300 WHERE ID = 410
SET 子句指定要更新的列并提供值。

WHERE 子句是可选的,它指定要更新的行。如果省略 WHERE 子句,则数据库管理程序用您提供的值更新表或视图中的每一行。

在此示例中,首先命名表 (PERS),然后指定要更新行的条件。雇员编号 410 的信息已更改:该雇员的工作职位更改为 Prgmr,它的薪水增加了 300$。

可以通过包括应用于两行或更多行的 WHERE 子句来更改多行数据。下列示例给每个销售员的薪水增加 15%:

UPDATE PERS SET SALARY = SALARY * 1.15 WHERE JOB = 'Sales'

· 删除数据

使用 DELETE 语句,基于在 WHERE 子句中指定的搜索条件从表中删除数据行。下列示例删除其中雇员 ID 为 120 的行:

DELETE FROM PERS WHERE ID = 120
WHERE 子句是可选的,它指定要删除的行。如果省略 WHERE 子句,则数据库管理程序删除表或视图中的所有行。

可以使用 DELETE 语句删除多行。下列示例删除其中雇员部门 (DEPT) 为 20 的所有行:

DELETE FROM PERS WHERE DEPT = 20
当删除某一行时,是除去整行,而不是除去行中的特定列值。

要删除表的定义及其内容,发出 DROP TABLE 语句,如 SQL Reference 中所述。

·"创建视图"
如视图中所讨论的,视图提供在一个或多个表中查看数据的替代方法。通过创建视图,可以对想要各种用户查看的信息进行限制。下列图表显示视图和表之间的关系。

tb2.gif

在图 2中,View_A 被限制仅存取 TABLE_A 的列 AC1 和 AC2。 View_AB 允许存取 TABLE_A 中的列 AC3 和 TABLE_B 中的列 BC2。通过创建 View_A,将用户可以具有的存取权限制于 TABLE_A,通过创建 VIEW_AB,将存取权限制于某些列并创建查看数据的替代方式。

下列语句创建 STAFF 表 中 20 部门的非经理人员视图,其中薪水和佣金不通过基表显示。

CREATE VIEW STAFF_ONLY
AS SELECT ID, NAME, DEPT, JOB, YEARS
FROM STAFF WHERE JOB <> 'Mgr' AND DEPT=20
在创建视图之后,下列语句显示视图的内容:

SELECT * FROM STAFF_ONLY
此语句产生下列结果:

IDNAMEDEPTJOBYEARS
20Pernal20Sales8
80James20Clerk 
190Sneider20Clerk8

早些时候,我们把 STAFF 和 ORG 表连接起来产生一个列出每个部门名称及其部门经理姓名的结果。下列语句创建可重复用于相同目的的视图:

CREATE VIEW DEPARTMENT_MGRS AS SELECT NAME, DEPTNAME
FROM STAFF, ORG WHERE MANAGER = ID
创建视图时,可以使用 WITH CHECK OPTION 子句将附加约束添加到通过视图插入和更新表。此子句导致数据库管理程序验证对视图的任何更新或插入是否符合该视图的定义,并拒绝那些不符合定义的更新或插入。如果省略此子句,则不检查违反视图定义的插入和更新。有关 WITH CHECK OPTION 如何起作用的详情,参考 SQL Reference 中的 CREATE VIEW 语句。

· 使用视图来处理数据

象 SELECT 语句一样,INSERT、DELETE 以及 UPDATE 语句可以应用于视图,就好象视图是一个实表一样。这些语句处理基本基表中的数据。因此当再次存取该视图时,使用最新的基表对它进行计算。如果未使用 WITH CHECK OPTION,则使用视图修改的数据可能由于不再满足原始视图定义而不在视图的重复存取中出现。

下列是一个将更新应用于视图 FIXED_INCOME 的示例:

FIXED_INCOME 的视图定义:
CREATE VIEW FIXED_INCOME (LNAME, DEPART, JOBTITLE, NEWSALARY)
AS SELECT NAME, DEPT, JOB, SALARY FROM PERS WHERE JOB <> 'Sales' WITH CHECK OPTION
UPDATE FIXED_INCOME SET NEWSALARY = 19000 WHERE LNAME = 'Li'
除了校验选项以外,先前视图中的更新等效于对基表 PERS 的更新:

UPDATE PERS SET SALARY = SALARY * 1.10 WHERE NAME = 'Li' AND JOB <> 'Sales'
注意:由于视图是在 CREATE VIEW FIXED_INCOME 中对约束 JOB <> 'Sales'使用 WITH CHECK OPTION 创建的,所以当 Limoges 调去做销售时不允许下列更新:

UPDATE FIXED_INCOME SET JOBTITLE = 'Sales' WHERE LNAME = 'Limoges'
不能更新由表达式 SALARY + COMM or SALARY * 1.25 定义的列。如果定义的视图包含一列或多个这样的列,则拥有者不接受对这些列的更新(UPDATE)特权。在包含这样的列的视图上不允许 INSERT 语句,但允许 DELETE 语句。

考虑一个没有一列定义为 NOT NULL 的 PERS 表。可以通过 FIXED_INCOME 视图将行插入 PERS 表中,即使该视图不包含基本表 PERS 的 ID、YEARS、COMM 或 BIRTHDATE。整个视图中看不到的列被适当地设置为空值或缺省值。

然而,PERS 表确实已将列 ID 定义为 NOT NULL。如果尝试通过 FIXED_INCOME 视图插入行,则系统试图将空值插入在整个视图中“看不到”的所有 PERS 列。由于 ID 列未包括在视图中并且该列不允许空值,所以系统不允许通过该视图进行插入。

有关修改视图的规则和限制,参考 SQL Reference 中的 CREATE VIEW 语句。

第 4 章 使用 SQL 语句存取数据 返回

本节讲述如何使用 SQL 语句来连接数据库和检索数据。

在示例中,我们给出了要输入的语句,大多数情况下后面跟着将显示在对样本数据库发出该语句时所产生的结果。注意:虽然用大写字母显示语句,但也可以用大小写字母混合来输入这些语句(用单引号(') 或引号('')括起来的语句除外)。

DB2 通用数据库包括的 SAMPLE 数据库由几个表组成,这些表在“附录 A”中列出。

取决于数据库是如何建立的,可能需要通过用模式名和一个句点给使用的表名加前缀来限定这些表名。对于本书中的示例,假定缺省模式为 USERID。所以可以将表 ORG 当作 USERID.ORG。询问管理员这是否是必要的。

· 连接数据库

您必须先与数据库连接,才能使用 SQL 语句来查询或操作该数据库。CONNECT 语句使数据库连接与用户名相关联。

例如,要连接 SAMPLE 数据库,在 DB2 命令行处理器中输入下列命令:

CONNECT TO SAMPLE USER USERID USING PASSWORD
(确保选择服务器系统上有效的 USER 和 USING 值)。

在此示例中,USER 的值为 USERID,USING 的值为 PASSWORD。

下列信息告诉您连接成功:

数据库连接信息

数据库产品 = DB2/6000 6.0.0
SQL 权限 ID = USERID
本地数据库别名 = SAMPLE

通过 CONNECT 语句设置连接时,建立显式连接。在隐式连接中已设置缺省服务器。在此情况下可以使用 CONNECT,或开始发出语句,将自动建立连接。

一旦连接上,就可以开始操作数据库。有关隐式和显式连接的详情,参考 SQL Reference 中的 CONNECT 语句。

· 调查错误

无论何时在任何示例中输入出错时,或者如果执行 SQL 语句期间出错,则数据库管理程序返回错误信息。错误信息由信息标识符、简要说明以及 SQLSTATE 组成。

SQLSTATE 是 DB2 系列产品的公共错误码。 SQLSTATE 符合 ISO/ANSI SQL92 标准。

例如,如果 CONNECT 语句中用户名或口令不正确,则数据库管理程序将返回信息标识符 SQL1403N 和 SQLSTATE 为 08004。该信息如下:

SQL1403N 提供的用户名和/或口令不正确。 SQLSTATE=08004
可以通过输入一个问号(?),然后输入信息标识符或 SQLSTATE 来获取关于错误信息的更多信息:

? SQL1403N

? SQL1403

? 08004
注意:错误 SQL1403N 的说明中倒数第二行表明 SQLCODE为-1403。 SQLCODE 为产品特定错误码。以 N(通知)或 C(严重)结尾的信息标识符表示一个错误,并且具有负 SQLCODE。以 W(警告)结尾的信息标识符表示一个警告,并且具有正 SQLCODE。

· 选择列

使用 SELECT 语句从表中选择特定的列。在该语句中指定用逗号分隔的列名列表。此列表称为选择列表。

下列语句从 SAMPLE 数据库的 ORG 表中选择部门名称 (DEPTNAME) 和部门号 (DEPTNUMB):

SELECT DEPTNAME, DEPTNUMB FROM ORG
上面语句产生下列结果:

DEPTNAMEDEPTNUMB
Head Office10
New England15
Mid Atlantic20
South Atlantic38
Great Lakes42
Plains 51
Pacific66
Mountain84

通过使用星号 (*) 可从表中选择所有列。下一个示例列出 ORG 表中的所有的列和行:

SELECT * FROM ORG
此语句产生下列结果:

DEPTNUMBDEPTNAMEMANAGERDIVISIONLOCATION
10Head Office160CorporateNew York
15New England50EasternBoston
20Mid Atlantic10EasternWashington
38South Atlantic30EasternAtlanta
42Great Lakes100MidwestChicago
51Plains140MidwestDallas
66Pacific270WesternSan Francisco
84Mountain290WesternDenver

· 选择行

要从表中选择特定行,在 SELECT 语句之后使用 WHERE 子句指定要选择的行必须满足的条件。从表中选择行的标准是搜索条件。

搜索条件由一个或多个谓词组成。谓词指定关于某一行是真或是假(或未知)的条件。可使用下列基本谓词在 WHERE 子句中指定条件:

 

谓词           功能

x = y         x 等于 y

x <> y       x 不等于 y

x < y         x 小于 y

x > y         x 大于 y

x <= y      x 小于或等于 y

x >= y       x 大于或等于 y

IS NULL/IS NOT NULL 测试空值

在构造搜索条件时,要注意只对数字数据类型执行算术运算,并只在相容数据类型之间进行比较。例如,不能将字符串与数字进行比较。

如果正在基于字符值来选择行,则该值必须用单引号括起来(例如,WHERE JOB = 'Clerk'),并且输入的每个字符值必须与数据库中的完全一样。如果数据值在数据库中是小写的,而您用大写形式来输入它,则将不选择行。如果正在基于数字值来选择行,则该值不得用引号括起来(例如,WHERE DEPT = 20)。

下列示例只从 STAFF 表中选择部门 20 的行:

SELECT DEPT, NAME, JOB FROM STAFF WHERE DEPT = 20
此语句产生下列结果:

DEPT      NAME          JOB
------ --------- -----
20          Sanders     Mgr
20          Pernal       Sales
20          James         Clerk
20          Sneider     Clerk
下一示例使用 AND 来指定多个条件。可以指定任意多个条件。该示例从 STAFF 表中选择部门 20 中的 clerk:

SELECT DEPT, NAME, JOB FROM STAFF WHERE JOB = 'Clerk' AND DEPT = 20
此语句产生下列结果:

DEPT       NAME          JOB
------ --------- -----
20         James          Clerk
20         Sneider      Clerk

未在其中输入值且不支持缺省值的列中出现空值。将值特别设置为空值的地方也可以出现空值。空值只能在定义为支持空值的列中出现。在表中定义和支持空值在创建表中讨论。

使用谓词 IS NULL 和 IS NOT NULL 来检查空值。

下列语句列出佣金未知的雇员:

SELECT ID, NAME FROM STAFF WHERE COMM IS NULL
此语句产生下列结果:

ID          NAME
------ ---------
10         Sanders
30         Marenghi
50         Hanes
100       Plotz
140       Fraye
160       Molinare
210       Lu
240       Daniels
260       Jones
270       Lea
290       Quill

值零与空值不相同。下列语句选择表中佣金为零的每个人:

SELECT ID, NAME FROM STAFF WHERE COMM = 0

因为样本表中的 COMM 列中没有零值,所以返回的结果集为空。

下一个示例选择 STAFF 表中 YEARS 的值大于 9 的所有行:

SELECT NAME, SALARY, YEARS FROM STAFF WHERE YEARS > 9

此语句产生下列结果:

NAME           SALARY        YEARS
--------- --------- ------
Hanes        20659.80      10
Lu               20010.00      10
Jones         21234.00      12
Quill         19818.00      10
Graham       21000.00      13

· 将行进行排序

您可能想要信息按特定次序返回。使用 ORDER BY 子句将信息按一个或多个列中的值进行排序。

下列语句显示部门 84 中按雇用年数排序的雇员:

SELECT NAME, JOB, YEARS FROM STAFF WHERE DEPT = 84 ORDER BY YEARS 此语句产生下列结果:

NAME            JOB      YEARS
--------- ----- ------
Davis          Sales     5
Gafney        Clerk     5
Edwards      Sales     7
Quill          Mgr        10
指定 ORDER BY 作为整个 SELECT 语句中的最后一个子句。在此子句中命名的列可以是表达式或表的任何列。ORDER BY 子句中的列名不必在选择列表中指定。

可通过在 ORDER BY 子句中显式指定 ASC 或 DESC 将行按升序或降序进行排序。如果既未指定 ASC,也未指定 DESC,则自动按升序将行进行排序。下列语句按雇用年数以降序显示部门 84 中的雇员:

SELECT NAME, JOB, YEARS FROM STAFF WHERE DEPT = 84 ORDER BY YEARS DESC
此语句产生下列结果:

NAME            JOB     YEARS
--------- ----- ------
Quill          Mgr     10
Edwards      Sales 7
Davis          Sales 5
Gafney        Clerk 5
可以按字符值以及数字值将行进行排序。下列语句按姓名字母顺序显示部门 84 的雇员:

SELECT NAME, JOB, YEARS FROM STAFF WHERE DEPT = 84 ORDER BY NAME 此语句产生下列结果:

NAME            JOB     YEARS
--------- ----- ------
Davis         Sales    5
Edwards     Sales    7
Gafney       Clerk    5
Quill         Mgr      10

· 除去重复行

当使用 SELECT 语句时,您可能不想要返回重复信息。例如,STAFF 有一个其中多次列出了几个部门编号的 DEPT 列,以及一个其中多次列出了几个工作说明的 JOB 列。

要消除重复行,在 SELECT 子句上使用 DISTINCT 选项。例如,如果将 DISTINCT 插入该语句,则部门中的每项工作仅列出一次:

SELECT DISTINCT DEPT, JOB FROM STAFF WHERE DEPT < 30 ORDER BY DEPT, JOB 此语句产生下列结果:

DEPT      JOB
------ -----
10          Mgr
15          Clerk
15          Mgr
15         Sales
20         Clerk
20         Mgr
20         Sales
DISTINCT 已消除了在 SELECT 语句中指定的一组列中所有包含重复数据的行

· 运算次序

考虑运算次序是很重要的。一个子句的输出是下一个子句的输入,如下面列表中所示。给表达式命名中给出一个要考虑其中运算次序的示例。

并且注意,此说明允许以一种更直观的方式对查询进行考虑。此说明不一定是在内部执行运算的方式。运算顺序如下:

1. FROM 子句         2. WHERE 子句    3. GROUP BY 子句    4. HAVING 子句    5. SELECT 子句

· 使用表达式来计算值

表达式是包括在语句中的计算或函数。下列语句计算,如果部门 38 中每个雇员都收到 $500 的奖金,则每人的薪水将是多少:

SELECT DEPT, NAME, SALARY + 500 FROM STAFF WHERE DEPT = 38 ORDER BY 3 此结果为:

DEPT       NAME                3
------ --------- ----------------
38           Abrahams     12509.75
38           Naughton     13454.75
38           Quigley       17308.30
38           Marenghi     18006.75
38           O'Brien       18506.00
注意第三列的列名是一个数字。这是一个系统生成的数字,因为 SALARY+500 未指定列名。以后此数字在 ORDER BY 子句中用来表示第三列。给表达式命名论及如何给表达式取有意义的名称。

可使用基本算术运算符加(+)、减(-)、乘(*)、除(/)来形成算术表达式。

这些运算符可对几种不同类型操作数的值进行运算,其中某些操作数为:

列名(例如在 RATE*HOURS 中)
常数值(例如在 RATE*1.07 中)
标量函数(例如在 LENGTH(NAME) + 1 中)。

· 给表达式命名

可选的 AS 子句允许您给表达式指定有意义的名称,这就使得以后再引用该表达式更容易。可使用 AS 子句为选择列表中的任何项提供名称。

下列语句显示其薪水加佣金少于 $13,000 的所有雇员。表达式 SALARY + COMM 命名为 PAY:

SELECT NAME, JOB, SALARY + COMM AS PAY FROM STAFF WHERE (SALARY + COMM) < 13000 ORDER BY PAY
此语句产生下列结果:

NAME           JOB      PAY
--------- ----- ----------
Yamaguchi  Clerk 10581.50
Burke          Clerk 11043.50
Scoutten    Clerk 11592.80
Abrahams    Clerk 12246.25
Kermisch    Clerk 12368.60
Ngan            Clerk 12714.80
通过使用 AS 子句,可以在 ORDER BY 子句中引用特定的列名而不是系统生成的数字。在此示例中,我们在 WHERE 子句中将(SALARY + COMM)与 13000 进行比较,而不是使用名称 PAY。这是运算次序的结果。在将(SALARY + COMM)命名为 PAY 之前计算 WHERE 子句的值。因此,不能在该谓词中使用 PAY。

· 从多个表中选择数据

可使用 SELECT 语句从两个或多个表中生成包含信息的报告。这通常称为 连接。例如,可以连接 STAFF 和 ORG 表中的数据以形成一个新表。要连接两个表,在 SELECT 子句中指定想要显示的列,在 FROM 子句中指定表名,在 WHERE 子句中指定搜索条件。WHERE 子句是可选的。

下一个示例使每个经理的姓名与部门名称关联。需要从两个表中选择信息,因为雇员信息(STAFF 表)和部门信息(ORG 表)是独立存储的。下列查询分别选择 STAFF 和 ORG 表的 NAME 和 DEPTNAME 列。搜索条件将选择范围缩小为 MANAGER 列中的值与 ID 列中的值相同的行:

SELECT DEPTNAME, NAME FROM ORG, STAFF WHERE MANAGER = ID
图 3演示如何比较两个不同表中的列。加框线的值表示满足搜索条件的匹配项。

tb3.gif

SELECT 语句产生下列结果:

DEPTNAME              NAME
-------------- ---------
Mid Atlantic       Sanders
South Atlantic   Marenghi
New England         Hanes
Great Lakes         Plotz
Plains                   Fraye
Head Office         Molinare
Pacific                 Lea
Mountain               Quill
该结果列出每个经理的姓名和他或她的部门。

· 使用子查询

在编写 SELECT 语句时,可在 WHERE 子句中放置另一个 SELECT 语句。每个附加的 SELECT 启动一个子查询。

子查询本身又可包括其值代入其 WHERE 子句的另一个子查询。另外,WHERE 子句可将子查询包括在多个搜索条件中。子查询可引用与主查询中所使用的不同的表和列。

下列语句从 ORG 表中选择 STAFF 表中其 ID 为 280 的雇员的分部和位置:

SELECT DIVISION, LOCATION FROM ORG WHERE DEPTNUMB = (SELECT DEPT FROM STAFF WHERE ID = 280)
在处理此语句时,DB2 首先确定子查询的结果。结果为 66,因为具有 ID 280 的雇员在部门 66。则最终结果从其部门号列具有值 66 的 ORG 表的行中得出。最终结果是:

DIVISION           LOCATION
----------      -------------
Western San     Francisco
当使用子查询时,数据库管理程序计算该子查询并将结果值直接代入 WHERE 子句。

在相关子查询中进一步讨论子查询。

· 使用函数

本节简要介绍了将用于全书示例的函数。 数据库函数是一组输入数据值和一个结果值之间的关系。

函数可以是内部的或用户定义的。 DB2 通用数据库提供很多内部函数和预安装的用户定义函数。可找到 SYSIBM 模式的内部函数和 SYSFUN 模式的预安装的用户定义函数。SYSIBM 和 SYSFUN 是保留模式。

内部函数和预安装的用户定义函数从不会满足所有需求。所以应用程序开发者可能需要创建自己的一套特定于他们的应用程序的函数。用户定义函数使这成为可能,例如通过扩展 DB2 通用数据库的范围以包括定制的商业或科学函数。这在用户定义函数中进一步讨论。

· 列函数
列函数对列中的一组值进行运算以得到单个结果值。下列就是一些列函数的示例。有关完整列表,参考 SQL Reference。

AVG              返回某一组中的值除以该组中值的个数的和

COUNT          返回一组行或值中行或值的个数

MAX              返回一组值中的最大值

MIN              返回一组值中的最小值

下列语句从 STAFF 表中选择最高薪水:

SELECT MAX(SALARY) FROM STAFF
此语句从员工 STAFF 样本表中返回值 22959.20。

下一个示例选择其收入超过平均收入但在公司的年数少于平均年数的雇员姓名和薪水。

SELECT NAME, SALARY FROM STAFF
WHERE SALARY > (SELECT AVG(SALARY) FROM STAFF) AND YEARS < (SELECT AVG(YEARS) FROM STAFF)
此语句产生下列结果:

NAME           SALARY
--------- ---------
Marenghi    17506.75
Daniels      19260.25
Gonzales    16858.20
在上面示例中的 WHERE 子句中,与直接实现列函数 (WHERE SALARY > AVG(SALARY)) 相反,在子查询中说明列函数。不能在 WHERE 子句中说明列函数。这是由于运算次序的结果。可认为 WHERE 子句是在 SELECT 子句之前进行计算的。因此,当正在计算 WHERE 子句时,列函数没有对该组值的存取权。稍后由 SELECT 子句选择这组值。

可指定 DISTINCT 作为列函数自变量的一部分,以在应用函数之前消除重复值。因此,COUNT(DISTINCT WORKDEPT) 计算不同部门的个数。

· 标量函数
标量函数对值进行某个运算以返回另一个值。下列就是一些由DB2 通用数据库提供的标量函数的示例。

ABS          返回数的绝对值

HEX          返回值的十六进制表示

LENGTH    返回自变量中的字节数(对于图形字符串则返回双字节字符数。)

YEAR       抽取日期时间值的年份部分

有关标量函数的详细列表和说明,参考 SQL Reference。

下列语句返回 ORG 表中的部门名称以及其每个名称的长度:

SELECT DEPTNAME, LENGTH(DEPTNAME) FROM ORG 此语句产生下列结果:

DEPTNAME                 2
-------------- -----------
Head Office         11
New England         11
Mid Atlantic       12
South Atlantic   14
Great Lakes         11
Plains                     6
Pacific                   7
Mountain                 8
注意:由于未使用 AS 子句给 LENGTH(DEPTNAME) 取一个有意义的名称,所以第二列中出现系统生成的数字。

· 分组

DB2 通用数据库具有基于表的特定列对数据进行分析的能力。

可按照在 GROUP BY 子句中定义的组对行进行分组。以其最简单的形式,组由称为分组列的列组成。 SELECT 子句中的列名必须为分组列或列函数。列函数对于 GROUP BY 子句定义的每个组各返回一个结果。下列示例产生一个列出每个部门编号的最高薪水的结果:

SELECT DEPT, MAX(SALARY) AS MAXIMUM FROM STAFF GROUP BY DEPT 此语句产生下列结果:

DEPT      MAXIMUM
------ ---------
10          22959.20
15          20659.80
20          18357.50
38          18006.00
42          18352.80
51          21150.00
66          21000.00
84         19818.00
注意:计算的是每个部门(由 GROUP BY 子句定义的组)而不是整个公司的 MAX(SALARY)。

· 将 WHERE 子句与 GROUP BY 子句一起使用
分组查询可以在形成组和计算列函数之前具有消除非限定行的标准 WHERE 子句。必须在GROUP BY 子句之前指定 WHERE 子句。例如:

SELECT WORKDEPT, EDLEVEL, MAX(SALARY) AS MAXIMUM FROM EMPLOYEE
WHERE HIREDATE > '1979-01-01' GROUP BY WORKDEPT, EDLEVEL ORDER BY WORKDEPT, EDLEVEL

结果为:

WORKDEPT EDLEVEL MAXIMUM
-------- ------- -----------
D11           17           18270.00
D21           15           27380.00
D21           16           36170.00
D21           17           28760.00
E11           12           15340.00
E21           14           26150.00
注意:在 SELECT 语句中指定的每个列名也在 GROUP BY 子句中提到。未在这两个地方提到的列名将产生错误。GROUP BY 子句对 WORKDEPT 和 EDLEVEL 的每个唯一组合各返回一行。

· 在 GROUP BY 子句之后使用 HAVING 子句
可应用限定条件进行分组,以便系统仅对满足条件的组返回结果。为此,在GROUP BY 子句后面包含一个 HAVING 子句。 HAVING 子句可包含一个或多个用 AND 和 OR 连接的谓词。每个谓词将组特性(如 AVG(SALARY))与下列之一进行比较:

· 该组的另一个特性

例如:

HAVING AVG(SALARY) > 2 * MIN(SALARY)
常数
例如:HAVING AVG(SALARY) > 20000
例如,下列查询寻找雇员数超过 4 的部门的最高和最低薪水:

SELECT WORKDEPT, MAX(SALARY) AS MAXIMUM, MIN(SALARY) AS MINIMUM
FROM EMPLOYEE GROUP BY WORKDEPT HAVING COUNT(*) > 4 ORDER BY WORKDEPT
此语句产生下列结果:

WORKDEPT MAXIMUM          MINIMUM
-------- ----------- -----------
D11           32250.00        18270.00
D21           36170.00        17250.00
E11           29750.00        15340.00
有可能(虽然很少见)查询有 HAVING 子句但没有 GROUP BY 子句。在此情况下,DB2 将整个表看作一个组。因为该表被看作是单个组,所以最多可以有一个结果行。如果 HAVING 条件对整个表为真,则返回选择的结果(该结果必须整个由列函数组成);否则不返回任何行。

第 5 章 表达式和子查询 返回

DB2 在表示查询方面提供了灵活性。本章描述一些可用于表示较复杂查询的重要方法。

本章给出下列各项的综合说明:

· 标量全查询

标量全查询是在括号中的全查询,该查询返回的一行只包含一个列值。标量全查询对从数据库中检索数据值供表达式使用是很有用的。

· 下列示例列出其薪水超过全部雇员平均薪水的雇员的姓名:

· SELECT LASTNAME, FIRSTNME FROM EMPLOYEE WHERE SALARY > (SELECT AVG(SALARY) FROM EMPLOYEE)
下列示例在两个不同的表中查寻雇员的平均薪水:
· SELECT AVG(SALARY) AS "Average_Employee",(SELECT AVG(SALARY) AS "Average_Staff" FROM STAFF) FROM EMPLOYEE

· 转换数据类型

有时可能需要将一些值从一种数据类型转换成另一种数据类型,例如,从数字值转换成字符串。要将一个值转换成另一个不同的类型,使用 CAST 说明。

转换说明的另一个可能用途是截断很长的字符串。在 EMP_RESUME 表中,RESUME 列是 CLOB(5K)。您可能只想显示包含应聘者个人信息的前 370 个字符。要从 EMP_RESUME 表中显示简历的 ASCII 格式的前 370 个字符,发出下列请求:


SELECT EMPNO, CAST(RESUME AS VARCHAR(370)) FROM EMP_RESUME WHERE RESUME_FORMAT = 'ascii'
会发出一个警告,通知您超过 370 个字符的值被截断。

可将空值转换为更便于在查询中进行处理的其他数据类型。 公共表表达式是一个为此目的使用转换的示例。

· 条件表达式

可在 SQL 语句中使用 CASE 表达式以便于处理表的数据表示。这提供了一种功能强大的条件表达式能力,在概念上与某些程序设计语言中的 CASE 语句类似。

· 要从 ORG 表中的 DEPTNAME 列将部门编号更改为有意义的词,输入下列查询:

· SELECT DEPTNAME,CASE DEPTNUMB
· WHEN 10 THEN 'Marketing'
· WHEN 15 THEN 'Research'
· WHEN 20 THEN 'Development'
· WHEN 38 THEN 'Accounting'
· ELSE 'Sales'
· END AS FUNCTION
· FROM ORG
结果为:

DEPTNAME              FUNCTION
-------------- -----------
Head Office        Marketing
New England        Research
Mid Atlantic      Development
South Atlantic  Accounting
Great Lakes        Sales
Plains                  Sales
Pacific                Sales
Mountain              Sales
可使用 CASE 表达式来防止出现异常情况,如被零除等。在下列示例中,如果雇员没有奖金或佣金报酬,则语句条件通过避免除法运算来防止出错:
· SELECT LASTNAME, WORKDEPT FROM EMPLOYEE WHERE(CASE WHEN BONUS+COMM=0 THEN NULL ELSE SALARY/(BONUS+COMM) END ) > 10
可在单个语句中使用 CASE 表达式,根据一个列中值的子集的总和与该列中所有值的总和的比来产生一个比率。使用 CASE 表达式的语句只需要传送数据一次。在没有 CASE 表达式的情况下,执行同样的计算至少需要传送两次。
下列示例使用 CASE 表达式计算部门 20 的薪水之和与全部薪水总额的比率:

SELECT CAST(CAST (SUM(CASE WHEN DEPT = 20 THEN SALARY ELSE 0 END) AS DECIMAL(7,2))/
SUM(SALARY) AS DECIMAL (3,2)) FROM STAFF
结果为 0.11。注意:CAST 函数确保结果的精度得到保持。

可使用 CASE 表达式来计算简单的函数,而不必调用函数本身,调用函数将需要额外开销。例如:
· CASE
· WHEN X<0 THEN -1
· WHEN X=0 THEN 0
· WHEN X>0 THEN 1
· END
此表达式与 SYSFUN 模式中 SIGN 用户定义函数有相同的结果。

· 表表达式

如果只需要单个查询的视图定义,可使用表表达式。

表表达式是临时的,只在 SQL 语句的使用期限内有效;表表达式不能共享,但它们比视图更灵活。任何授权的用户都可共享视图定义。

本节描述如何在查询中使用公共表表达式和嵌套表表达式。

· 嵌套表表达式
嵌套表表达式是一个临时视图,其中的定义被嵌套(直接定义)在主查询的 FROM子 句中。

下列查询使用嵌套表表达式来寻找那些教育级别超过 16 的雇员的平均总收入、教育级别以及雇用年份:


SELECT EDLEVEL, HIREYEAR, DECIMAL(AVG(TOTAL_PAY), 7,2) FROM (SELECT YEAR(HIREDATE) AS HIREYEAR, EDLEVEL,
SALARY+BONUS+COMM AS TOTAL_PAY FROM EMPLOYEE WHERE EDLEVEL > 16 ) AS PAY_LEVEL
GROUP BY EDLEVEL, HIREYEAR ORDER BY EDLEVEL, HIREYEAR

结果如下:

EDLEVEL  HIREYEAR        3
------- ----------- ---------
17            1967               28850.00
17            1973               23547.00
17            1977               24430.00
17            1979               25896.50
18            1965               57970.00
18            1968               32827.00
18            1973               45350.00
18            1976               31294.00
19            1958               51120.00
20            1975               42110.00
此查询使用嵌套表表达式,首先从 HIREDATE 列中抽取雇用年份,以便可以在后续的 GROUP BY 子句中使用该雇用年份。您可能不想将此查询作为视图来创建,因为您打算用不同的 EDLEVEL 值来执行相似查询。

此示例中使用了标量内部函数 DECIMAL。 DECIMAL 返回数字或字符串的十进制表示。有关函数的更多详情,参考 SQL Reference。

· 公共表表达式
公共表表达式是在全查询的开头使用 WITH 关键字定义的命名结果表。公共表表达式是您创建以在复杂查询之中使用的表表达式。在查询的开头使用 WITH 子句定义并命名公共表表达式。对公共表表达式的重复引用使用同一个结果集。相比之下,如果使用嵌套表表达式或视图,则每次都将重新生成结果集,其结果可能各不相同。

下列示例列出公司中教育级别大于 16、平均工资比那些同时雇用的且有同样教育级别的人低的所有人。在该查询后面会更详细地描述查询的各个部分。

(1)

WITH PAYLEVEL AS (SELECT EMPNO, YEAR(HIREDATE) AS HIREYEAR, EDLEVEL,
SALARY+BONUS+COMM AS TOTAL_PAY FROM EMPLOYEE WHERE EDLEVEL > 16),

(2)

PAYBYED (EDUC_LEVEL, YEAR_OF_HIRE, AVG_TOTAL_PAY) AS (SELECT EDLEVEL, HIREYEAR, AVG(TOTAL_PAY)
FROM PAYLEVEL GROUP BY EDLEVEL, HIREYEAR)

(3)

SELECT EMPNO, EDLEVEL, YEAR_OF_HIRE, TOTAL_PAY, DECIMAL(AVG_TOTAL_PAY,7,2)
FROM PAYLEVEL, PAYBYED WHERE EDLEVEL=EDUC_LEVEL AND HIREYEAR = YEAR_OF_HIRE AND TOTAL_PAY < AVG_TOTAL_PAY

(1)

这是名为 PAYLEVEL 的公共表表达式。此结果表包括雇用某个人的年份、该雇员的总收入以及他(或她)的教育级别。只包括雇员的教育级别大于 16 的那些行。

(2)

这是名为 PAYBYED(或 PAY by education)的公共表表达式。该表达式使用在前一个公共表表达式中创建的 PAYLEVEL 表来确定每个教育级别同一年雇用的雇员的教育级别、雇用年份以及平均收入。此表返回的列被赋予的名称与选择列表中所使用的列名不同(如 EDUC_LEVEL)。这会生成命名为 PAYBYED 的结果集,与嵌套表表达式示例中产生的结果相同。

(3)

最后,我们获得能产生期望结果的实际查询。连接这两个表(PAYLEVEL,PAYBYED)以确定总收入比同年雇用的人的平均收入低的那些人。注意:PAYBYED 是以 PAYLEVEL 为基础。所以在完整语句中有效地存取了 PAYLEVEL 两次。两次都使用同一组行来计算查询。

最终结果如下:

EMPNO   EDLEVEL YEAR_OF_HIRE TOTAL_PAY           5
------ ------- ------------ ------------- ---------
000210 17           1979                 20132.00            25896.50

· 相关名

相关名是用于识别一个对象的多种用途的标识符。可在查询的 FROM 子句中和 UPDATE 或 DELETE 语句的第一个子句中定义相关名。相关名可与表、视图或嵌套表表达式关联,但只限于定义相关名的上下文中。

例如,子句 FROM STAFF S、ORG O 分别指定 S 和 O 作为 STAFF 和 ORG 的相关名。

SELECT NAME, DEPTNAME FROM STAFF S, ORG O WHERE O.MANAGER = S.ID
一旦定义了相关名,则只可以使用相关名来限定对象。例如,上例中如果写成 ORG.MANAGER=STAFF.ID 的话,则该语句就会失效。

也可以使用相关名作为表示数据库对象的简称。只输入 S 比输入 STAFF 更容易。

通过使用相关名,可复制对象。这在需要将表中各项与自己本身作比较时很有用。在下列示例中,EMPLOYEE 表与它自己的另一个实例比较以寻找所有雇员的经理。该示例显示非设计员的雇员的姓名、这些雇员的经理的姓名以及部门编号。

SELECT E2.FIRSTNME, E2.LASTNAME, E2.JOB, E1.FIRSTNME, E1.LASTNAME, E1.WORKDEPT
FROM EMPLOYEE E1, EMPLOYEE E2 WHERE E1.WORKDEPT = E2.WORKDEPT
AND E1.JOB = 'MANAGER' AND E2.JOB <> 'MANAGER' AND E2.JOB <> 'DESIGNER'

此语句产生下列结果:

FIRSTNME         LASTNAME               JOB            FIRSTNME        LASTNAME               WORKDEPT
------------ --------------- -------- ------------ --------------- --------
DOLORES           QUINTANA               ANALYST    SALLY               KWAN                       C01
HEATHER           NICHOLLS               ANALYST    SALLY               KWAN                       C01
JAMES               JEFFERSON             CLERK       EVA                   PULASKI                D21
MARIA               PEREZ                     CLERK       EVA                   PULASKI                D21
SYBIL               JOHNSON                 CLERK       EVA                   PULASKI                D21
DANIEL             SMITH                     CLERK       EVA                   PULASKI                D21
SALVATORE       MARINO                   CLERK       EVA                   PULASKI                D21
ETHEL               SCHNEIDER             OPERATOR  EILEEN             HENDERSON            E11
MAUDE               SETRIGHT               OPERATOR  EILEEN             HENDERSON            E11
PHILIP             SMITH                     OPERATOR  EILEEN             HENDERSON            E11
JOHN                 PARKER                   OPERATOR  EILEEN             HENDERSON            E11
RAMLAL             MEHTA                     FIELDREP  THEODORE         SPENSER                E21
JASON               GOUNOT                   FIELDREP  THEODORE         SPENSER                E21
WING                 LEE                         FIELDREP  THEODORE         SPENSER                E21

--------------------------------------------------------------------------------

· 相关子查询

允许引用先前提到的任何表的子查询称为相关子查询。我们也说该子查询具有对主查询中表的相关引用。

下列示例是一个不相关子查询,该子查询列出部门 'A00' 中薪水超过该部门平均薪水的雇员的雇员编号和姓名:

SELECT EMPNO, LASTNAME FROM EMPLOYEE
WHERE WORKDEPT = 'A00' AND SALARY > (SELECT AVG(SALARY)
FROM EMPLOYEE WHERE WORKDEPT = 'A00')
如果想要知道每个部门的平均薪水,则需要对每个部门计算一次子查询。对在外层查询中标识的表的每一行,各使用一次 SQL 的相关功能(该能力允许您编写重复执行的子查询),就可做到这一点。此类型的相关子查询用来计算外层表的每一行的某个特性,该特性是在子查询中计算谓词所需要的。

此示例显示薪水高于部门平均薪水的所有雇员:

SELECT E1.EMPNO, E1.LASTNAME, E1.WORKDEPT FROM EMPLOYEE E1
WHERE SALARY > (SELECT AVG(SALARY) FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT) ORDER BY E1.WORKDEPT
在此查询中,对每个部门计算一次子查询。结果为:

EMPNO    LASTNAME               WORKDEPT
------ --------------- --------
000010  HAAS                       A00
000110  LUCCHESSI             A00
000030  KWAN                       C01
000060  STERN                     D11
000220  LUTZ                       D11
000200  BROWN                     D11
000170  YOSHIMURA             D11
000150  ADAMSON                 D11
000070  PULASKI                 D21
000270  PEREZ                     D21
000240  MARINO                   D21
000090  HENDERSON             E11
000280  SCHNEIDER             E11
000100  SPENSER                 E21
000340  GOUNOT                   E21
000330  LEE                         E21
要编写带有相关子查询的查询,使用与带有子查询的普通外部查询相同的基本格式。然而,在外部查询的 FROM 子句中,只是在表名后面放置一个相关名。于是子查询可能包含由该相关名限定的列引用。例如,如果 E1 是相关名,则 E1.WORKDEPT 表示外部查询中表的当前行的工作部门值。在外部查询中对表的每一行(概念上)重新计算子查询。

通过使用相关子查询,可以使系统为您工作并减少需要在应用程序中编写的代码量。

DB2 中允许非限定相关引用。例如,表 EMPLOYEE 有一个命名为 LASTNAME 的列,表 SALES 有一个命名为 SALES_PERSON 的列,但没有命名为 LASTNAME 的列。

SELECT LASTNAME, FIRSTNME, COMM FROM EMPLOYEE
WHERE 3 > (SELECT AVG(SALES) FROM SALES WHERE LASTNAME = SALES_PERSON)
在此示例中,系统检查最内层的 FROM 子句,以获取 LASTNAME 列。如果未找到 LASTNAME 列,则系统检查次最内层的 FROM 子句(此情况下为外部 FROM 子句)。虽然不总是必要的,还是建议限定相关引用以改进查询的可读性并确保获取想要的结果。

· 实现相关子查询
想何时使用相关子查询?列函数的使用有时是一条线索。

假定您想要列出教育级别高于部门平均值的雇员。

首先,您必须确定选择列表项。问题为 "List the employees"。这隐含着来自 EMPLOYEE 表中的 EMPNO 应该足以唯一标识雇员。该问题也将 EDLEVEL 和雇员的部门 WORKDEPT 说明为条件。当问题未明确要求显示列时,在选择列表中包括这些列将会有助于说明解法。现在可构造查询的一部分:

SELECT LASTNAME, WORKDEPT, EDLEVEL FROM EMPLOYEE
接着需要搜索条件(WHERE子句)。问题语句为 "...whose level of education is higher than the average for that employee's department"。这意味着对于表中每个雇员,必须计算该雇员所在部门的平均教育级别。此语句适合相关子查询的说明。正在对每行计算某个特性(当前雇员所在部门的平均教育级别)。 EMPLOYEE 表需要相关名:

SELECT LASTNAME, WORKDEPT, EDLEVEL FROM EMPLOYEE E1
需要的子查询较简单。该子查询计算每个部门的平均教育级别。完整的 SQL 语句为:

SELECT LASTNAME, WORKDEPT, EDLEVEL FROM EMPLOYEE E1
WHERE EDLEVEL > (SELECT AVG(EDLEVEL) FROM EMPLOYEE E2 WHERE E2.WORKDEPT = E1.WORKDEPT)
结果为:

LASTNAME               WORKDEPT EDLEVEL
--------------- -------- -------
HAAS                       A00            18
KWAN                       C01            20
PULASKI                 D21            16
HENDERSON             E11            16
LUCCHESSI             A00            19
PIANKA                   D11            17
SCOUTTEN               D11            17
JONES                     D11            17
LUTZ                       D11            18
MARINO                   D21            17
JOHNSON                 D21            16
SCHNEIDER             E11            17
MEHTA                     E21            16
GOUNOT                   E21            16
假定不列出雇员的部门编号,则应列出部门名称。需要的信息(DEPTNAME)在独立表(DEPARTMENT)中。定义相关变量的外层查询也可以是连接查询(参见从多个表中选择数据以了解详情)。

当在外层查询中使用连接时,列出要在 FROM 子句中连接的表,并将相关名放在这些表名的任何一个表名旁边。

要修改查询以列出部门名称而不是部门编号,在选择列表中用 DEPTNAME 替换 WORKDEPT。 FROM 子句现在也必须包括 DEPARTMENT 表,并且 WHERE 子句必须表示适当的连接条件。

以下是修改的查询:

SELECT LASTNAME, DEPTNAME, EDLEVEL FROM EMPLOYEE E1, DEPARTMENT
WHERE E1.WORKDEPT = DEPARTMENT.DEPTNO AND EDLEVEL > (SELECT AVG(EDLEVEL)
FROM EMPLOYEE E2 WHERE E2.WORKDEPT = E1.WORKDEPT)
上例显示,必须在包含相关子查询的某个查询的 FROM 子句中定义用于子查询中的相关名。然而,这种包含可能涉及若干层嵌套。

假定某些部门只有几个雇员,因此这些部门的平均教育级别可能是错误的。可以决定,为了使平均教育级别在用于比较雇员时是有意义的数字,一个部门中必须至少有 5 个雇员。因此现在必须列出教育级别高于雇员所在部门平均值的雇员,并只考虑至少有 5 个雇员的部门。

该问题暗含另一个子查询,因为对于外层查询中每个雇员来说,必须计算该雇员所在部门的雇员总数:

SELECT COUNT(*) FROM EMPLOYEE E3 WHERE E3.WORKDEPT = E1.WORKDEPT
仅当计数大于或等于 5 时才计算平均值:

SELECT AVG(EDLEVEL) FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT AND 5 <= (SELECT COUNT(*)
FROM EMPLOYEE E3 WHERE E3.WORKDEPT = E1.WORKDEPT)
最后,只包括其教育级别高于部门平均值的那些雇员:

SELECT LASTNAME, DEPTNAME, EDLEVEL FROM EMPLOYEE E1, DEPARTMENT
WHERE E1.WORKDEPT = DEPARTMENT.DEPTNO
AND EDLEVEL > (SELECT AVG(EDLEVEL) FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT
AND 5 <= (SELECT COUNT(*) FROM EMPLOYEE E3
WHERE E3.WORKDEPT = E1.WORKDEPT))
此语句产生下列结果:

LASTNAME               DEPTNAME                                            EDLEVEL
--------------- ----------------------------- -------
PIANKA                   MANUFACTURING SYSTEMS                  17
LUTZ                      MANUFACTURING SYSTEMS                  18
JONES                     MANUFACTURING SYSTEMS                  17
SCOUTTEN               MANUFACTURING SYSTEMS                  17
PULASKI                 ADMINISTRATION SYSTEMS                16
JOHNSON                 ADMINISTRATION SYSTEMS                16
MARINO                   ADMINISTRATION SYSTEMS                17
HENDERSON             OPERATIONS                                        16
SCHNEIDER             OPERATIONS                                        17

第 6 章 在查询中使用运算符和谓词 返回

在 DB2 通用数据库中,可用不同的集合运算符组合查询,并可用定量谓词构造复杂的条件语句。

本章说明如何:

· 用 UNION、EXCEPT 以及 INTERSECT 集合运算符组合不同的表

用定量谓词构造用于查询的复杂条件。基本谓词已在选择行中简要讨论过。

· 用集合运算符组合查询

UNION、EXCEPT 以及 INTERSECT 集合运算符使您能够将两个或更多外层查询组合成单个查询。执行用这些集合运算符连接的每个查询并组合各个查询的结果。根据运算符不同,产生不同的结果。

UNION 运算符
UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2。

在下列 UNION 运算符的示例中,查询返回薪水高于 $21,000、有管理责任且工龄少于 8 年的人员的姓名:

(1)

SELECT ID, NAME FROM STAFF WHERE SALARY > 21000 UNION
(2)

SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8 ORDER BY ID

各个查询的结果如下:

(1)

ID          NAME
------ ---------
140        Fraye
160        Molinare
260        Jones

(2)

ID          NAME
------ ---------
10          Sanders
30          Marenghi
100        Plotz
140        Fraye
160        Molinare
240        Daniels

数据库管理程序组合这两个查询的结果,消除重复行,并按升序返回最终结果。

ID          NAME
------ ---------
10          Sanders
30          Marenghi
100        Plotz
140        Fraye
160        Molinare
240        Daniels
260        Jones
如果在带有任何集合运算符的查询中使用 ORDER BY 子句,则必须在最后一个查询之后写该子句。系统对组合的回答集进行排序。如果两个表中的列名不同,则组合的结果表没有相应列的名称。替代地,将这些列按其出现的顺序编号。因此,如果想要对结果表进行排序,则必须在 ORDER BY 子句中指定列号。

EXCEPT 运算符
EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。

在下列 EXCEPT 运算符的示例中,查询返回收入超过 $21,000 但没有经理职位且工龄为 8 年或更长的所有人员的姓名。

SELECT ID, NAME FROM STAFF WHERE SALARY > 21000
EXCEPT
SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8

各个查询的结果在关于 UNION 的一节中列出。上面的语句产生下列结果:

ID           NAME
------ ---------
260        Jones

INTERSECT 运算符
INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有重复行而派生出一个结果表。当 ALL 随 INTERSECT 一起使用时 (INTERSECT ALL),不消除重复行。

在下列 INTERSECT 运算符的示例中,查询返回收入超过 $21,000 有管理责任且工龄少于8年的雇员的姓名和 ID。

SELECT ID, NAME FROM STAFF WHERE SALARY > 21000 INTERSECT
SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8
各个查询的结果在关于 UNION 的一节中列出。这两个使用 INTERSECT 的查询的结果为:

ID           NAME
------ ---------
140        Fraye
160        Molinare

当使用 UNION、EXCEPT 以及 INTERSECT 运算符时,记住下列事项:

· 运算符的查询选择列表中的所有对应项必须是相容的。有关更多信息,参见 SQL Reference 中的数据类型相容性表。

ORDER BY 子句(如果使用该子句的话)必须放在最后一个带有集合运算符的查询后面。对于每个运算符来说,如果列的名称与查询的选择列表中对应项的名称相同,则该列名只能在 ORDER BY 子句中使用。
在具有相同数据类型和相同长度的列之间进行的运算会产生一个具有该类型和长度的列。针对 UNION、EXCEPT 以及 INTERSECT 集合运算符的结果,参见 SQL Reference 中结果数据类型的规则。

· UNION 运算符

UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2。

在下列 UNION 运算符的示例中,查询返回薪水高于 $21,000、有管理责任且工龄少于 8 年的人员的姓名:

(1)

SELECT ID, NAME FROM STAFF WHERE SALARY > 21000 UNION

(2)

SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8 ORDER BY ID

各个查询的结果如下:

(1)

ID          NAME
------ ---------
140        Fraye
160        Molinare
260        Jones

(2)

ID          NAME
------ ---------
10          Sanders
30          Marenghi
100        Plotz
140        Fraye
160        Molinare
240        Daniels

数据库管理程序组合这两个查询的结果,消除重复行,并按升序返回最终结果。

ID         NAME
------ ---------
10          Sanders
30          Marenghi
100        Plotz
140        Fraye
160        Molinare
240        Daniels
260        Jones
如果在带有任何集合运算符的查询中使用 ORDER BY 子句,则必须在最后一个查询之后写该子句。系统对组合的回答集进行排序。如果两个表中的列名不同,则组合的结果表没有相应列的名称。替代地,将这些列按其出现的顺序编号。因此,如果想要对结果表进行排序,则必须在 ORDER BY 子句中指定列号。

· EXCEPT 运算符

EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。

在下列 EXCEPT 运算符的示例中,查询返回收入超过 $21,000 但没有经理职位且工龄为 8 年或更长的所有人员的姓名。

SELECT ID, NAME FROM STAFF WHERE SALARY > 21000 EXCEPT
SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8

各个查询的结果在关于 UNION 的一节中列出。上面的语句产生下列结果:

ID          NAME
------ ---------
260       Jones

· INTERSECT 运算符

INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有重复行而派生出一个结果表。当 ALL 随 INTERSECT 一起使用时 (INTERSECT ALL),不消除重复行。

在下列 INTERSECT 运算符的示例中,查询返回收入超过 $21,000 有管理责任且工龄少于8年的雇员的姓名和 ID。

SELECT ID, NAME FROM STAFF WHERE SALARY > 21000 INTERSECT
SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8
各个查询的结果在关于 UNION 的一节中列出。这两个使用 INTERSECT 的查询的结果为:

ID          NAME
------ ---------
140       Fraye
160       Molinare

当使用 UNION、EXCEPT 以及 INTERSECT 运算符时,记住下列事项:

· 运算符的查询选择列表中的所有对应项必须是相容的。有关更多信息,参见 SQL Reference 中的数据类型相容性表。

ORDER BY 子句(如果使用该子句的话)必须放在最后一个带有集合运算符的查询后面。对于每个运算符来说,如果列的名称与查询的选择列表中对应项的名称相同,则该列名只能在 ORDER BY 子句中使用。
在具有相同数据类型和相同长度的列之间进行的运算会产生一个具有该类型和长度的列。针对 UNION、EXCEPT 以及 INTERSECT 集合运算符的结果,参见 SQL Reference 中结果数据类型的规则。

· 谓词

谓词允许您构造条件,以便只处理满足这些条件的那些行。基本谓词在选择行讨论。本节讨论 IN、BETWEEN、LIKE、EXISTS 以及定量谓词。

· 使用 IN 谓词
使用 IN 谓词将一个值与其他几个值进行比较。例如:

SELECT NAME FROM STAFF WHERE DEPT IN (20, 15)
此示例相当于:

SELECT NAME FROM STAFF WHERE DEPT = 20 OR DEPT = 15
当子查询返回一组值时,可使用 IN 和 NOT IN 运算符。例如,下列查询列出负责项目 MA2100 和 OP2012 的雇员的姓:

SELECT LASTNAME FROM EMPLOYEE WHERE EMPNO IN (SELECT RESPEMP
FROM PROJECT WHERE PROJNO = 'MA2100' OR PROJNO = 'OP2012')
计算一次子查询,并将结果列表直接代入外层查询。例如,上面的子查询选择雇员编号 10 和 330,对外层查询进行计算,就好象 WHERE 子句如下:

WHERE EMPNO IN (10, 330)
子查询返回的值列表可包含零个、一个或多个值。

· 使用 BETWEEN 谓词
使用 BETWEEN 谓词将一个值与某个范围内的值进行比较。范围两边的值是包括在内的,并考虑 BETWEEN 谓词中用于比较的两个表达式。

下一示例寻找收入在 $10,000 和 $20,000 之间的雇员的姓名:

SELECT LASTNAME FROM EMPLOYEE WHERE SALARY BETWEEN 10000 AND 20000
这相当于:

SELECT LASTNAME FROM EMPLOYEE WHERE SALARY >= 10000 AND SALARY <= 20000
下一个示例寻找收入少于 $10,000 或超过 $20,000 的雇员的姓名:

SELECT LASTNAME FROM EMPLOYEE WHERE SALARY NOT BETWEEN 10000 AND 20000
· 使用 LIKE 谓词
使用 LIKE 谓词搜索具有某些模式的字符串。通过百分号和下划线指定模式。

· 下划线字符(_)表示任何单个字符。

百分号(%)表示零或多个字符的字符串。
任何其他表示本身的字符。
下列示例选择以字母'S'开头长度为 7 个字母的雇员名:

SELECT NAME FROM STAFF WHERE NAME LIKE 'S_ _ _ _ _ _'
下一个示例选择不以字母'S'开头的雇员名:

SELECT NAME FROM STAFF WHERE NAME NOT LIKE 'S%'
· 使用 EXISTS 谓词
可使用子查询来测试满足某个条件的行的存在性。在此情况下,谓词 EXISTS 或 NOT EXISTS 将子查询链接到外层查询。

当用 EXISTS 谓词将子查询链接到外层查询时,该子查询不返回值。相反,如果子查询的回答集包含一个或更多个行,则 EXISTS 谓词为真;如果回答集不包含任何行,则 EXISTS 谓词为假。

通常将 EXISTS 谓词与相关子查询一起使用。下面示例列出当前在项目(PROJECT) 表中没有项的部门:

SELECT DEPTNO, DEPTNAME FROM DEPARTMENT X WHERE NOT EXISTS
(SELECT * FROM PROJECT WHERE DEPTNO = X.DEPTNO) ORDER BY DEPTNO
可通过在外层查询的 WHERE 子句中使用 AND 和 OR 将 EXISTS 和 NOT EXISTS 谓词与其他谓词连接起来。

· 定量谓词
定量谓词将一个值和值的集合进行比较。如果全查询返回多个值,则必须通过附加后缀 ALL、ANY 或 SOME 来修改谓词中的比较运算符。这些后缀确定如何在外层谓词中处理返回的这组值。使用>比较运算符作为示例(下面的注释也适用于其他运算符):

表达式 > ALL (全查询)

如果该表达式大于由全查询返回的每个单值,则该谓词为真。如果全查询未返回值,则该谓词为真。如果指定的关系至少对一个值为假,则结果为假。注意:<>ALL 定量谓词相当于 NOT IN 谓词。

下列示例使用子查询和> ALL 比较来寻找收入超过所有经理的所有雇员的姓名和职业:

SELECT LASTNAME, JOB FROM EMPLOYEE WHERE SALARY > ALL
(SELECT SALARY FROM EMPLOYEE WHERE JOB='MANAGER')
表达式 > ANY (全查询)

如果表达式至少大于由全查询返回的值之一,则该谓词为真。如果全查询未返回值,则该谓词为假。注意:=ANY 定量运算符相当于 IN 谓词。

表达式 > SOME(全查询)

SOME 与 ANY 同义。

有关谓词和运算符的更多信息,参考 SQL Reference。

第 7 章 高级 SQL 返回

本章包括 DB2 通用数据库的几个功能,这些功能允许您在定制查询以满足您的要求的同时更有效地设计查询。本章中的主题假设您对这之前的材料已透彻理解。

· 用约束和触发器实施商业规则

在商界,我们的确通常需要确保始终实施某些规则。例如,参与项目的雇员必须被雇用。或者想要某些事件有计划地发生。例如,如果销售员售出一批商品,则应增加其佣金。

DB2 通用数据库为此提供了一套有用的方法。 唯一约束是禁止在表的一列或多列中出现重复值的规则。 参考完整性约束确保在整个指定的表中数据一致性。 表检查约束是一些条件,它们定义为表定义的一部分,限制一列或多列中使用的值。触发器允许您定义一组操作,这些操作通过对指定的表进行删除、插入或更新操作来执行或触发。触发器可用于写入其他表、修改输入值以及发布警报信息。

第一节提供关键字的概念性概述。接着,通过示例和图表进一步探讨参考完整性、约束以及触发器。

· 关键字
关键字是可用来标识或存取特定行的一组列。

由不止一列组成的关键字称为组合关键字。在具有组合关键字的表中,组合关键字中各列的排序不受这些列在表中排序的约束。

唯一关键字
唯一关键字被定义为它的任何值都不相同。唯一关键字的列不能包含空值。在执行 INSERT 和 UPDATE 语句期间,数据库管理程序强制执行该约束。一个表可以有多个唯一关键字。唯一关键字是可选的,并且可在 CREATE TABLE 或 ALTER TABLE 语句中定义。

主关键字
主关键字是一种唯一关键字,表定义的一部分。一个表不能有多个主关键字,并且主关键字的列不能包含空值。主关键字是可选的,并且可在 CREATE TABLE 或 ALTER TABLE 语句中定义。

外部关键字
外部关键字在参考约束的定义中指定。一个表可以有零个或多个外部关键字。如果组合外部关键字的值的任何部分为空,则该值为空。外部关键字是可选的,并且可在 CREATE TABLE 语句或 ALTER TABLE 语句中定义。

· 唯一约束
唯一约束确保关键字的值在表中是唯一的。唯一约束是可选的,并且可以通过使用指定 PRIMARY KEY 或 UNIQUE 子句的 CREATE TABLE 或 ALTER TABLE 语句来定义唯一约束。例如,可在一个表的雇员编号列上定义一个唯一约束,以确保每个雇员有唯一的编号。

· 参考完整性约束
通过定义唯一约束和外部关键字,可以定义表与表之间的关系,从而实施某些商业规则。唯一关键和外部关键字约束的组合通常称为参考完整性约束。外部关键字所引用的唯一约束称为父关键字。外部关键字表示特定的父关键字,或与特定的父关键字相关。例如,某规则可能规定每个雇员(EMPLOYEE 表)必须属于某现存的部门(DEPARTMENT 表)。因此,将 EMPLOYEE 表中的“部门号”定义为外部关键字,而将 DEPARTMENT 表中的“部门号”定义为主关键字。下列图表提供参考完整性约束的直观说明。

tb4.gif

· 表检查约束
表检查约束指定对于表的每行都要进行判定的条件。可对个别列指定检查约束。可使用 CREATE 或 ALTER TABLE 语句添加检查约束。

下列语句创建具有下列约束的表:

部门编号的值必须在范围 10 至 100 内
雇员的职务只能为下列之一: "Sales"、"Mgr"或"Clerk"
1986 年之前雇用的每个雇员的工资必须超过 $40,500。

CREATE TABLE EMP (ID SMALLINT NOT NULL, NAME VARCHAR(9),
DEPT SMALLINT CHECK (DEPT BETWEEN 10 AND 100),
JOB CHAR(5) CHECK (JOB IN ('Sales', 'Mgr', 'Clerk')),
HIREDATE DATE, SALARY DECIMAL(7,2), COMM DECIMAL(7,2),
PRIMARY KEY (ID), CONSTRAINT YEARSAL CHECK (YEAR(HIREDATE) >= 1986 OR SALARY > 40500) )
仅当条件判定为假时才会违反约束。例如,如果插入行的 DEPT 为空值,则插入继续进行而不出错,尽管 DEPT 的值应该象约束中定义的那样在 10 和 100 之间。

下列语句将一个约束添加至名为 COMP 的 EMPLOYEE 表中,该约束为雇员的总报酬必须超过 $15,000:


ALTER TABLE EMP
ADD CONSTRAINT COMP CHECK (SALARY + COMM > 15000)
将检查表中现存的行以确保这些行不违反新约束。可通过使用如下的 SET CONSTRAINTS 语句将此检查延期:

SET CONSTRAINTS FOR EMP OFF
ALTER TABLE EMP ADD CONSTRAINT COMP CHECK (SALARY + COMM > 15000)
SET CONSTRAINTS FOR EMP IMMEDIATE CHECKED

首先使用 SET CONSTRAINTS 语句以延期对表的约束检查。然后可将一个或多个约束添加至表而不检查这些约束。接着再次发出 SET CONSTRAINTS 语句,反过来将约束检查打开并执行任何延期的约束检查。

· 触发器
一个触发器定义一组操作,这组操作通过修改指定基表中数据的操作来激活。

可使用触发器来执行对输入数据的验证;自动生成新插入行的值;为了交叉引用而读取其他表;为了审查跟踪而写入其他表;或通过电子邮件信息支持警报。使用触发器将导致应用程序开发及商业规则的全面实施更快速并且应用程序和数据的维护更容易。

DB2 通用数据库支持几种类型的触发器。可定义触发器在 DELETE、INSERT 或 UPDATE 操作之前或之后激活。每个触发器包括一组称为触发操作的 SQL 语句,这组语句可包括一个可选的搜索条件。

可进一步定义后触发器以对每一行都执行触发操作,或对语句执行一次触发操作,而前触发器总是对每一行都执行触发操作。

在 INSERT、UPDATE 或 DELETE 语句之前使用触发器,以便在执行触发操作之前检查某些条件,或在将输入值存储在表中之前更改输入值。使用后触发器,以便在必要时传播值或执行其他任务,如发送信息等,这些任务可能是触发器操作所要求的。

下列示例说明了前触发器和后触发器的使用。考虑一个记录并跟踪股票价格波动的应用程序。该数据库包含两个表,CURRENTQUOTE 和 QUOTEHISTORY,定义如下:


CREATE TABLE CURRENTQUOTE (SYMBOL VARCHAR(10), QUOTE DECIMAL(5,2), STATUS VARCHAR(9))

CREATE TABLE QUOTEHISTORY (SYMBOL VARCHAR(10), QUOTE DECIMAL(5,2), TIMESTAMP TIMESTAMP)
当使用如下语句更新 CURRENTQUOTE 的 QUOTE 列时:


UPDATE CURRENTQUOTE SET QUOTE = 68.5 WHERE SYMBOL = 'IBM'
应更新 CURRENTQUOTE 的 STATUS 列以反映股票是否:

在升值
处于本年度的新高
在下跌
处于本年度的新低
价位稳定
这通过使用下列前触发器来实现:

(1)

CREATE TRIGGER STOCK_STATUS NO CASCADE BEFORE UPDATE OF QUOTE ON CURRENTQUOTE
REFERENCING NEW AS NEWQUOTE OLD AS OLDQUOTE FOR EACH ROW MODE DB2SQL

(2)

SET NEWQUOTE.STATUS =

(3)

CASE

(4)

WHEN NEWQUOTE.QUOTE >= (SELECT MAX(QUOTE) FROM QUOTEHISTORY
WHERE SYMBOL = NEWQUOTE.SYMBOL AND YEAR(TIMESTAMP) = YEAR(CURRENT DATE) ) THEN 'High'

(5)

WHEN NEWQUOTE.QUOTE <= (SELECT MIN(QUOTE) FROM QUOTEHISTORY
WHERE SYMBOL = NEWQUOTE.SYMBOL AND YEAR(TIMESTAMP) = YEAR(CURRENT DATE) ) THEN 'Low'

(6)

WHEN NEWQUOTE.QUOTE > OLDQUOTE.QUOTE THEN 'Rising'
WHEN NEWQUOTE.QUOTE < OLDQUOTE.QUOTE THEN 'Dropping'
WHEN NEWQUOTE.QUOTE = OLDQUOTE.QUOTE THEN 'Steady'
END

(1)

此代码块将名为 STOCK_STATUS 的触发器定义为一个应该在更新 CURRENTQUOTE 表的 QUOTE 列之前激活的触发器。第二行指定,在将 CURRENTQUOTE 表的实际更新所引起的任何更改应用于数据库之前,要应用触发操作。第二行也意味着触发操作将不会激活任何其他触发器。第三行指定一些名称,必须将这些名称作为列名的限定符用于新值 (NEWQUOTE) 和旧值 (OLDQUOTE)。用这些相关名(NEWQUOTE 和 OLDQUOTE)限定的列名称为转换变量。第四行表示应对每一行都执行触发操作。

(2)

这标记此触发器的触发操作中第一个也是唯一的一个 SQL 语句的开始。 SET 转换变量语句在一个触发器中用来将值赋给表的行中的列,该表正在由激活该触发器的语句进行更新。此语句正将一个值赋给 CURRENTQUOTE 表的 STATUS 列。

(3)

该赋值语句右边使用的表达式为 CASE 表达式。 CASE 表达式扩充为 END 关键字。

(4)

第一种情况检查新报价 (NEWQUOTE.QUOTE) 是否超过当前日历年度中股票符号的最高价。子查询正在使用由跟在后面的后触发器更新的 QUOTEHISTORY 表。

(5)

第二种情况检查新报价 (NEWQUOTE.QUOTE) 是否小于当前日历年度中股票符号的最低价。子查询正在使用由跟在后面的后触发器更新的 QUOTEHISTORY 表。

(6)

最后三种情况将新报价 (NEWQUOTE.QUOTE) 与表 (OLDQUOTE.QUOTE) 中的报价比较,以确定新报价是大于、小于还是等于旧报价。 SET 转换变量语句在此处结束。

除了更新 CURRENTQUOTE 表中的项之外,还需要通过将新报价连同时间戳记一起复制到 QUOTEHISTORY 表中来创建一个审查记录。这通过使用下列后触发器来实现:

(1)

CREATE TRIGGER RECORD_HISTORY AFTER UPDATE OF QUOTE ON CURRENTQUOTE REFERENCING NEW AS NEWQUOTE
FOR EACH ROW MODE DB2SQL BEGIN ATOMIC

(2)

INSERT INTO QUOTEHISTORY VALUES (NEWQUOTE.SYMBOL, NEWQUOTE.QUOTE, CURRENT TIMESTAMP);
END

(1)

此代码块将命名为 RECORD_HISTORY 的触发器定义为应该在更新 CURRENTQUOTE 表的 QUOTE 列之后激活的触发器。第三行指定应该作为列名的限定符用于新值 (NEWQUOTE) 的名称。第四行表示应对每一行都执行触发操作。

(2)

此触发器的触发操作包括单个 SQL 语句,该语句使用已更新的行中的数据(NEWQUOTE.SYMBOL 和 NEWQUOTE.QUOTE)和当前的时间戳记将该行插入 QUOTEHISTORY 表。

CURRENT TIMESTAMP 是包含时间戳记的专用寄存器。专用寄存器中提供了列表和解释。

· 连接

从两个或更多个表中组合数据的过程称为连接表。数据库管理程序从指定的表中形成行的所有组合。对于每个组合,它都测试连接条件。连接条件是带有一些约束的搜索条件。有关约束的列表,参考 SQL Reference。

注意:连接条件涉及的列的数据类型不必相同;然而,这些数据类型必须相容。计算连接条件的方式与计算其他搜索条件的方式相同,并且使用相同的比较规则。

如果未指定连接条件,则返回在 FROM 子句中列出的表中行的所有组合,即使这些行可能完全不相关。该结果称为这两个表的交叉积。

本节中的示例基于下面两个表。这两个表只是样本数据库中表的简化形式,在样本数据库中并不存在。这两个表一般用来概述关于连接的重点。 SAMP_STAFF 列出未作为合同工雇用的雇员的姓名以及这些雇员的职务说明,而 SAMP_PROJECT 则列出雇员(合同工和全职人员)的姓名以及这些雇员所参与的项目。

这些表如下:

tb5.gif

下列示例产生两个表的交叉积。因未指定连接条件,所以给出了行的所有组合:

SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT, SAMP_STAFF
此语句产生下列结果:

NAME              PROJ      NAME            JOB
---------- ------ ---------- --------
Haas             AD3100  Haas             PRES
Thompson     PL2100  Haas            PRES
Walker         MA2112  Haas            PRES
Lutz             MA2111  Haas            PRES
Haas             AD3100  Thompson    MANAGER
Thompson     PL2100  Thompson    MANAGER
Walker         MA2112  Thompson    MANAGER
Lutz             MA2111  Thompson    MANAGER
Haas             AD3100  Lucchessi  SALESREP
Thompson     PL2100  Lucchessi  SALESREP
Walker         MA2112  Lucchessi  SALESREP
Lutz             MA2111  Lucchessi  SALESREP
Haas             AD3100  Nicholls    ANALYST
Thompson     PL2100  Nicholls    ANALYST
Walker         MA2112  Nicholls    ANALYST
Lutz             MA2111  Nicholls    ANALYST
两个主要的连接类型是内连接和外连接。到目前为止,所有示例中使用的都是内连接。内连接只保留交叉积中满足连接条件的那些行。如果某行在一个表中存在,但在另一个表中不存在,则结果表中不包括该信息。

下列示例产生两个表的内连接。该内连接列出分配给某个项目的全职雇员信息:

SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT, SAMP_STAFF WHERE SAMP_STAFF.NAME = SAMP_PROJECT.NAME
或者,也可以指定如下内连接:

SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT INNER JOIN SAMP_STAFF ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
结果是:

NAME              PROJ     NAME            JOB
---------- ------ ---------- --------
Haas             AD3100  Haas             PRES
Thompson PL2100 Thompson MANAGER
注意:该内连接的结果由右表和左表中姓名列的值匹配的行组成- 'Haas' 和 'Thompson' 都包括在列出所有全职雇员的 SAMP_STAFF 表中以及列出分配给某个项目的专职和合同雇员的 SAMP_PROJECT 表中。

外连接是内连接和左表和/或右表中未包括内连接中的那些行的并置。当对两个表执行外连接时,可任意将一个表指定为左表而将另一个表指定为右表。外连接有三种类型:

1. 左外连接包括内连接和左表中未包括在内连接中的那些行。

2. 右外连接包括内连接和右表中未包括在内连接中的那些行。

3. 全外连接包括内连接以及左表和右表中未包括在内连接中的行。

使用 SELECT 语句来指定要显示的列。在 FROM 子句中,列出后跟关键字 LEFT OUTER JOIN、RIGHT OUTER JOIN 或 FULL OUTER JOIN 的第一个表的名称。接着需要指定后跟 ON 关键字的第二个表。在 ON 关键字后面,指定表示要连接的表之间关系的连接条件。

在下列示例中,将 SAMP_STAFF 指定为右表,而 SAMP_PROJECT 则被指定为左表。通过使用 LEFT OUTER JOIN,列出所有全职和合同雇员(在 SAMP_PROJECT 中列出)的姓名和项目编号,如果是全职雇员(在 SAMP_STAFF 中列出),还列出这些雇员的职位:

SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT LEFT OUTER JOIN SAMP_STAFF ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
此语句产生下列结果:

NAME             PROJ                                  NAME            JOB
---------- -------------------- ---------- --------------------
Haas             AD3100                              Haas            PRES
Lutz             MA2111                             -                   -
Thompson     PL2100                             Thompson     MANAGER
Walker         MA2112                             -                   -
所有列中都具有值的那些行是该内连接的结果。这些都是满足连接条件的行: 'Haas' 和 'Thompson' 既在 SAMP_PROJECT(左表)中列出又在 SAMP_STAFF(右表)中列出。对于不满足连接条件的行,右表的列上出现空值: 'Lutz' 和 'Walker' 都是在 SAMP_PROJECT 表中列出的合同雇员,因而未在 SAMP_STAFF 表中列出。注意:左表中的所有行都包括在结果集中。

在下一个示例中,将 SAMP_STAFF 指定为右表而 SAMP_PROJECT 则被指定为左表。通过使用 RIGHT OUTER JOIN 列出所有专职雇员(在 SAMP_STAFF 中列出)的姓名和工作职位,如果将这些雇员分配给了某个项目(在 SAMP_PROJECT 中列出),还列出他们的项目编号:

SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT RIGHT OUTER JOIN SAMP_STAFF ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
结果为:

NAME              PROJ                                 NAME            JOB
---------- -------------------- ---------- --------------------
Haas             AD3100                             Haas              PRES
-                   -                                       Lucchessi    SALESREP
-                  -                                     Nicholls      ANALYST
Thompson     PL2100                           Thompson      MANAGER
象在左外连接中一样,所有列中都具有值的那些行是内连接的结果。这些都是满足连接条件的行: 'Haas'和'Thompson'既在 SAMP_PROJECT(左表)中列出又在 SAMP_STAFF(右表)中列出。对于不满足连接条件的行,右表的列上出现空值: 'Lucchessi'和'Nicholls'都是未分配项目的专职雇员。虽然他们在 SAMP_STAFF 中列出,但未在 SAMP_PROJECT 中列出。注意:右表中的所有行都包括在结果集中。

下一个示例对 SAMP_PROJECT 表和 SAMP_STAFF 表使用 FULL OUTER JOIN。该示例列出所有专职雇员(包括未分配项目的雇员)和合同雇员的姓名:

SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT FULL OUTER JOIN SAMP_STAFF ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
结果为:

NAME             PROJ                               NAME           JOB
---------- -------------------- ---------- --------------------
Haas             AD3100                           Haas            PRES
-                  -                                     Lucchessi   SALESREP
-                   -                                     Nicholls     ANALYST
Thompson     PL2100                            Thompson     MANAGER
Lutz             MA2111                            -                  -
Walker        MA2112                            -                  -
此结果包括左外连接、右外连接以及内连接。列出所有专职雇员和合同雇员。正如左外连接和右外连接一样,对于不满足连接条件的值,相应列中出现空值。 SAMP_STAFF 和 SAMP_PROJECT 中的每一行都包括在结果集中。

· 复杂查询

DB2 通用数据库允许您通过使用 ROLLUP 和 CUBE 分组、合并及查看单个结果集中的多列。这种新型而强大的功能增强并简化了基于数据分析的 SQL。

有很多方法可从数据库中抽取有用信息。可执行递归查询从现存数据集中产生结果表。

· ROLLUP 和 CUBE 查询
在查询的 GROUP BY 子句中指定 ROLLUP 和 CUBE 运算。 ROLLUP 分组产生包含常规分组行和小计行的结果集。CUBE 分组产生包含来自 ROLLUP 和交叉制表行中的行的结果集。所以对于 ROLLUP,可获取每人每月的销售量以及每月销售总量和总部总量。对于 CUBE,将包括每人销售总量的附加行。参见 SQL Reference 以了解更详细的情况。

· 递归查询
递归查询是迭代使用结果数据来确定进一步结果的查询。可以把这想象成在一棵树上或一幅图中来回移动。使用递归查询的常见示例包括材料单应用程序、订票系统、网络计划和调度。递归查询是使用包括引用自己名称的的公共表表达式来编写的。参见 SQL Reference 以获取递归查询的示例。

第 8 章 定制和增强数据操作 返回

本章包括 DB2 通用数据库的几个功能,这些功能允许您在定制查询以满足您的要求的同时更有效地设计查询。本章中的主题假设您对这之前的材料已透彻理解。

· 用户定义类型

单值类型是与现存类型(它的“源”类型)共享其内部表示的用户定义数据类型,但对于大多数运算来说,认为单值类型是独立和不兼容的。例如,您可能想定义年龄类型、重量类型以及高度类型,所有这些类型都有相当不同的语义,但都使用内部数据类型 INTEGER 作为它们的内部表示。

下列示例说明了命名为 PAY 的单值类型的创建:

CREATE DISTINCT TYPE PAY AS DECIMAL(9,2) WITH COMPARISONS
虽然 PAY 有与内部数据类型 DECIMAL(9,2)相同的表示,但还是认为它是与 DECIMAL(9,2)或任何其他类型不可比的独立类型。它只能与相同的单值类型比较。并且,会影响到按 DECIMAL 使用的运算符和函数将在此不适用。例如,具有 PAY 数据类型的值不能与具有 INTEGER 数据类型的值相乘。因此,您必须编写只应用于 PAY 数据类型的函数。

使用单值数据类型可限制偶然错误。例如,如果将 EMPLOYEE 表的 SALARY 列定义为 PAY 数据类型,则不能将该列添加至 COMM,即使它们的源类型相同。

单值数据类型支持类型转换。源类型可以转换为单值数据类型,单值数据类型也可以转换为源类型。例如,如果将表 EMPLOYEE 的 SALARY 列定义为 PAY 数据类型,则下列示例将不会在比较运算符处出错。

SELECT * FROM EMPLOYEE WHERE DECIMAL(SALARY) = 41250
DECIMAL(SALARY)返回一个十进制数据类型。相反地,数字数据类型可以转换为 PAY 类型。例如,可以使用 PAY(41250) 来转换数字 41250 的类型。

·用户定义函数

如使用函数中所述,DB2 通用数据库提供内部函数和用户定义函数 (UDF)。然而,此函数集从不会满足所有需求。您常常需要为特别的任务创建定制函数。用户定义函数允许您创建定制函数。

用户定义函数有两种类型:源和外部函数。

源用户定义函数允许允许用户定义类型有选择地引用另一个已为数据库所知的内部函数或用户定义函数。您可以既使用标量函数又使用列函数。

在下一示例中,基于内部 MAX 列函数来创建称为 MAX 的用户定义函数,它采用 DECIMAL 数据类型作为输入。MAX UDF 采用 PAY 类型作为输入且返回一个 PAY 类型作为输出。

CREATE FUNCTION MAX(PAY) RETURNS PAY SOURCE MAX(DECIMAL)
外部用户定义函数由用户用程序设计语言编写。有外部标量函数和外部表函数,在 SQL Reference 中讨论了这两个函数。

假定您已编写了一个计算字符串中字数的函数,则您可以使用 CREATE FUNCTION 语句以名称 WORDCOUNT 向数据库注册该函数。然后就可在 SQL 语句中使用此函数。

例如,下列语句返回雇员编号和他们简历的 ASCII 格式的字数。WORDCOUNT 是用户已向数据库注册并且现正在语句中使用的外部标量函数。

SELECT EMPNO, WORDCOUNT(RESUME) FROM EMP_RESUME WHERE RESUME_FORMAT = 'ascii'
有关编写用户定义函数的更详细信息,参考 Application Development Guide。

·大对象 (LOB)

术语大对象及其缩写词 LOB 用于表示三种数据类型:BLOB、CLOB 或 DBCLOB。这些类型可以包含诸如音频、图片以及文档等对象的大量数据。

二进制大对象(BLOB)是变长字符串,以字节进行量度,最大长度可达 2 吉字节。BLOB 主要用来保存非传统数据,如图片、声音以及混合媒体等。

字符大对象(CLOB)是变长字符串,以字节进行量度,最大长度可达 2 吉字节。 CLOB 用于存储大的单字节字符集数据,如文档等。CLOB 被认为是字符串。

双字节字符大对象(DBCLOB)是最大长度可达 2 吉字节的双字节字符变长字符串(1 073 741 823 双字节字符)。 DBCLOB 用于存储大的双字节字符集数据,如文档等。DBCLOB 被认为是图形字符串。

·操作大对象(LOB)
由于 LOB 值可以很大,所以将它们从数据库服务器传送至客户机应用程序可能要花费一些时间。然而,一般一次处理 LOB 值的一部分,而不是将它们作为一个整体处理。对于应用程序不需要(或不想要)将整个 LOB 值存储在应用程序内存中的那些情况,应用程序可以通过大对象定位器变量引用此值。

然后后续语句可以使用定位器对数据执行操作,而不必检索整个大对象。定位器变量用来减少应用程序的存储器需求,并通过减少客户机与服务器之间的数据流而改进性能。

另一个机制是文件引用变量。它们用来直接对文件检索大对象或直接从文件来更新表中的大对象。文件引用变量用来减少应用程序的存储器需求,因为这些变量不必存储大对象数据。有关更多信息,参考 Application Development Guide 和 SQL Reference。

·专用寄存器

专用寄存器是由数据库管理程序为连接定义的存储区,用于存储可以用 SQL 语句引用的信息。下列是几个较常用的专用寄存器的示例。有关所有专用寄存器的列表和更详细的信息,参考 SQL Reference。

· CURRENT DATE:保存对应于执行 SQL 语句时的日时钟的日期。

CURRENT FUNCTION PATH:保存一个指定用于分解函数和数据类型引用的函数路径的值。
CURRENT SERVER:指定当前应用程序服务器。
CURRENT TIME:保存对应于执行 SQL 语句时的日时钟的时间。
CURRENT TIMESTAMP:指定对应于执行 SQL 语句时的日时钟的时间戳记。
CURRENT TIMEZONE:指定世界时与应用程序服务器本地时间之间的差别。
USER:指定运行期权限 ID。
您可以用 VALUES 语句显示专用寄存器的内容。例如:

VALUES (CURRENT TIMESTAMP)
也可以使用:

SELECT CURRENT TIMESTAMP FROM ORG
这将为表中的每一行项返回 TIMESTAMP。

转载于:https://www.cnblogs.com/kyo1028/archive/2006/05/15/400264.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值