第3章 关系数据库标准语言SQL 本章介绍关系数据库标准语言SQL,包括SQL 概述、数据定 义、基本SQL查询、数据更新以及视图。 3.1 SQL概述 SQL诞生于20世纪70年代,最初的名称是SEQUEL(Structured EnglishQueryLanguage),后更名为SQL(StructuredQueryLanguage)。 SQL是一种非过程化语言,用户只要说明需要的数据库内容,不必 说明存取所需数据的具体过程和操作。SQL 集数据定义、数据查 询、数据操纵和数据控制等功能于一体,具有功能综合、风格统一、 数据操纵高度非过程化、面向集合操作、语法结构统一和语言简洁 等特点。 SQL标准自诞生以来不断发展完善,目前已有8个版本,最新 版本为SQL2016,但SQL92已经基本完善。考虑到SQL92相对简 单,且被众多RDBMS实现,本书仍以SQL92为基础。读者在上机 实现时,应参考所使用的RDBMS用户手册。 3.2 数据定义 3.2.1 模式的定义与删除 下面是SQL92中关于模式定义的语法。 <schema definition> ::= CREATE SCHEMA <schema name clause> [ <schema character set or path> ] [ <schema element>... ] <schema name clause> ::= <schema name> | AUTHORIZATION <schema authorization identifier> 38 第3章 关系数据库标准语言SQL | <schema name> AUTHORIZATION <schema authorization identifier> <schema authorization identifier> ::= <authorization identifier> SQL标准语法描述中,符号“::=”表示对一个元素的定义。不带括号的字符串 如“CREATESCHEMA”表示保留的关键字。带括号的字符串如“<schemaname clause>”表示一个元素。符号“[ ]”表示可选内容。 根据语法,创建模式必须包含CREATESCHEMA 关键字,后面是模式名称子句 (<schemanameclause>),以及两个可选内容。模式名称子句中可以指定模式名称 或被授权的用户,也可以两者同时指定。 【例3.1】 为用户张三创建一个模式Online-Shopping。 CREATE SCHEMA "Online-Shopping" AUTHORIZATION "张三" 下面是删除模式的语法。 <drop schema statement> ::= DROP SCHEMA <schema name> <drop behavior> <drop behavior> ::= CASCADE | RESTRICT 其中CASCADE和RESTRICT 两者必选其一。选择了CASCADE(级联),表示在删 除模式的同时把该模式中所有的数据库对象全部删除。选择了RESTRICT(限制), 表示如果该模式中已经定义了数据库对象(如表、视图等),则拒绝该删除语句的执 行;只有当该模式中没有任何数据库对象时才能执行DROPSCHEMA 语句。 【例3.2】 删除例3.1中创建的模式。 DROP SCHEMA "Online-Shopping" CASCADE 3.2.2 基本表的定义、修改与删除 创建表的基本语法,至少要包含CREATETABLE、表的名称(<tablename>) 和<tableelementlist>。 <table definition> ::= CREATE [ { GLOBAL | LOCAL } TEMPORARY ] TABLE <table name> <table element list> [ ON COMMIT { DELETE | PRESERVE } ROWS ] <tableelementlist> 的内容是一对圆括号,其中包含一个或多个<table element>,中间由逗号分隔。 <table element list> ::= <left paren> <table element> [ { <comma> <table element> }... ] < right paren> 3.2 数据定义 39 <tableelement>可以是<columndefinition>或<tableconstraintdefinition>。 <table element> ::= <column definition> | <table constraint definition> <columndefinition>至少要包含<columnname>,并定义其<datatype> 或 <domainname>。 <column definition> ::= <column name> { <data type> | <domain name> } [ <default clause> ] [ <column constraint definition>... ] [ <collate clause> ] <columnconstraintdefinition>至少要包含<columnconstraint>。 <column constraint definition> ::= [ <constraint name definition> ] <column constraint> [ <constraint attributes> ] <columnconstraint>可以是NOTNULL(不允许空值)、<uniquespecification> (唯一性约束)、<referencesspecification> (外键约束)或者<checkconstraint definition>(check约束)。 <column constraint> ::= NOT NULL | <unique specification> | <references specification> | <check constraint definition> <tableconstraintdefinition>必须包含<tableconstraint>。 <table constraint definition> ::= [ <constraint name definition> ] <table constraint> [ <constraint attributes> ] <tableconstraint> 可以是<uniqueconstraintdefinition>、<referential constraintdefinition>或<checkconstraintdefinition>中的一个。 <table constraint> ::= <unique constraint definition> | <referential constraint definition> | <check constraint definition> 【例3.3】 创建顾客表。 CREATE TABLE Customers( 40 第3章 关系数据库标准语言SQL CID VARCHAR(32) PRIMARY KEY, CName VARCHAR(128) NOT NULL, City VARCHAR(128) ); 顾客表包含3个属性。CID 表示顾客ID,数据类型是VARCHAR,表示其为可 变长度的字符串,最大长度为32。PRIMARY KEY 是列级约束条件(<column constraint>),表示这是主码。CName表示顾客姓名,后面的NOT NULL也是列级 约束条件,表明其不可为空值。 【例3.4】 创建供应商表。 CREATE TABLE Suppliers( SID VARCHAR(32) PRIMARY KEY, SName VARCHAR(128) NOT NULL, City VARCHAR(128) ); 供应商表包含3个属性。SID 是供应商ID,为主码;SName是供应商名称;City 是供应商所在城市。 【例3.5】 创建商品表。 CREATE TABLE Products( PID VARCHAR(32) PRIMARY KEY, PName VARCHAR(128) NOT NULL, Price DECIMAL, Category VARCHAR(128), SID VARCHAR(32) NOT NULL, FOREIGN KEY (SID) REFERENCES Suppliers(SID) ); 商品表包含5个属性。PID是商品ID,为主码;PName是商品名称,不可为空值; Price是商品单价,类型为带小数的数字;Category是商品类别;SID 是供应商ID,不 可为空值。最后一行定义了一个外码约束,表示SID 属性是一个外码,引用了 Suppliers表的SID属性。 【例3.6】 创建订单表。 CREATE TABLE Orders( OID VARCHAR(32) PRIMARY KEY, CID VARCHAR(32), CreateTime DATETIME, FOREIGN KEY (CID) REFERENCES Customers(CID) ); 订单表包含3个属性。OID是订单ID,为主码;CID是顾客ID;CreateTime是订 单创建时间,DATETIME 是时间数据类型。最后一行定义了一个外码约束,表示 CID属性是一个外键,引用了Customers表中的CID属性。 3.3 基本SQL查询 41 【例3.7】 创建订单项表。 CREATE TABLE OrderItems( OID VARCHAR(32), PID VARCHAR(32), Quantity INT, Discount DECIMAL, PRIMARY KEY(OID, PID), FOREIGN KEY (OID) REFERENCES Orders(OID), FOREIGN KEY (PID) REFERENCES Products(PID) ); 订单项表包含4个属性。OID是订单ID;PID是商品ID;Quantity是数量,类型 为整数;Discount是折扣。表的主码由OID 和PID 共同组成。OID 和PID 都是外 码,分别引用了订单表的OID和商品表的PID。 3.2.3 索引的创建 创建索引需要指定索引的名称、表的名称和属性列的名称。若指定关键词 CLUSTERED,则将创建聚簇索引。聚簇索引要求RDBMS根据指定属性列的值存 储元组,因此每个关系最多只能有一个聚簇索引。 CREATE [CLUSTERED] INDEX name ON table_name ( { column_name | ( expression ) } 【例3.8】 在顾客表的姓名列上创建索引。 CREATE INDEX idx-cname ON Custermers (CName); 3.3 基本SQL查询 3.3.1 单表查询 查询是SQL中语义最为丰富,表达最为灵活的部分之一。下面简单介绍SQL标 准中的查询部分。 <query specification> ::= SELECT [ <set quantifier> ] <select list> <table expression> <select list> ::= <asterisk> | <select sublist> [ { <comma> <select sublist> }... ] <select sublist> ::= <derived column> | <qualifier> <period> <asterisk> 42 第3章 关系数据库标准语言SQL 查询以SELECT开始,必须包含<selectlist>和<tableexpression>。 <selectlist>可以是<asterisk>(即*)或<selectsublist>。<selectsublist>包 含<derivedcolumn>或<qualifier> <period> <asterisk>。 <asterisk> ::= * <derived column> ::= <value expression> [ <as clause> ] <as clause> ::= [ AS ] <column name> <derivedcolumn>由<valueexpression>定义,<asclause>即AS 加上自己 定义的列名。<valueexpression>指一个表达式,可以是数值型、字符串型、日期型 或区间型,可参考后面的例子。 <tableexpression>描述了从哪些表中查询数据,至少包含一个<fromclause>, 可以包含<whereclause>、<groupbyclause>或<havingclause>。注意:使用 <havingclause>必须同时使用<groupbyclause>。 <table expression> ::= <from clause> [ <where clause> ] [ <group by clause> ] [ <having clause> ] <order by clause> ::= ORDER BY <sort specification list> 查询后面可以加上<orderbyclause>对结果进行排序。 鉴于查询语义的复杂性,为便于读者理解,下面给出查询语句的一般格式。 SELECT [ALL|DISTINCT] <目标列表达式> [别名][,<目标列表达式>[别名]]... FROM <表名或视图名>[别名][,<表名或视图名>[别名]]... [WHERE <条件表达式>] [GROUP BY <列名1> [HAVING <条件表达式>]] [ORDER BY <列名2> [ASC|DESC]] 【例3.9】 查询所有顾客信息,结果如表3.1所示。 SELECT * FROM Customers; 表3.1 例3.9查询结果 CID CName City 1001 张三北京 1002 李四广州 1003 王五上海 【例3.10】 查询订单ID及其创建的年份,结果如表3.2所示。 3.3 基本SQL查询 43 SELECT OID, YEAR(CreateTime) AS 年份; 上面的查询语句中,假定YEAR是一个函数,其输入为一个DATETIME类型数 据,输出为对应年份。注意:这是<valueexpression>的示例。“AS 年份”表示将输 出的列更名为“年份”。 表3.2 例3.10查询结果 OID 年 份 O001 2023 O002 2023 O003 2023 O004 2023 【例3.11】 查询价格低于1000元的商品,按价格从高到低排序,结果如表3.3 所示。 SELECT * FROM Products WHERE Price < 1000 ORDER BY Price DESC; 上面的查询用到了WHERE 子句和ORDERBY 子句,其中“ORDERBYPrice DESC”表示按价格从高到低排序。DESC是降序的意思,默认是ASC(升序)。 表3.3 例3.11查询结果 PID PName Price Category SID P0002 老人专用手机899 数码产品S001 P0005 流浪太阳65 书籍S003 P0004 数据库教材48 书籍S003 上面是关于单表查询的简单示例,GROUPBY 在后面聚集查询时再给例子 解释。 3.3.2 连接查询 <joined table> ::= <cross join> | <qualified join> | <left paren> <joined table> <right paren> <cross join> ::= <table reference> CROSS JOIN <table reference> SQL标准允许从单个表或多个表中查询,若用到多个表,就涉及连接查询(joined table)。 44 第3章 关系数据库标准语言SQL <qualified join> ::= <table reference> [ NATURAL ] [ <join type> ] JOIN <table reference> [ <join specification> ] 连接有两种类型,笛卡儿积(<crossjoin>)和限定连接(<qualifiedjoin>)。笛 卡儿积中的两个表(或是表的引用)用CROSSJOIN 连接。通常用得最多的是限定连 接,语法相对复杂一些。 <join type> ::= INNER | <outer join type> [ OUTER ] | UNION 连接类型(<jointype>)可以是内连接(INNER)、外连接(OUTER),外连接又 分左外连接(LEFT)和右外连接(RIGHT)以及并(UNION)。其中UNION 的用法示 例将在3.3.3节介绍。 <join specification> ::= <join condition> | <named columns join> <named columns join> ::= USING <left paren> <join column list> <right paren> <join condition> ::= ON <search condition> <joinspecification>描述两个表在哪个属性对上进行连接。 【例3.12】 用SQL描述Customers和Suppliers的笛卡儿积,结果如表3.4所示。 SELECT * FROM Customers CROSS JOIN Suppliers; 表3.4 例3.12查询结果 CID CName Customers.City SID SName Suppliers.City 1001 张三北京S001 华北手机厂北京 1002 李四广州S001 华北手机厂北京 1003 王五上海S001 华北手机厂北京 1001 张三北京S002 西北电子厂西安 1002 李四广州S002 西北电子厂西安 1003 王五上海S002 西北电子厂西安 1001 张三北京S003 未央印刷厂长安 1002 李四广州S003 未央印刷厂长安 1003 王五上海S003 未央印刷厂长安 3.3 基本SQL查询 45 从上面的结果可以看出,笛卡儿积通常没有实际意义。 【例3.13】 查询“张三”创建的所有订单,显示CID、CName、OID,以及 CreateTime,结果如表3.5所示。 SELECT Customers.CID,CName,OID,CreateTime FROM Customers INNER JOIN Orders ON Customers.CID = Orders.CID WHERE CName = '张三'; 表3.5 例3.13查询结果 CID CName OID CreateTime 1001 张三O001 2023-1-118:00:10 说明:在后续SQL标准,以及很多RDBMS产品实现中,也可以不显式地书写 JOIN,而是在WHERE 子句中增加一个连接条件。比如,例3.13也可以写成如下 形式: SELECT Customers.CID,CName,OID,CreateTime FROM Customers,Orders WHERE Customers.CID= Orders.CID AND CName = '张三'; 【例3.14】 查询供应商所供应的商品信息,显示供应商的全部信息以及其供应商 品的ID,要求将没有供应商品的供应商也显示出来,结果如表3.6所示。 SELECT Suppliers.*, PID FROM Suppliers LEFT OUTER JOIN Products ON Suppliers. SID = Products.SID; 因为要显示没有供应商品的供应商,上面采用了左外连接。假设存在一个ID 为 S004,位于延安,名为宝塔山印刷厂的供应商,其没有供应任何商品。 表3.6 例3.14查询结果 SID SName City PID S001 华北手机厂 北京P0002 S002 西北电子厂 西安P0003 S003 未央印刷厂 长安P0004 S003 未央印刷厂 长安P0005 S004 宝塔山印刷厂延安NULL 3.3.3 集合查询 <non-join query expression> ::= <non-join query term> |<query expression> UNION [ALL][<corresponding spec>]<query term> |<query expression> EXCEPT[ALL][<corresponding spec>]<query term> 46 第3章 关系数据库标准语言SQL 可以使用UNION 或EXCEPT将两个查询的结果合并起来,此时要求两个查询 结果有相同数量的列,且对应列的数据类型相同。UNION 表示将两个查询的结果合 并,EXCEPT则表示从第一个查询结果中删去第二个查询的结果。 【例3.15】 查询价格低于50或价格高于1500的商品信息,结果如表3.7所示。 SELECT * FROM Products WHERE Price <50 UNION SELECT * FROM Products WHERE Price > 1500 表3.7 例3.15查询结果 PID PName Price Category SID P0004 数据库教材48 书籍 S003 P0001 智能手机 1999 数码产品S001 P0003 平板电脑 1688 数码产品S002 3.3.4 空值查询 判断一个属性是否为空,不能用等号或不等号,而必须使用ISNULL或ISNOT NULL。 【例3.16】 查询类别为空值的商品。该查询结果为空,如表3.8所示。 SELECT * FROM Products WHERE Category IS NULL; 表3.8 例3.16查询结果 PID PName Price Category SID 3.3.5 聚集查询 在查询定义的<valueexpression>中,可以使用聚集函数,定义如下。 <set function specification> ::= COUNT <left paren> <asterisk> <right paren> | <general set function> <general set function> ::= <set function type> < left paren> [ < set quantifier> ] < value expression> < right paren> <set quantifier> ::= DISTINCT | ALL 根据定义,SQL 标准包含5 个聚集函数,分别是AVG、MAX、MIN、SUM 和 COUNT。可以指定DISTINCT,去掉重复值。默认是ALL,即所有值参与运算。 3.4 数据更新 47 <set function type> ::= AVG | MAX | MIN | SUM | COUNT 【例3.17】 查询最高的商品价格,结果如表3.9所示。 SELECT MAX(Price) AS 最高价格FROM Products; 表3.9 例3.17查询结果 最高价格 1999 【例3.18】 查询不同种类的商品的数量,结果如表3.10所示。 SELECT Category, COUNT(*) AS 数量 FROM Products GROUP BY Category; 表3.10 例3.18查询结果 Category 数 量 数码产品3 书籍 2 注意:本例用到了GROUPBY,先对结果分组,再将聚集函数分别作用于各组。 3.4 数据更新 数据更新指向数据库中插入数据,修改已有数据或删除已有数据,分别通过 INSERT、UPDATE或DELETE实现。 3.4.1 插入数据 <insert statement> ::= INSERT INTO <table name> <insert columns and source> <insert columns and source> ::= [ <left paren> <insert column list> <right paren> ] <query expression> | DEFAULT VALUES <insert column list> ::= <column name list> 插入语句以INSERTINTO 开始,后面是表的名称,之后可以加上“(<insert columnlist>)”,即插入的属性列。后面跟一个查询表达式,或是由VALUES 引出 的一组插入的值(即一个元组)。 48 第3章 关系数据库标准语言SQL 【例3.19】 在供应商表中新增一条记录,其供应商ID为S004,名称为宝塔山印 刷厂,城市为延安。 INSERT INTO Suppliers VALUES('S004','宝塔山印刷厂','延安') 上例中,由于省略了<insertcolumnlist>,后面VALUES包含的元组必须包含 Suppliers的全部属性,且按照定义该关系时所给属性列的顺序列出。 【例3.20】 新增一个关系TotalPrice,记录各订单的总价,然后根据Products和 OrderItems关系查询所有订单的总价。 CREATE TABLE TotalPrice( OID VARCHAR(32) PRIMARY KEY, TotalPrice DECIMAL ); INSERT INTO TotalPrice (OID, TotalPrice) SELECT OID, SUM(Quantity * Price * Discount) FROM Products INNER JOIN OrderItems ON Products.PID = OrderItems.PID GROUP BY OID; 上例中,先是创建了一个关系TotalPrice,包含OID和TotalPrice两个属性。接 下来,执行一个插入语句,该语句将里面的子查询结果插入TotalPrice中。该子查询 在Products和OrderItems之间做了连接,并按OID进行分组,最后计算每组(即每个 订单)的数量乘以价格再乘以折扣之和,即订单总价。 3.4.2 修改数据 <update statement: searched> ::= UPDATE <table name> SET <set clause list> [ WHERE <search condition> ] UPDATE语句有两类:一类是positioned版本,与游标相关;另一类是searched 版本。这里只介绍searched版本。UPDATE 语句必须指明要更新的表名,然后用 SET引出修改动作,后面可以加一个WHERE子句,指明对哪些元组进行更新。 【例3.21】 由于数码产品降价,将所有类别为数码产品的商品价格调整为原价 的90%。 UPDATE Products SET Price = Price * 0.9 WHERE Category = '数码产品'; 3.4.3 删除数据 <delete statement: searched> ::= DELETE FROM <table name> 3.5 视图 49 [ WHERE <search condition> ] 同样,DELETE语句也有positioned版本,与游标相关。此处只介绍searched版 本。DELETE语句很简单,只需要指明删除数据所在的表,可以加上WHERE语句, 指定要删除的元组。 【例3.22】 从OrderItems表中删除所有OID为'O001'的数据。 DELETE FROM OrderItems WHERE OID = 'O001'; 3.5 视图 3.5.1 定义视图 <view definition> ::= CREATE VIEW <view name> [ <left paren> <view column list> <right paren> ] AS <query expression> [ WITH [ <levels clause> ] CHECK OPTION ] 定义视图以CREATE VIEW 开始,后面视图名称,之后可以加上选择的属性列表。 若没有指定属性列表,则视图包含的属性就是之后的查询结果。之后由AS关键字引出 一个查询表达式。最后如果指定WITHCHECKOPTION,则视图应该是可以更新的。 通过该视图插入或更新后的元组需要满足视图定义。默认是CASCADED,即该视图和 其所依赖的视图都应满足此条件。若指定LOCAL,则只检查当前视图。 <levels clause> ::= CASCADED | LOCAL <view column list> ::= <column name list> 【例3.23】 定义一个视图,存放订单的总价。 CREATE VIEW view_TotalPrice AS SELECT OID, SUM(Quantity * Price * Discount) AS TotalPrice FROM Products INNER JOIN OrderItems ON Products.PID = OrderItems.PID GROUP BY OID; 上例中,定义了一个名为view_TotalPrice的视图,没有指定属性列表,因此其包 含后面查询的两个列,即OID和总价。注意:该视图包含了分组聚集的结果,因此是 不能更新的。 【例3.24】 定义一个视图,里面只存放所有类别是“数码产品”的商品信息。 CREATE VIEW view_DigitalProducts AS 50 第3章 关系数据库标准语言SQL SELECT * FROM Products WHERE Category = '数码产品' WITH CHECK OPTION; 上例中,定义了一个名为view_ DigitalProducts的视图,没有指定属性列表,因此其 包含后面查询的所有列。注意:该视图可以更新,也指定了WITHCHECKOPTION。 3.5.2 查询视图 查询视图与查询基础表的语法是相同的。 【例3.25】 根据已定义的视图view_TotalPrice,查询编号为'O001'的订单总价, 结果如表3.11所示。 SELECT * FROM view_TotalPrice WHERE OID = 'O001' 表3.11 例3.25查询结果 OID 总 价 O001 3148.15 3.5.3 更新视图 对于可更新的视图,其更新语法与更新基础表相同。 【例3.26】 根据视图view_ DigitalProducts进行更新,修改ID为P0001的商品, 将其价格降价100元,更新后查询视图view_ DigitalProducts,其结果如表3.12所示。 UPDATE view_DigitalProducts SET Price = Price - 100 WHERE PID ='P0001'; 表3.12 例3.26查询结果 PID PName Price Category SID P0001 智能手机1899 数码产品S001 3.6 本章小结 本章主要介绍了SQL,包括使用SQL定义数据、查询数据和更新数据,此外也介 绍了如何定义和使用视图。SQL 可以认为是RDBMS提供的标准接口,是用户与 RDBMS交互的最重要工具。因此本章是全书重点之一,建议读者仔细阅读SQL语 法,并在实际的RDBMS系统中反复练习,熟练掌握。 本章介绍的语法主要基于SQL92标准,RDBMS在具体实现时与该标准多少存 实验51 在一定差异。读者在进行上机练习时,一定要认真阅读RDBMS提供的SQL参考手 册。另外后续还有若干SQL标准,增加了很多新的特性,例如派生表等。读者可以根 据实际需要,学习相关资料。限于篇幅,本书不对新的特性予以展开讲解。 本章难点在于查询语句。SQL查询非常灵活,可以有WHERE 、ORDERBY 、 GROUPBY等多个子句。读者可以结合关系代数理解每个子句的语义,并通过上机 实验大量练习。 习题 1.了解TPCC性能测试,在关系数据库中创建一个名为TPCC的模式。 2.在TPCC模式中,使用SQL语句建立TPCC场景所包含的多个关系。 3.对2.1.3节介绍的“网上商城”数据库,使用SQL语句完成下面的查询。 (1)查询所有价格大于500元的数码产品,结果显示商品ID 、商品名称和价格,并 按价格降序排列。 (2)查询所有来自北京的顾客所下的订单,结果显示订单ID 、顾客姓名。 (3)查询所有数码产品的销量,结果显示商品ID 、商品名称、总销量。其中一个 商品的总销量表示其在所有订单中的数量之和。 (4)查询不同类别商品的种数,结果显示类别和该类别商品的种数。 (5)查询商品及供应商信息,结果显示商品名称、供应商ID和供应商名称。 (6)查询所有订单的实际销售额,结果显示订单ID 、顾客ID和实际销售额。其 中实际销售额计算方式为订单中每种商品的数量×价格×折扣之和。 4.修改“网上商城”数据库商品信息,将所有商品的价格下调10% 。 5.建立一个视图,包含订单ID 、顾客ID和订单总金额。 实验 读者可以选择一个合适的关系数据库管理系统,包括但不限于Oracle、SQL Server、MySQL 、OpenGaus 以及KingBaseES,认真阅读其SQL参考手册,完成上述 习题内容。读者也可以比较所选择RDBMS所支持的SQL语法与标准SQL语法的 差异。