视图 view
视图是通过对其他关系执行查询而定义的关系。
视图不存储在数据库中,但可以像实际存在的关系一样被查询。
关系表的数据存储在磁盘上,有时我们称之为 "基关系" 或 "基表"。
视图的定义(即 SQL 查询语句)存储在磁盘上。
当我们创建视图时,数据库管理系统(DBMS)只会保存视图的定义,并不会执行对应的SELECT语句来获取结果。视图的 SQL 查询语句仅在视图被使用时才会执行
sql
CREATE VIEW <view name>(<attribute list>)
AS <view definition>
创建视图示例
sql
派拉蒙影业(Paramount Studios)出品电影的片名和年份
CREATE VIEW ParamountMovies
AS SELECT title, year
FROM Movies
WHERE studioName = 'Paramount';
包含电影片名及其制片人姓名的视图
CREATE VIEW MovieProd (moveTitle, prodName)
AS SELECT title, name
FROM Movies, MovieExec
WHERE producerC = cert;
包含制片厂名称及其电影数量的视图
CREATE VIEW StudioMovNum (studioName, movieNo)
AS SELECT studioName, count(*)
FROM Movies
GROUP BY studioName;
删除视图
删除视图 ParamountMovies:
DROP VIEW ParamountMovies;
DROP VIEW 仅删除视图本身。
删除电影表 Movies:
DROP TABLE Movies;
DROP TABLE 不仅删除表,还会删除所有关联的视图。
修改视图
大多数情况下,视图不支持插入、删除和修改元组。
可更新视图
SQL 对视图修改的允许条件有明确规定:视图需通过从单个关系 R 中选择部分属性定义,且需满足以下 3 个要点:
- WHERE 子句不得包含涉及 R 的子查询。
- FROM 子句只能包含一个 R 的实例,不得包含其他关系。
- SELECT 子句的属性列表需包含足够的属性。视图的查询列(SELECT)必须包含 "能唯一确定基表 R 中一行" 的属性(通常是主键 / 唯一键)
插入示例:
Movies(title, year, length, genre, studioName, producerC#)
sql
创建可更新视图 ParamountMovies:
CREATE VIEW ParamountMovies
AS SELECT title, year
FROM Movies
WHERE studioName = 'Paramount';
向该视图插入元组(本质是插入到基表 Movies):
INSERT INTO ParamountMovies VALUES('Star Trek', 1979);
-- 等价于向基表插入:
INSERT INTO Movies(title, year) VALUES('Star Trek', 1979);
删除与更新示例
sql
1.删除视图 ParamountMovies 中片名包含 "Trek" 的
电影(本质是删除基表数据)
DELETE FROM ParamountMovies WHERE title LIKE '%Trek%';
-- 等价于删除基表数据:
DELETE FROM Movies
WHERE title LIKE '%Trek%' AND studioName = 'Paramount';
2.更新视图 ParamountMovies 中《星际迷航》(Star Trek the Movie)的年份:
UPDATE ParamountMovies
SET year = 1979
WHERE title = 'Star Trek the Movie';
-- 等价于更新基表数据:
UPDATE Movies
SET year = 1979
WHERE title = 'Star Trek the Movie' AND studioName = 'Paramount';
视图的替代触发器(INSTEAD-OF TRIGGERS)
创建视图 ParamountMovies:
CREATE VIEW ParamountMovies
AS SELECT title, year
FROM Movies
WHERE studioName = 'Paramount';
问题:向该视图插入元组时,DBMS 无法推断 studioName 属性的值应为 'Paramount'。
解决方案:为视图创建触发器
sql
CREATE TRIGGER Paramountinsert
INSTEAD OF INSERT ON ParamountMovies
REFERENCING NEW ROW AS NewRow
FOR EACH ROW
INSERT INTO Movies(title, year, studioName)
VALUES(NewRow.title, NewRow.year, 'Paramount');
触发器会拦截对视图的插入操作,并替换为对基表的插入操作。
索引
索引是基于关系中某一属性 A 的数据结构,可高效查找该属性值固定的元组。在大型关系上实现索引技术,是数据库管理系统(DBMS)实现中的核心环节。
索引与关键字的区别在于:关键字用于唯一标识元组,索引用于优化查询效率
一个问题:
查询 1990 年迪士尼(Disney)出品的电影:
SELECT * FROM Movies WHERE studioName = 'Disney' AND year = 1990;
假设电影表有 10,000 条元组,其中仅 200 条是 1990 年出品的。若能直接定位这 200 条元组并验证条件,查询效率会显著提升。所以我们可以为 year 属性创建索引。
再如:
查询《星球大战》(Star Wars)制片人的姓名:
SELECT name
FROM Movies, MovieExec
WHERE title = 'Star Wars' AND producerC# = cert#;
问题在于我们是否需要基于笛卡尔积进行全表扫描?
若为 Movies 表的 title 属性创建索引,可快速定位《星球大战》对应的元组。然后从该元组中提取 producerC#(制片人证书编号)。若为 MovieExec 表的 cert# 属性创建索引,可通过 producerC# 快速查找对应制片人的元组,进而获取姓名。若为两个关系创建索引,DBMS 仅需查找两个元组(每个关系各一个)。
联表属性上的索引可简化多关系查询。
创建与删除索引
创建索引
CREATE INDEX <索引名>
ON <表名> (<属性列表>);
删除索引语法
DROP INDEX <索引名>;
示例:
sql
为 Movies 表的 year 属性创建索引:
CREATE INDEX Yearindex ON Movies(year);
为 Movies 表的 title 和 year 属性创建复合索引:
CREATE INDEX Keyindex ON Movies(title, year);
删除 Yearindex 索引:
DROP INDEX Yearindex;
索引分类
按存储结构分类
B 树 / B + 树索引(B-Tree/B+Tree index)
哈希索引(Hash index)
位图索引(Bitmap index)
全文索引(Full-text index)
按应用层级分类
普通索引(Normal index)
唯一索引(Unique index)
复合索引(Composite index)
按数据物理存储顺序与关键字逻辑顺序的关系分类
聚集索引(Clustered index)
非聚集索引(Non-clustered index)
优缺点
优点
支持快速查找(亚线性时间复杂度)。
强制数据库约束(如 UNIQUE、PRIMARY KEY)。
排序后的关键字便于 GROUP BY 子句执行。
缺点
创建和维护索引需额外时间。
占用额外磁盘空间。
降低插入、更新和删除操作的性能。
程序中访问数据库的方式
嵌入式 SQL Embedded SQL
动态 SQL Dynamic SQL
存储过程 Stored Procedure
Transact-SQL(T-SQL)
SQL 并非设计为编程语言,为了在程序中访问数据库并对查询结果进行后续处理,需将 SQL 与编程语言(如 C/C++ 等)结合。
需要解决以下问题:
1.编程语言如何接收 SQL 语句?
2.编程语言与 DBMS 如何交换数据和消息?
3.DBMS 的查询结果为集合(bag),如何传递给编程语言的变量?
4.DBMS 与编程语言的数据类型可能不完全一致。
解决方案
嵌入式 SQL
最基础的方法,通过预编译将嵌入式 SQL 语句转换为内部库函数调用,实现数据库访问。
编程接口(Programming APIs)
直接为开发者提供一组库函数或动态链接库(DLLs),编译时与应用程序链接。
ODBC(开放式数据库连接,Open Database Connectivity)
JDBC(Java 数据库连接,Java Database Connectivity)
类库(Class Library)
面向对象编程(OOP)兴起后支持,将数据库访问库函数封装为一组类,简化编程语言中对数据库的操作。
嵌入式 SQL 的使用
基本规则
1.SQL 语句可直接在 C 程序中使用,以EXEC SQL开头,以;结尾。
2.通过**宿主变量(host variables)**在 C 语言和 SQL 之间传递信息:
(1)宿主变量需在EXEC SQL BEGIN DECLARE SECTION和EXEC SQL END DECLARE SECTION之间定义。
(2)SQL 语句中,宿主变量前需加:,以区分 SQL 自身的变量或属性名。
(3)在宿主语言(如 C)中,宿主变量可作为普通变量使用。
(4)宿主变量不可定义为数组或结构体。
3.特殊宿主变量 SQLCA(SQL 通信区):
需通过EXEC SQL INCLUDE SQLCA引入。通过SQLCA.SQLCODE判断执行结果状态。
4.使用指示器(short int 类型)处理宿主语言中的 NULL 值。
举例:
c
EXEC SQL BEGIN DECLARE SECTION;
char SNO[7];
char GIVENSNO[7];
char CNO[6];
char GIVENCNO[6];
float GRADE;
short GRADEI; /* GRADE的指示器,用于处理NULL */
EXEC SQL END DECLARE SECTION;
可执行语句示例
c
连接数据库:
EXEC SQL CONNECT :uid IDENTIFIED BY :pwd;
执行 DDL 或 DML 语句:
EXEC SQL INSERT INTO SC(SNO,CNO,GRADE)
VALUES (:SNO, :CNO, :GRADE);
执行查询语句(单条结果):
EXEC SQL SELECT GRADE
INTO :GRADE:GRADEI
FROM SC
WHERE SNO=:GIVENSNO AND CNO=:GIVENCNO;
游标(CURSOR)
用于处理返回多条元组的查询结果
c
1.定义游标:
EXEC SQL DECLARE <游标名> CURSOR FOR
SELECT ...
FROM ...
WHERE ...
2.打开游标(类似打开文件):
EXEC SQL OPEN <游标名>;
3.从游标中提取数据:
EXEC SQL FETCH <游标名>
INTO :hostvar1, :hostvar2, ...
4.判断游标结束:
当SQLCA.SQLCODE返回 100 时,表示已到结果集末尾。
5.关闭游标:
EXEC SQL CLOSE <游标名>;
游标查询示例
c
EXEC SQL DECLARE C1 CURSOR FOR
SELECT SNO, GRADE
FROM SC
WHERE CNO =:GIVENCNO;
EXEC SQL OPEN C1;
if(SQLCA.SQLCODE < 0) exit(1); /* 查询出错 */
while(1){
EXEC SQL FETCH C1 INTO :SNO, :GRADE:GRADEI;
if (SQLCA.SQLCODE == 100) break; /* 结果集结束 */
/* 处理提取的数据(省略) */
}
EXEC SQL CLOSE C1;
动态 SQL
嵌入式 SQL 的 SQL 语句需在编译前确定,而动态 SQL 支持程序运行时动态构建 SQL 语句,适用于 SQL 语句无法预先确定的场景。
SQL 标准及多数关系型数据库管理系统(RDBMS)均支持动态 SQL。
分类:
直接执行的动态 SQL Dynamic SQL executed directly
带动态参数的动态 SQL Dynamic SQL with dynamic parameters
查询类动态 SQL Dynamic SQL for query
直接执行的动态 SQL
仅适用于非查询类 SQL 语句(如 DELETE、INSERT、UPDATE)
c
EXEC SQL BEGIN DECLARE SECTION;
char sqlstring[200];
EXEC SQL END DECLARE SECTION;
char cond[150];
// 第一步:先拼 SQL 的固定部分
strcpy(sqlstring, "DELETE FROM STUDENT WHERE ");
printf("Enter search condition: ");
// 第二步:提示用户输入条件(比如让用户输入 SNO='001')
scanf("%s", cond);
// 第三步:把用户输入的条件拼到 SQL 后面,最终 sqlstring 是 "DELETE FROM STUDENT WHERE SNO='001'"
strcat(sqlstring, cond);
// 第四步:直接执行这个拼好的 SQL 字符串
EXEC SQL EXECUTE IMMEDIATE :sqlstring;
风险:用户输入啥就拼啥,容易 SQL 注入(比如用户输入 1=1; DROP TABLE STUDENT;,就会删库);
带动态参数的动态 SQL
适用于非查询类 SQL 语句,通过占位符实现动态参数(类似 C 语言宏处理)
c
EXEC SQL BEGIN DECLARE SECTION;
char sqlstring[200];
int birth_year;
EXEC SQL END DECLARE SECTION;
// 第一步:写 SQL 模板,:y 是占位符(不是 C 宏,是数据库的参数占位符)
strcpy(sqlstring, "DELETE FROM STUDENT WHERE YEAR(BDATE) <= :y; ");
// 第二步:提示用户输入年份
printf("Enter birth year for delete: ");
scanf("%d", &birth_year);
// 第三步:预处理 SQL 模板,给模板起个名字叫 purge
EXEC SQL PREPARE purge FROM :sqlstring;
// 第四步:执行预处理好的 purge,把 birth_year 的值传给占位符 :y
EXEC SQL EXECUTE purge USING :birth_year;
查询类动态 SQL
用于动态构建查询语句,需结合游标处理结果
c
// 1. 声明变量:
// sqlstring:存拼好的查询 SQL;SNO/GRADE:存取到的学号/成绩;
// GRADEI:GRADE 的指示变量(标记成绩是否为空);GIVENCNO:用户输入的课程号
EXEC SQL BEGIN DECLARE SECTION;
char sqlstring[200];
char SNO[7];
float GRADE;
short GRADEI;
char GIVENCNO[6];
EXEC SQL END DECLARE SECTION;
char orderby[150]; // 存用户输入的排序条件(比如 ORDER BY GRADE DESC)
// 第一步:拼查询 SQL 的固定部分(带参数占位符 :C)
strcpy(sqlstring, "SELECT SNO, GRADE FROM SC WHERE CNO= :C");
printf("Enter the ORDER BY clause: ");
scanf("%s", orderby);
// 第三步:把排序条件拼到 SQL 后面,最终 sqlstring
//是 "SELECT SNO, GRADE FROM SC WHERE CNO=:C
//ORDER BY GRADE DESC"
strcat(sqlstring, orderby);
// 第四步:提示用户输入课程号(比如 C001)
printf("Enter the course number: ");
scanf("%s", GIVENCNO);
// 第五步:预处理查询 SQL 模板,命名为 query
EXEC SQL PREPARE query FROM :sqlstring;
// 第六步:声明游标 grade_cursor,绑定到预处理好的 query 上(游标用来取结果)
EXEC SQL DECLARE grade_cursor CURSOR FOR query;
// 第七步:打开游标,把 GIVENCNO 的值传给占位符 :C,数据库开始执行查询
EXEC SQL OPEN grade_cursor USING :GIVENCNO;
if (SQLCA.SQLCODE < 0) exit(1); /* 查询出错 */
while (1){
// 取一行结果,放到 SNO 和 GRADE 里,GRADEI 标记成绩是否为空
EXEC SQL FETCH grade_cursor INTO :SNO, :GRADE:GRADEI;
if (SQLCA.SQLCODE == 100) break; /* 结果集结束 */
/* 处理提取的数据(省略) */
}
EXEC SQL CLOSE grade_cursor;
存储过程
存储过程是关系型数据库管理系统中可供应用程序调用的子程序。
可将常用的数据库访问逻辑编写为过程,编译后存储在数据库中,需用时直接调用。
优点
1.便捷性:直接调用,无需重复编写代码,可复用。
2.性能优化:存储过程已预编译,使用时无需再次解析和查询优化。
3.扩展 DBMS 功能:支持编写脚本实现复杂逻辑。
4.减少网络开销:客户端仅需发送调用指令,无需传输完整 SQL 语句。
5.安全性:可通过设置执行权限控制访问。
语法(以 T-SQL 为例)
sql
定义存储过程
CREATE PROCEDURE procedure_name
@parameter <数据类型> = 默认值,
@parameter <数据类型> = 默认值
AS
BEGIN
{SQL语句 | SQL语句块}
END
GO;
执行存储过程
EXEC procedure_name;
举例
sql
查询指定城市的客户
-- 1. 创建存储过程,命名为 SelectAllCustomersFromCity1
CREATE PROCEDURE SelectAllCustomersFromCity1
-- 定义输入参数:@City,类型是 nvarchar(30)
@City nvarchar(30)
AS
-- 存储过程的核心逻辑:查询 Customers 表中 City
-- 等于参数 @City 的所有行
SELECT * FROM Customers WHERE City = @City
GO;
-- GO 是 SQL Server 的批处理结束符,标记存储过程创建完成
-- 2. 调用(执行)这个存储过程,传入参数值:City = 'London'
EXEC SelectAllCustomersFromCity1 @City = 'London';
查询指定城市和邮政编码的客户:
CREATE PROCEDURE SelectAllCustomersFromCity2
@City nvarchar(30),
@PostalCode nvarchar(10)
AS
SELECT * FROM Customers
WHERE City = @City AND PostalCode = @PostalCode
GO;
EXEC SelectAllCustomersFromCity2 @City = 'London', @PostalCode = 'WA1 1DP';
Transact-SQL(T-SQL)
T-SQL 是微软(Microsoft)和赛贝斯(Sybase)对 SQL 的专有扩展,用于与关系型数据库交互。
扩展功能:
在 SQL 标准基础上增加:
过程式编程(procedural programming)
局部变量(local variables)
字符串处理、日期处理、数学运算等支持函数
DELETE 和 UPDATE 语句的增强功能
T-SQL 是使用 Microsoft SQL Server 的核心语言
变量声明与赋值
sql
声明变量:
DECLARE @var1 NVARCHAR(30);
赋值方式 1(SET):
SET @var1 = 'Some Name';
赋值方式 2(SELECT,从查询结果中取值):
SELECT @var1 = Name
FROM Sales.Store
WHERE CustomerID = 100;
流程控制
T-SQL 的流程控制关键字包括:BEGIN 和 END、BREAK、CONTINUE、GOTO、IF 和 ELSE、RETURN、WAITFOR、WHILE。
sql
示例:判断当前是否为周末并输出提示:
IF DATEPART(dw, GETDATE()) = 7 OR
DATEPART(dw, GETDATE()) = 1
BEGIN
PRINT 'It is the weekend.';
PRINT 'Get some rest on the weekend!';
END;
ELSE
BEGIN
PRINT 'It is a weekday';
PRINT 'Get to work on a weekday!';
END;
DELETE 和 UPDATE 语句的增强
T-SQL 中,DELETE 和 UPDATE 语句支持直接使用其他表的数据,无需子查询:
DELETE 语句:FROM 子句可包含关联表,需在 DELETE 和 FROM 之间指定要删除数据的表名或别名。
UPDATE 语句:支持添加 FROM 子句,待更新表可在 FROM 子句中关联(通过别名引用),或按标准 SQL 语法在语句开头指定。
sql
DELETE u
FROM users AS u
INNER JOIN user_flags AS f ON u.id = f.id
WHERE f.name = 'idle';