SQL 语言 - 数据库系统原理

SQL 发展历程

       从 1970 年美国 IBM 研究中心的 E.F.Codd 发表论文到 1974 年 Boyce 和 Chamberlin 把 SQUARE 语言改为 SEQUEL 语言,到现在的 SQL2、SQL3,SQL 一直都在不断完善和发展之中。SQL(结构化查询语言)虽然名为查询,但实际上具有定义、查询、更新和控制等多种功能。

 

SQL 数据库的体系结构

       SQL 数据库的体系结构也是三级结构,但术语与传统关系模型术语不同,在 SQL 中,关系模式称为“基本表”,存储模式称为“存储文件”,子模式称为“视图”,元组称为“行”,属性称为“列”。

  • SQL 数据库体系的结构要点如下:
  1. 一个 SQL 模式是表和约束的集合。
  2. 一个 SQL 表由行集构成,行是列的序列,每列对应一个数据项。
  3. 表有三种形式:基本表(实际存储在数据库中的表),导出表(执行了查询时产生的表),视图(若干基本表或其它视图构成的表的定义)。
  4. 一个基本表可以跨一个或多个存储文件,一个存储文件也可以放一个或多个基本表。存储文件与物理文件对应。
  5. 用户可以用 SQL 语句对视图和基本表进行查询等操作。
  6. SQL 的用户可以是应用程序,也可以是终端用户。

 

SQL 的组成

  • SQL 由四部分组成:
  1. 数据定义语言(SQL DDL):定义 SQL 模式、基本表、视图、索引。
  2. 数据操纵语言(SQL DML):包括数据查询和数据更新。
  3. 数据控制语言(SQL DCL):包括对基本表和视图的授权、完整性规则的描述、事务控制等。
  4. 嵌入式 SQL 的使用规定。

 

SQL 的数据定义

  • SQL 模式的创建和撤销

       SQL 模式的创建可简单理解为建立一个数据库,定义了一个存储空间,其句法是:CREATE SCHEMA <模式名> AUTHORIZATION <用户名>。

       撤销 SQL 模式的句法为:DROP SCHEMA <模式名> [ CASCADE | RESTRICT ],CASCADE 表示级联式方式,执行时将模式下所有基本表、视图、索引等元素全部撤销;RESTRICT 表示约束式,执行时必须在 SQL 模式中没有任何下属元素时方可撤销模式。

       “模式”这个名词学术味太重,大多数 DBMS 中不愿采用这个名词,而是采用“数据库”,而语句采用 CREATE DATABASE 和 DROP DATABASE。

 

SQL 的基本数据类型

  • 数值型:integer、smallint、real、double、precision、float(n)、numeric(p,d) 或 decimal(p,d)。
  • 字符串型:char(n)、varchar(n),前者定长,后者变长。
  • 位串型:bit(n)、bit varying(n),同上。
  • 时间型:date、time。

       各类型的值可以进行比较操作,但算术操作只限于数值型。两个日期类型的差是时间间隔类型(INTERVAL),日期加或减去 INTERVAL 可得到另一个日期。

 

基本表的创建、修改、撤销

       下面是基本表的创建,其中,完整性约束包括主键子句(PRIMARY KEY)、检查子句(CHECK)、外键子句(FOREIGN KEY):

CREATE TABLE T
(
    T#            CHAR(4)    NOT NULL,
    TNAME     CHAR(8)     NOT NULL,
    TITLE         CHAR(10),
    
    PRIMARY KEY (T#)
)
 
CREATE TABLE C
(
    C#                CHAR(4)    NOT NULL,
    CNAME         CHAR(8)     NOT NULL,
    T#                 CHAR(10),
    
    PRIMARY    KEY (C#),
    FOREIGN    KEY (T#)    REFERENCES    T(T#)
)

       基本表的修改句法如下,需注意,新增的列不能定义为 NOT NULL。

ALTER TABLE <基本表名> ADD <列名> <类型>
ALTER TABLE <基本表名> DROP <列名> [ CASCADE | RESTRICT ]
ALTER TABLE <基本表名> MODIFY <列名> <类型>
DROP TABLE <基本表名>     [ CASCADE | RESTRICT ]

       索引的创建和撤销:

CREATE [UNIQUE] INDEX <索引名> ON <基本表名> (<列名序列> [ASC][DESC])
DROP INDEX <索引名>

 

SQL 的数据查询

  • SELECT 语句完整的句法:
SELECT <列名表(逗号隔开)> FROM <基本表或视图> [ WHERE <行条件表达式> ] [ GROUP BY <列名序列> ] [ HAVING <组条件表达式> ] [ ORDER BY < 列名 [ ASC | DESC ]> ]
  • SELECT 语句中的限制和规定:
  1. 要求输出表格中不出现重复元组,则在 SELECT 后 加 DISTINCT 选项。
  2. 星号 * 是对于在 FROM 子句中命名表的所有列的简写。
  3. 列表达式是对于一个单列求聚合值的表达式。
  4. 表达式中允许出现加减乘除及列名、常数的算术表达式。
  5. 同一基本表在 SELECT 语句中多次引用时 用 AS 来增加别名。
  6. 查询结构的结构完全一致时,可以将两个查询进行并(UNION)、交(INTERSECT)、差(EXCEPT)操作。
  7. WHERE 子句可以用 BETWEEN … AND … 限定一个值的范围。
  8. WHERE 子句中字符串匹配用 LIKE 和百分号、下划线。
  9. 查询空值操作时用 IS NULL 来测试。
  10. 集合成员比较用 IN / NOT IN,集合成员算术比较用 <元组> θ SOME | ALL | ANY。
  • 嵌套查询的改进写法:
  1. 导出表的使用:如果在 FROM 子句中使用子查询,则要给子查询的结果起个表名和相应的列名。
  2. WITH 子句和临时视图:SQL3 允许用户用 WITH 子句定义一个临时视图(即子查询),置于 SELECT 语句的开始处。而临时视图本身是用 SELECT 语句定义的。
  • 例题:对于教学数据库的三个基本表如下,试用 SQL 的查询语句表达下列查询:
  1. S(S#,SNAME,AGE,SEX)
  2. SC(S#,C#,SCORE)
  3. C(C#,CNAME,TEACHER)      

(1)检索学号为 S3 的学生所学课程的课程名与任课教师名。

SELECT CNAME,TEACHER FROM C WHERE C# IN (SELECT C# FROM SC WHERE S# = 'S3')

SELECT CNAME,TEACHER FROM SC,C WHERE SC.C# = C.C# AND S# = 'S3'

(2)检索至少选修 LIU 老师所授课程中一门课程的女学生姓名。

SELECT SNAME FROM S WHERE SEX = '女' AND S# IN (SELECT S# FROM SC WHERE C# IN (SELECT C# FROM C WHERE TEACHER = 'LIU'))

SELECT SNAME FROM S,SC,C WHERE SEX = '女' AND S.S# = SC.S# AND SC.C# = C.C# AND TEACHER = 'LIU'

(3)检索 WANG 同学不学的课程的课程号。

SELECT C# FROM C WHERE C# NOT IN (SELECT C# FROM SC WHERE S# = (SELECT S# FROM S WHERE SNAME='WANG'))

(4)检索至少选修两门课程的学生学号。

SELECT S# FROM SC GROUP BY S# WHERE COUNT(S#) >= 2

SELECT DISTINCT S# FROM SC AS X, SC AS Y WHERE X.S# = Y.S# AND X.C# != Y.C#

(5)检索全部学生都选修的课程的课程号与课程名。

SELECT C#,CNAME FROM C WHERE C.C# IN (SELECT C# FROM SC GROUP BY C# WHERE COUNT(C#) = SELECT COUNT(S#) STUCOUNT FROM S GROUP BY S#)

SELECT C#,CNAME FROM C WHERE NOT EXISTS (SELECT * FROM S WHERE NOT EXISTS (SELECT * FROM SC WHERE S# = S.S# AND C# = C.C#))

 

SQL 的数据更新

  • 数据插入

       INSERT INTO <基本表名> [(<列名序列>)] VALUES (<元组值>) // 注:元组值可以连续插入

       INSERT INTO <基本表名> [(<列名序列>)] <SELECT 查询语句>

  • 数据删除

       DELETE FROM <基本表名> [ WHERE <条件表达式>]

  • 数据更新

       UPDATE <基本表名> SET <列名>=<值表达式>, [ <列名>=<值表达式> ] [ WHERE <条件表达式> ]

 

视图

  • 视图的创建

       CREATE VIEW <视图名> (<列名序列>) AS < SELECT 查询语句 >

  • 视图的撤销

       DROP VIEW <视图名>

  • 视图的更新

       对视图(虚表)的查询与基本表一致,但更新操作受到下面三条规则的限制:      

  1. 如果视图是从多个基本表使用连接操作导出的,则不允许更新。
  2. 如果导出的视图使用了分组和聚合操作,不允许更新。
  3. 如果视图是从单个基本表只使用选择和投影操作导出的,且包括了基本表的主键或某个候选键,则可以执行更新操作。(这就相当于在基本表上操作)

 

嵌入式 SQL

  • SQL 语言的使用方式:

       在主语言(如C,Java,C# 等)中使用的 SQL 结构称为 嵌入式 SQL。在终端交互方式下使用,称为 交互式 SQL

       存储设备商的数据库是用 SQL 语句存取的,数据库和主语言程序间信息的传递是通过共享变量实现的,这些共享变量要用 SQL 的 DECLARE 语句说明。SQL2 规定,SQL_STATE 是一个特殊的共享变量,起着解释 SQL 语句执行状况的作用。它由 5 个字符组成,当一个 SQL 语句执行成功时,系统自动给它赋上全零值(“00000”)表示未发生错误;否则其值为非全零,表示发生的各种错误情况。程序可以根据 SQL_STATE 的值转向不同的分支,以控制程序的流向。

  • 嵌入式 SQL 的实现方式:
  1. 扩充主语言的编译程序,使之能处理 SQL 语句。
  2. 采用预处理方式。(多数系统采用这种方式)
  • 嵌入式 SQL 的使用规定:
  1. 在程序中区分 SQL 语句与主语言语句。SQL 语句前加前缀 EXEC SQL,并以 END_EXEC 作为结束标志(结束标志在不同的主语言中不同)。
  2. 允许嵌入的 SQL 语句引用主语言的程序变量。这些变量前需要加冒号“:”作前缀标识,并由 SQL 的 DECLARE 语句说明。
  3. SQL 的集合处理方式与主语言单记录处理方式之间的协调。需要用游标(cursor)机制,把集合处理转换成单记录处理方式。

例如,在 C 语言中可用下列形式声明共享变量。下面四行组成一个说明节,3 个共享变量,SQL_STATE 长度是 6 而不是 5 是因为 C 语言规定字符串变量值应有结束符 “\0”。

EXEC SQL BEGIN DECLARE SECTION;
    CHAR sno[5], name[9];
    CHAR SQL_STATE[6];
EXEC SQL END DECLARE SECTION;

 

嵌入式 SQL 的使用技术

       SQL DDL 语句,只要加上前缀 EXEC_SQL 和结束标志 END_EXEC 就能嵌入在主语言中使用。但 SQL DML 语句在嵌入使用时,要注意是否使用了游标机制。

  • SQL DML 语句的嵌入使用(不涉及游标)

       由于增删改语句不返回数据结果,因此只需要加上前后缀就可以在主语言中使用。而对于 SELECT 语句,如果已知查询结果是单元组时,在加上前后缀后,还应再增加一个 INTO 子句,指出找到的值应送到共享变量中去。

例1. 在基本表 S 中,根据共享变量 givensno 的值检索学生的姓名、年龄、性别。“:”作为共享变量的前缀,与数据库中变量的作区别。程序已预先给出 givensno 的值,而 SELECT 查询结果将送到变量 sn、sa、ss 中。

EXEC SQL SELECT sname, age, sex INTO :sn, :sa, :ss FROM s WHERE s# = :givensno;

例2. 在基本表 S 中插入一个新学生,诸属性值已在共享变量中。

EXEC SQL INSERT INTO s (s#, sname, age) VALUES (:givesno, :sn, :sa);

例3. 根据学生的姓名共享变量 sn,删除一个学生的成绩。

EXEC SQL DELETE FROM SC WHERE s# = (SELECT s# FROM S WHERE SNAME = :sn);

例4. 把课程名为 MATHS 的成绩增加某个值,该值在 raise 中已给出。

EXEC SQL UPDATE SC SET SCORE = SCORE + :raise WHERE C# = (SELECT C# FROM C WHERE CNAME = 'MATHS');

  • SQL DML 语句的嵌入使用(涉及游标)

       当查询结果是多个元组时,此时主语言程序无法使用,一定要使用游标机制把多个元组一个一个地传送给主语言程序使用。

       具体过程:

  1. 先定义一个游标与某个 SELECT 语句对应。
  2. 游标用 OPEN 语句打开后,处于活动状态,此时,游标指向查询结果第一个元组之前。
  3. 每执行一次 FETCH 语句,游标指向下一个元组,并把其值送到共享变量,供程序处理。如此反复,直到所有查询结果处理完毕。
  4. 最后使用 CLOSE 语句关闭游标。关闭的游标可以被重新打开,与新的查询结果相联系,但在没有被打开后前,不能使用。

例1. 在基本表 SC 中检索某学生(学号由共享变量 givensno 给出)的学习成绩信息(S#, C#, SCORE)。

#define NO_MORE_TUPLES    ! (strcmp (SQLSTATE, "02000"))
void sel()
{
    EXEC SQL BEGIN DECLARE SECTION;
        char sno [5], cno [5], givensno [5];
        int g;
        char SQL_STATE [6];
    EXEC SQL END DECLARE SECTION;
    
    scanf ("%s", givensno);
    
    EXEC SQL DECLARE scx CURSOR FOR
        SELECT S#, C#, SCORE FROM SC WHERE S# = :givensno;
    EXEC SQL OPEN scx;    
    while(1)
    {
        EXEC SQL FETCH FROM scx INTO :sno, :cno, :g;
        if (NO_MORE_TUPLES) break;
        print("%s, %s %d", sno, cno, g);
    }
    EXEC SQL CLOSE scx;
}

 

卷游标的定义和推进

       之前的游标在推进时只能一行行推进,且不能返回,这给使用带来了一定的麻烦。SQL2 提供了卷游标(scroll cursor)技术解决这个问题,卷游标可以进退自如。

  • 卷游标的定义语句
EXEC SQL DECLARE scx SCROLL CURSOR FOR <SELECT 子句>
  • 卷游标的推进句法:NEXT(进一行)、PRIOR(退一行),FIRST(定位到查询结果第一行),LAST(定位到查询结果最后一行),RELATIVE n(n 可为正负数,表示相对当前游标位置,推进或后退 n 行),ABSOLUTE n(绝对定位,正数表示查询结果从上往下第 n 行,负数表示从下往上第 n 行)。
EXEC SQL FETCH NEXT FROM <游标名> INTO <变量表>
EXEC SQL FETCH PRIOR FROM <游标名> INTO <变量表>
EXEC SQL FETCH LAST FROM <游标名> INTO <变量表>
EXEC SQL FETCH RELATIVE -3 FROM <游标名> INTO <变量表>
EXEC SQL FETCH ABSOLUTE 5 FROM <游标名> INTO <变量表>

 

动态 SQL 语句

       有时,在实际问题中,源程序往往还不能包括用户的所有操作,用户对数据库的操作需要在系统运行时才能提出来,这就要使用嵌入式 SQL 的动态技术才能实现。

  • 动态 SQL 预备语句
EXEC SQL PREPARE <动态SQL语句名> FROM <共享变量或字符串> 
  • 动态 SQL 执行语句
EXEC SQL EXECUTE <动态SQL语句名> 

 

存储过程

       存储过程 是使用 SQL 语句和流程控制语句编写的模块,存储过程经编译和优化后存储在数据库服务器端的数据库中,使用时调用即可。

  • 存储过程具有以下优点:
  1. 提高运行速度。运行之前,数据库会分析其语法及语义,给出优化执行方案;调用时,其执行 SQL 语句的大部分准备工作已经完成,所以执行速度很快。相比之下,交互执行的 SQL 语句是解释执行的,速度较慢。
  2. 增强了 SQL 的功能和灵活性。因为有流程控制语句,灵活性很高,能完成复杂的逻辑和运算。
  3. 可以降低网络的通信量。存储过程保存在服务器端,可想而知,传输一大堆 SQL 语句和只传输一个调用的存储过程名之间的带宽使用率是完全不同的。
  4. 减轻了程序编写的工作量。减轻的关键是存储过程可被不同程序重用。
  5. 间接实现安全控制功能。例如,不允许用户直接使用某些表和视图,但可以使用存储过程完成允许范围内的信息查询。
  • 存储过程的声明
CREATE PROCEDURE <存储过程名> (<参数>)
<局部声明>
<函数体>; -- 局部声明 和 参数 是可选的。
-- 通常,每个参数应有一个 SQL 数据类型,还应有一个参数模式:IN(输入值)、OUT(输出值),INOUT(可输入可输出)

       也可声明一个函数,但必须给出返回类型(函数的限制比较多,相对于存储过程函数比较鸡肋!):

CREATE FUNCTION <函数名> (<参数>)
RETURNS <>
<局部声明>
<函数体>; 

 

SQL / PSM

       SQL / PSM 是 SQL 标准的一部分,指定了如何编写持久存储模块,提供流程控制语句来表示存储过程的应用逻辑。

  • 有三种基本结构:
  1. 顺序执行。
  2. 条件分支语句。
  3. 循环语句。
IF <条件> THEN <语句列表>
    ELSEIF <条件> THEN <语句列表>
    ......
    ELSEIF <条件> THEN <语句列表>
    ELSE <语句列表>
END IF;
-- 标准的 WHILE 循环结构
WHILE <条件> DO
    <语句列表>
END WHILE
 
-- 标准的 REPEAT 循环结构 
REPEAT
    <语句列表>
UNTIL <条件>
END REPEAT;
 
-- 基于游标的循环结构
FOR <循环名> AS <游标名> CURSOR FOR <查询> DO
    <语句列表>
END FOR

你可能感兴趣的:(SQL 语言 - 数据库系统原理)