目录
第3章:关系数据库标准语言 SQL
基于SQLServer学习使用,与MySQL有略微差别!
3.1、SQL概述
3.1.1、历史
- 1974年,由Boyce和Chamber提出。
- 1975-1979年,在由IBM的San Jose研究室研制的System R上实现,称为Sequel,现在称为SQL (Struceured Query Languang)。
- 1986年10月,美国国家标准局(American National Standard Institute,简称ANSI)的数据库委员会批准了SQL作为关系数据库语言的美国标准。
- 1987年,国际标准组织(International Organization for Standardization,简称ISO)也通过了这一标准。
3.3.2、SQL语言的功能
SQL是一个综合的、通用的、功能极强的关系数据库语言,它具有四个方面的功能:
- 数据定义(Data Definition)
- 数据查询(Data Query)
- 数据操纵(Data Manipulation)
- 数据控制(Data Contro1)
3.3.3、SQL的特点
1、综合统一
- 集DDL、DML、DCL功能于一体。
- 可以独立完成数据库生命周期中的全部活动
- 建立数据库,定义关系模式,插入数据;
- 对数据库中的数据进行查询和更新;
- 数据库重构和维护
- 数据库安全性、完整性控制等
2、高度非过程化
- 用户只需提出“做什么”,无须告诉“怎么做”,不必了解存取路径。
存取路径的选择由系统自动完成。
3、面向集合的操作方式
- 一次一集合。
4、以同一种语法结构提供两种使用方式
- SQL是自含式语言
- SQL是嵌入式语言
5、语言简洁,易学易用
- SQL语言完成核心功能只用9个动词,语法接近英语口语。
SQL语言支持数据库的三级模式结构:
- 所有基本表←→模式
- 部分视图和部分基本表←→外模式,
- 所有存储文件←→内模式
3.3.4、基本概念
- 基本表(Base Table)是本身独立存在的表,每个(多个)基本表对应一个存储文件,一个表可以带若干索引。
- 视图(View)是从一个或多个基本表中导出的表,数据库中只存放视图的定义而不存放视图对应的数据,可以将其理解为一个虚表。用户可以在视图上再定义视图
- 存储文件的逻辑结构组成了关系数据库的内模式。
3.2、学生-课程数据库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X0SlKt4U-1588934201045)(http://cdn.rainszj.com/学生-课程.png)]
student(sno, sname, ssex, sage, sdept)
course(cno, cname, cpno, ccredit)
cpno为外码
sc(sno, cno, grade)
sno为外码,cno为外码
3.3、数据定义
3.3.1、使用数据库
- 创建数据库:create database <数据库名>
CREATE DATABASE test;
- 使用数据库:use <数据库名>
use test;
- 删除数据库:drop database <数据库名>
drop database test;
在SQLServer中,一个正在使用的数据库是不能删除的;
在MySQL中,一个正在使用的数据库是可以删除的;
SQLServer数据库中有哪几种文件组成?
1、主数据文件:*.mdf
- 每个数据库有且只有一个主数据文件,它是数据库和其他数据文件的起点,扩展名一般为 .mdf
2、次数据文件:*.ndf
- 用于存储主数据文件中未存储的剩余资料和数据库对象;
- 一个数据库可以没有次要数据文件,但也可以同时拥有多个次要数据文件;
- 次要数据文件的多少主要根据数据库的大小、磁盘存储情况和存储性能要求而设置;
- 扩展名一般为:.ndf
3、日志文件:*.ldf
- 存储数据库的事务日志信息,当数据库损坏时,管理员使用事务日志恢复数据库;
- 扩展名一般为:.ldf
每个数据库中至少有两个文件:主数据文件、日志文件
MySQL数据库中有哪几种文件组成?
3.3.2、SQLServer中的数据类型
3.3.3、基本表的建立(create)
语法:
create table <表名> (
<列名> <数据类型>,
<列名> <数据类型>,
……
<列名> <数据类型>
);
注意:建表时,最后一个列不能加 *逗号(,)*
以下代码在SQLServer中执行可以,列级完整性约束中的 constraint 在MySQL中无法执行!
列级完整性约束:
CREATE TABLE course(
cno CHAR(1) CONSTRAINT PK_course_cno PRIMARY KEY,
cname varchar(20) CONSTRAINT UQ_course_cname UNIQUE,
cpno CHAR(1),
ccredit SMALLINT
);
表级完整性约束:
CREATE TABLE course (
cno CHAR(1),
cname VARCHAR(20),
cpno CHAR(1),
ccredit SMALLINT,
CONSTRAINT PK_course_cno PRIMARY KEY(cno),
CONSTRAINT UQ_course_cname UNIQUE(cname)
);
- 如果完整性约束条件涉及到该表的多个属性列,完整性约束条件必须定义在表级上
- 如果完整性约束条件只涉及到该表的一个属性列,完整性约束条件既可以定义在列级上也可以定义在表级上。
约束名:
- 每一个约束都有一个名字,称为约束名。
- 约束名要全数据库唯一。
- 在定义约束的时候,如果没有指定名字,系统默认给定一个名字。
- 在定义约束的时候,用户可以显式指定约束名,方法:
constraint <约束名> <具体约束>
常用约束:
- 主码约束:PK_
- 唯一性约束:UQ_(某一列有了唯一性约束之后,在该列只允许一个值为null)
- 默认值约束:DF_(只能定义在列级上)
- 参照完整性约束:FK_
- CHECK约束:CK_
默认值约束举例:
create table student(
sno char(5),
sname varchar(20) not null,
sage smallint constraint DF_student_sage default(20),
constraint PK_student_sno primary key(sno)
);
参照完整性(外码)约束举例:
CREATE TABLE course (
cno CHAR(1),
cname VARCHAR(20),
cpno CHAR(1),
ccredit SMALLINT,
CONSTRAINT PK_course_cno PRIMARY KEY(cno),
CONSTRAINT UQ_course_cname UNIQUE(cname),
constraint CK_course_ccredit check(ccredit >0),
constraint FK_course_cpno foreign key(cpno) references course(cno)
);
注意:外码的数据类型必须和相应的主码的数据类型保持一致
CHECK约束举例:
CREATE TABLE course (
cno CHAR(1),
cname VARCHAR(20),
cpno CHAR(1),
ccredit SMALLINT,
CONSTRAINT PK_course_cno PRIMARY KEY(cno),
CONSTRAINT UQ_course_cname UNIQUE(cname),
constraint CK_course_ccredit check(ccredit > 0)
);
3.3.4、基本表的修改(add、alter、drop)
add方式:用于增加新的列和完整性约束
语法:
alter table <表名> add <列定义> | <完整性约束定义>
- <列定义> 格式为:<列名> <数据类型> [ null | not null ]
- <完整性约束定义>格式为:constraint <约束名> <具体约束>
添加属性scardid到student,scardid取值不重复
-- 方式一
alter table student add scardid char(18);
alter table stuent add constraint UQ_student_scardid unique(scardid);
-- 方式二
alter table student add scardid char(18) constraint UQ_student_scardid unique;
在sc表中增加完整性约束定义,使grade在0~100之间。
-- 方式一
ALTER TABLE sc ADD CONSTRAINT CK_sc_grade CHECK(grade >= 0 AND grade <= 100);
-- 方式二
ALTER TABLE sc ADD CHECK(grade BETWEEN 0 AND 100);
在course表中为ccredit增加默认约束,约束值为2。
-- SQLServer中
ALTER TABLE course ADD CONSTRAINT DF_course_ccredit DEFAULT(2) FOR ccredit;
-- MYSQL中
ALTER TABLE course MODIFY ccredit SMALLINT DEFAULT 2;
alter方式:只能用于修改某些列定义
语法:
alter table <表名> alter column <列定义>;
- <列定义> 格式为:<列名> <数据类型> [ null | not null ]
- alter 方式只能修改列的定义,不能修改约束,约束只能增加或删除
注意:
-
使用 alter 方式修改列的定义时,不能将含有空值的列的定义修改为 not null
-
不能修改列名
drop方式:用于删除某些列以及某些约束
语法:
alter table <表名> drop column <列名>;
alter table <表名> drop constraint <约束名>;
举例:
-- 根据约束名删除约束
alter table student drop constraint UQ_student_scardid;
-- 删除列
alter table student drop column sdept;
3.3.5、基本表的删除(drop)
语法:
drop table <表名>
注意:
在sqlserver中删除基本表后,基本表的定义、表中数据、索引、约束都将被删除。
在sqlserver中删除基本表后,由此表导出的视图仍然保留,但用户引用会出错。
在sqlserver中,如果有另外一个表的外码参照该表的主码,则不允许删除该基本表。
3.3.6、索引的建立和删除
索引建立的目的:提高查询速度,但会降低插入数据和修改数据的速度
索引的种类:
1、聚集索引
- 表数据按照索引的顺序来存储,也就是说索引项的顺序与表中记录的物理顺序一致;
- 对于聚集索引,节点即存储了真实的数据行,不再有另外单独的数据页;
- 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种;
2、非聚集索引
- 表数据存储顺序与索引顺序无关;
- 对于非聚集索引,节点包含索引字段值及指向数据页数据行的逻辑指针;
3、唯一索引
- 每一个索引值只对应唯一的数据记录
创建索引:
语法:
create [unique] [clustered] index <索引名>
on <表名>(<列名>[<次序>][,<列名>[<次序>]]…);
注意:
一个表中的索引名需要唯一,多个表中的索引名可以重名;
一个索引可以建立在该表的一列或者多列上,各列名之间用逗号分隔;
用次序指定索引值的排列次序,升序:ASC,降序:DESC,默认是ASC
举例:
create table t2(
c1 int constraint PK_t2_c1 primary key, -- 自动创建聚集唯一性索引
c2 int constraint UQ_t2_c2 unique -- 自动创建非聚集的唯一性索引
);
-- 在SQLServer中执行失败,因为SQLServer会根据表中的主码默认自动创建一个聚集索引
create clustered index ID_c2 on t2(c2);
删除索引
语法:
drop index <表名>.<索引名>
举例:
create table t1(
c1 int,
c2 int
);
-- 创建索引
create clustered index ID_t1_c1 on t1(c1);
-- 删除索引
drop index t1.ID_t1_c1;
索引的有关说明:
- 一个表中可以有多个索引,索引可以提高查询效率,但索引过多耗费空间,且降低了插入、删除、更新的效率。
- 应该在使用频率高的、经常用于连接的列上建索引。
- 可以动态地定义索引,即可以随时建立和删除索引。
3.4、数据查询
3.4.1、单表查询
单表查询的语法:
select [all|distinct] <目标列表达式> [as] [别名]
[ ,<目标列表达式> [as] [别名] ]…
from <表名或视图名>
[where <条件表达式>]
[order by <列名> [ASC|DESC] ]
1、通过select查询指定的列:
- select 子句的<目标列表达式>可以为:算术表达式、函数、属性列名
-- 查询学生的姓名、年龄、出生日期 SQLServer中有该三个参数的库函数
select sname name, Datediff(YEAR, borndate, GETDATE()) age, borndate from Student
-- 查询学号、课程号、考试时间、分数、分数+10
select sno, cno, examdate, grade, grade + 10 from sc
-- 查询学号、课程号
select sno, cno from sc
-- 不写,默认为 all,【all】展示该列的所有行
select all sno, cno from sc
-- 通过 【distinct】 关键字将查询出的结果进行【去重】,去除重复行
select distinct sno, cno from sc
-- 通过 as 可以为目标列表达式起别名,改变查询结果的列标题,as也可以省略,用空格代替
select sno as 学号, cno 课程号 from sc;
2、通过select查询所有属性列:
-- 查询所有学生的信息。
-- 在SELECT关键字后面列出所有列名
SELECT Sno, Sname, Ssex, Sage, Sdept FROM Student;
-- 把SELECT关键字后面指定为 *
select * from student;
where子句,相当于关系代数中的选择
3、比较大小运算符:=、>、 <、>=、 <=、<>(不等于、!=)、!>、!<
-- 查询学号不等于 95001 的学生的信息
select * from student where sno <> '95001'
-- 查询年龄小于 35的学生的信息
select * from student where Datediff(YEAR, borndate, GETDATE()) < 35
-- 查询有课程不及格的学生的学号,必须进行【去除重复行】
select distinct sno from sc where grade < 60
4、确定范围
- X between A and B(在A和B之间,包含A和B)
- X not between A and B(不在A和B之间)
-- 查询年龄 在 [35,40] 的学生的学号、姓名、年龄
select sno, sname,Datediff(YEAR, borndate, GETDATE()) age from student where Datediff(YEAR, borndate, GETDATE()) between 35 and 40
-- 查询年龄 不在 [35,40] 的学生的学号、姓名、年龄
select sno, sname,Datediff(YEAR, borndate, GETDATE()) age from student where Datediff(YEAR, borndate, GETDATE()) not between 35 and 40
4、确定集合
- X in ( A, B, … )
- X not in ( A, B, … )
-- 查询信息系或者数学系学生的信息
select * from student where sdept in('信息系', '数学系')
-- 查询不在信息系或者数学系学生的信息
select * from student where sdept not in('信息系', '数学系')
5、字符匹配
- X like '<字符串>' [esacape '<换码符>']
- X not like '<字符串>' [esacape '<换码符>']
- X 必须是字符串
- % :代表任意长度(长度可以为0)的字符串
- _ :代表任意单个字符
-
[ ]:指定集合或范围中的任何单个字符。
- 集合:c[adf]n,表示can、cdn、cfn
- 范围: [C-P]arsen, 表示以C 与 P 之间的任何单个字符开始的以'arsen'结尾的字符,例如,Carsen、Larsen、Karsen 等。
- distinct:去除重复行
- escape '<标识字符>':不转义标识字符后面那个字符
-- 查询学号为95001的学生的信息,完全匹配
select * from student where sno = '95001'
-- 查询学号为95001的学生的信息,模糊查询
select * from student where sno like '95001'
-- 查询所有姓刘的学生的所有信息
select * from student where sname like '刘%'
-- 查询所有姓【刘%】的学生的所有信息
select * from student where sname = '刘%'
-- 查询所有姓刘的且姓名为3个字的学生的信息
select * from student where sname like '刘__'
-- 查询所有姓刘的且姓名为2个字的学生的信息
select * from student where sname like '刘_'
-- 查询名字中第2个字为“阳”字的学生的姓名和学号
SELECT sname, Sno FROM student WHERE sname LIKE '_阳%'
-- 查询所有姓王或者姓李的学生的姓名和学号
SELECT sname, Sno FROM student WHERE sname LIKE '[王李]%'
-- 查询DB_Design课程的所有信息,完全匹配
select * from course where cname = 'DB_Design'
-- 查询前缀为 DB_ 的所有课程的所有信息,模糊查询
select * from course where cname like 'DB_%' -- 【错误】
select * from course where cname like 'DB\_%' escape '\' -- 【正确】
select * from course where cname like 'DB!_%' escape '!' -- 【正确】
6、涉及空值的查询
- 使用谓词 is null 或 is not null 来测试指定列的值是否为空值。
“is null” 不能用 “= null” 代替,
“is not null” 不能用 “!= null” 代替
select * from course where cpno is null
select * from course where cpno is not null
7、逻辑运算符:not、and、or
- and 的优先级高于or,可以用括号改变优先级
-- 查询计算机系并且年龄小于35的学生的信息
select * from student where sdept = '计算机系' and Datediff(YEAR, borndate, GETDATE()) < 35
-- 查询计算机系或者年龄小于35的学生的信息
select * from student where sdept = '计算机系' or Datediff(YEAR, borndate, GETDATE()) < 35
-- 查询不是计算机系的学生的信息
select * from student where not sdept = '计算机系'
8、order by 子句
- 可以对查询结果按一个或多个属性列排序
- 升序:ASC;降序:DESC;缺省值为升序
- 当排序列含空值时
- ASC:排序列为空值的元组最先显示
- DESC:排序列为空值的元组最后显示
-- 查询学生课程表中的所有信息,按照考试时间升序、成绩降序排序
select * from sc order by examdate asc, grade desc
-- 查询课程表中的所有信息,按照先行课升序【默认】排序
select * from course order by cpno
-- 查询课程表中的所有信息,按照先行课降序排序
select * from course order by cpno desc
9、集函数(聚合函数)
-
计数:
count([distinct|all] * )
count([distinct|all] <列名>) -
计算总和:
sum([distinct|all] <列名>) -
计算平均值:
avg([distinct|all] <列名>) -
求最大值:
max([distinct|all] <列名>) -
求最小值:
min([distinct|all] <列名>)
-- 查询 95001 号学生的总成绩
select sum(grade) as sumgrade from sc where sno = '95001'
-- 查询课程表中所有课程的数目,其中 cno 为 primary key
select count(*) as count_course from course
select count(cno) as count_course from course
-- 查询选课总人数
select count(sno) from sc; -- 【错误】
select count(distinct sno) from sc -- 【正确】
-- 求学号为’95001’学生的总分、最高分、最低分、平均分
select sum(grade), max(grade), min(grade), avg(grade) from sc where sno = '95001'
当集函数遇到空值时,除count(*) 外,其余集函数都不处理空值,只处理非空值。
如下例:
insert sc values( '10000', '01', getdate(), 90 );
insert sc values( '10000', '02', getdate(), 70 );
insert sc values( '10000', '03', getdate(),null );
select * from sc;
select count(*) from sc where sno = '10000'; -- 3
select count(grade) from sc where sno = '10000'; -- 2
select sum(grade) from sc where sno = '10000';
select avg(grade) from sc where sno = '10000';
select max(grade) from sc where sno = '10000';
select min(grade) from sc where sno = '10000';
考虑下面这种情形?
select sno from sc;
select count(*) from sc
select sno, count(*) from sc; -- 【错误】
select sno, count(*) from sc group by sno; -- 【正确】
错误原因:
选择列表中的列 'sc.sno' 无效,因为该列没有包含在聚合函数或 GROUP BY 子句中。
10、group by 子句
注意:
使用 group by 子句后,select 子句的后面只能出现
分组属性
对每一组返回单一值的集函数
group by 子句后面可以跟上 having 短语
having 短语用来指定条件,只有满足 having 短语指定条件的组才输出
-- 查询每个学生的学号、选课的数目
select cno, count(*) as num from sc group by cno
select cno, count(cno) from sc group by cno;
-- 列出每门课程的最高、最低分
select cno, max(grade) as maxg, min(grade) as ming from sc group by cno
select cno, sno from SC group by cno --【错误】
-- 查询每个学生的选课门数
select sno, count(*) from sc group by sno
-- 查询选修了3门以上课程的学生学号及选课门数
select sno, count(*) from sc group by sno having count(*) > 3
-- 查询平均成绩大于85分的各门课程的课号及平均成绩
select cno, avg(grade) from sc group by cno having avg(grade) > 85
-- 查询有2门以上课程是80分以上的学生的学号及(80分以上的)课程数
select sno, count(*) from sc where grade > 80 group by sno having count(*) > 2
3.4.2、集合查询
1)集合并(union):
注意:
集合并操作自动去除重复元组,如果要保留重复元组的话,在 union 后面上all关键词指明
参加 union 操作的各结果表的列数必须相同,对应列的数据类型也必须相同
-- 求选修了01号课程的的学生学号和选修了02号课程的学生学号的并集 【默认去重】
select SNO from SC where CNO ='01'
union
select SNO from SC where CNO ='02';
-- 求选修了01号课程的的学生学号和选修了02号课程的学生学号的并集【去重】
select distinct sno from sc where cno = '02' or cno = '01';
-- 求选修了01号课程的的学生学号和选修了02号课程的学生学号的并集 【指明不去重】
select SNO from SC where CNO ='01'
union all
select SNO from SC where CNO ='02';
-- 求选修了01号课程的的学生学号和选修了02号课程的学生学号的并集 【默认不去重】
select sno from sc where cno = '02' or cno = '01';
注意:
order by 子句只能用于对最终查询结果排序,不能对中间结果排序
任何情况下,order by 子句只能出现在查询语句最后
-- 查询 计算机系 系或年龄小于19的学生, 按学号排序
-- 【错误】
SELECT * FROM Student
WHERE Sdept= '计算机系'
ORDER BY Sno
UNION
SELECT * FROM Student
WHERE datediff(YEAR, borndate, GETDATE()) < 19
ORDER BY Sno;
-- 【正确】
SELECT * FROM Student
WHERE Sdept = '计算机系'
UNION
SELECT * FROM Student
WHERE datediff(YEAR, borndate, GETDATE()) < 19
ORDER BY Sno;
2)集合交:intersect
-- 查询计算机系中年龄不大于38的人
select * from student where sdept = '计算机系'
intersect
select * from student where DATEDIFF(YEAR, borndate, GETDATE()) <= 38;
3)集合差:except
-- 查询计算机科学系的学生与年龄不大于38岁的学生的差集
select * from student where sdept = '计算机系'
except
select * from student where DATEDIFF(YEAR, borndate, getDate()) <= 38;
3.4.3、连接查询
- 连接总可以表示成在广义笛卡尔积上先进行选择,然后在进行适当的投影
- 关系代数里的选择对应着SQL语句中的 where 子句。
- 关系代数里的投影对应着SQL语句中的 select 子句。
1、广义的笛卡尔积
-
语法:<表1> cross join <表2>
-
用法:用在from子句后面,cross join 也可用逗号代替。
-
意义:表1与表2之间做广义笛卡儿积。
-- 广义的笛卡尔积
select * from student, sc, course;
select * from student cross join sc cross join course;
select * from student, sc cross join course;
-- 查询选课学生的基本信息及其选修课程的情况,课程只给出课程号和成绩
select student.*, sc.cno, sc.grade
from student cross join sc
where student.sno = sc.sno
-- 查询每个学生的信息以及课程的成绩
select s.*, c.cname, sc.grade, sc.examdate
from student s, sc, course c
where s.sno = sc.sno and c.cno = sc.cno
-- 查询选课学生的学号和姓名
select distinct s.sno, s.sname
from student s, sc
where s.sno = sc.sno
2、内连接
-
语法:<表1> inner join <表2> on <条件>
-
用法:用在from子句后面,
-
意义:表1与表2之间在on指定的条件上进行θ连接。
-- 查询95001号学生选修的课程号和课程名
select distinct c.cno, c.cname
from sc inner join course c
on sc.cno = c.cno
where sc.sno = '95001';
-- 查询所有选修了2号课程的学生学号、姓名。
select distinct s.sno, s.sname
from student s inner join sc
on sc.sno = s.sno
where sc.cno = '02'
-- 查询2号课成绩在85分以上的所有学生的学号、姓名
select s.sno, s.sname
from student s inner join sc
on s.sno = sc.sno
where sc.grade >= 85 and sc.cno = '02'
-- 查询选修了'数学'课的学生学号及姓名
select s.sno, s.sname
from student s inner join sc on s.sno = sc.sno
inner join course c on sc.cno = c.cno
where c.cname = '数学'
3、自身连接
- 一个表与其自己进行连接
- 需要给表起别名以示区别
- 由于所有属性名都是同名属性,因此必须使用表的别名来访问各个属性
-- 查询每一门课的间接先修课(即先修课的先修课,没有直接先行课的不输出)
select c1.cno, c2.cpno
from course c1 inner join course c2 on c1.cpno = c2.cno
-- 查询数据库的间接先行课的课号、课程名称
select c2.cpno, c2.cname
from course c1 inner join course c2 on c1.cpno = c2.cno
where c1.cname = '数据库'
4、外连接
1)左外连接
-
语法:<表1> left outer join <表2> on <条件>
-
用法:用在from子句后面,
-
意义:表1与表2之间在on指定的条件上进行左外连接,防止左边的表中(表1 )的信息因失配而丢失。
2)右外连接
- 语法:<表1> right outer join <表2> on <条件>
- 用法:用在from子句后面,
- 意义:表1与表2之间在on指定的条件上进行右外连接,防止右边的表中(表2 )的信息因失配而丢失。
3)全外连接
- 语法:<表1> full outer join <表2> on <条件>
- 用法:用在from子句后面,
- 意义:表1与表2之间在on指定的条件上进行全外连接,防止左右两边的表中的信息因失配而丢失。
-- 查询每个学生及其选修课程的情况(包括没有选课的学生,课程只给出课程号和成绩)
select s.*, c.cno, sc.grade
from student s left join sc on s.sno = sc.sno
left join course c on sc.cno = c.cno
-- 学生表与成绩表内连接
select * from student inner join sc on student.sno = sc.sno
-- 学生表与成绩表左外连接
select * from student left join sc on student.sno = sc.sno
-- 学生表与成绩表右外连接
select * from sc right join student on student.sno = sc.sno
-- 查询每个学生和每门课的信息,对于每个学生选修了哪些课,对于每门课有哪些学生选修
-- 方式一
select s.*, c.*, sc.grade
from student s left outer join sc on s.sno = sc.sno
full outer join course c on c.cno = sc.cno
-- 方式二
select s.*, c.*, sc.grade
from student s full outer join sc on s.sno = sc.sno
full outer join course c on c.cno = sc.cno
3.4.4、嵌套查询
嵌套查询的概述:一个 select-from-where
语句称为一个查询块,在查询时,可以将一个查询块嵌套在另一个查询块的 where
子句或 having
短语中,这样的查询称为嵌套查询
-- 查询选修了02号课程的学生学号,姓名
-- 内连接
select s.sno, s.sname
from student s inner join sc on s.sno = sc.sno
where sc.cno = '02'
-- 嵌套查询,查询结果有多列,当做另一张表来处理
select s.sno, s.sname
from student s inner join (select distinct sc.sno from sc where sc.cno = '02') t
on t.sno = s.sno
select distinct sc.sno from sc where sc.cno = '02'
-- 嵌套查询,查询结果有多列,使用 in 来处理
select s.sno, s.sname
from student s
where s.sno in (select distinct sc.sno from sc where sc.cno = '02')
注意:
子查询不能使用 order by 子句,order by 子句子句只能作用于最终查询结果
有些嵌套查询可以用连接运算替代
子查询的形式:
- 带有 in 谓词的子查询
- 带有比较运算符子查询
- 带有any 和 all 谓词子查询
- 带有exists 谓词子查询
1、嵌套查询(带有 in 谓词)
基本格式:
- 表达式 in (子查询)
- 表达式 not in (子查询)
- 判断表达式的值是否在子查询的结果中。
-- 查询与 '95001'学号的学生在一个系的学生
select s.sdept from student s where s.sno = '95001'
select s.sno, s.sname, s.sdept
from student s
where s.sdept in (select s.sdept from student s where s.sno = '95001')
-- 查询哪门课的先行课是:数据处理
-- 方式1:使用集合
select * from course
where cpno in (select cno from course where cname ='数据处理')
-- 方式2:自身连接
select c1.*, c2.cname
from course c1 inner join course c2 on c1.cpno = c2.cno
where c2.cname = '数据处理'
-- 查询选修了课程名为“信息系统”的学生学号和姓名
-- 内连接
select s.sno, s.sname, c.cname
from course c inner join sc on c.cno = sc.cno
inner join student s on s.sno = sc.sno
where c.cname = '信息系统'
-- 嵌套查询
-- 方式1 一层内连接,两层嵌套
select c.cno
from course c where c.cname = '信息系统'
select s.sno, s.sname, sc.cno
from sc inner join student s on s.sno = sc.sno
where sc.cno in (select c.cno
from course c where c.cname = '信息系统')
-- 方式2 一层内连接,两层嵌套
select distinct sc.sno
from course c inner join sc on c.cno = sc.cno
where c.cname = '信息系统'
select s.sno, s.sname
from student s
where s.sno in (select distinct sc.sno
from course c inner join sc on c.cno = sc.cno
where c.cname = '信息系统')
-- 方式3 三层嵌套
select c.cno
from course c
where c.cname = '信息系统'
select sc.sno
from sc
where sc.cno in (select c.cno
from course c
where c.cname = '信息系统')
select s.sno, s.sname
from student s
where s.sno in (select sc.sno
from sc
where sc.cno in (select c.cno
from course c
where c.cname = '信息系统'))
不相关子查询:
- 子查询的查询条件不依赖于父查询。
- 是由里向外逐层处理。即每个子查询在上一级查询处理之前求解,子查询的结果用于建立其父查询的查找条件
2、嵌套查询(带有比较运算符)
当能确切知道内层查询返回单值时,可用比较运算符(>,<,=,>=,<=,< >或!= )来确定父查询的条件。
-- 查询与 '95001'学号的学生在一个系的学生
select s.sdept from student s where s.sno = '95001'
select s.sno, s.sname, s.sdept
from student s
where s.sdept = (select s.sdept from student s where s.sno = '95001')
-- 查询与“刘晨”在同一个系学习的学生
-- 当有多个叫“刘晨”的学生且这多个“刘晨”不在同一个系是时,下面的SQL语句出错。
-- 带有比较运算符的子查询要求返回单值
select s.sdept from student s where s.sname = '刘晨';
select s.* from student s where s.sdept = (select s.sdept from student s where s.sname = '刘晨')
-- 查询与95001号学生在同一个系学习的学生
select s.sdept from student s where s.sno = '95001'
select * from student s where (select s.sdept from student s where s.sno = '95001') = s.sdept
-- 找出95002号学生在哪些课上的成绩超过或等于他所选修课程的平均成绩(给出课程号)
select cno from sc
where sno = '95002' and grade >= (select avg(grade) from sc where sno = '95002')
-- 找出每个学生超过或等于他选修课程平均成绩的课程号 【相关子查询】
select sno, cno
from sc t1
where t1.grade >= (select avg(grade) from sc t2 where t2.sno = t1.sno)
注:在SQLServer中子查询可以跟在比较符之前
相关子查询(相当于双层 for 循环,里层借助于外层):
- 子查询的查询条件依赖于父查询。
- 首先取外层查询中的表中的第一个元组,根据它与内层查询相关的属性值处理内层查询,若 where 子句返回值为真,则取此元组放入结果表;
- 然后再取外层表的下一个元组进行同样处理;
- 重复这一过程,直至外层表全部检查完为止。
3、嵌套查询(带有 any、all 谓词)
谓词语义
- any:某一个值
- all:所有值
需要配合比较运算符使用
- 表达式 比较运算符 any (子查询)
表达式的值与子查询结果中的某一个值相比满足比较运算符。 - 表达式 比较运算符 all (子查询)
表达式的值与子查询结果中的所有的值相比都满足比较运算符。
-- 查询其他系中比信息系中所有学生年龄小的学生姓名和年龄
-- 出生日期比他大,年龄就比他小
-- 方式1
select s.borndate, DATEDIFF(YEAR, borndate, GETDATE()) sage
from student s
where s.sdept = '信息系'
select s.sname, DATEDIFF(YEAR, borndate, GETDATE()) sage, s.sdept
from student s
where s.sdept <> '信息系' and s.borndate > all(select s.borndate
from student s
where s.sdept = '信息系')
-- 方式2
select s1.sname, DATEDIFF(YEAR, borndate, GETDATE()) sage, s1.sdept
from student s1
where s1.sdept <> '信息系' and s1.borndate > (select max(s2.borndate)
from student s2
where s2.sdept = '信息系')
ANY和ALL谓词有时可以用集函数实现:
对应关系:
= | <> | < | <= | > | >= | |
---|---|---|---|---|---|---|
any | in | -- | < max | <= min | > min | >= min |
all | -- | not in | < min | <= min | > max | >= max |
4、嵌套查询(带有EXISTS谓词)
不同形式的查询间的替换所有带 in 谓词、比较运算符、any 和 all 谓词的子查询都能用带 exists 谓词的子查询等价替换。反过来不一定成立
-- 用于判断一个查询语句查询的结果是否为空
if exists( SELECT * FROM SC WHERE Cno = '21')
print '存在'
else
print '不存在'
-- 查询选修了2号课程的学生学号、姓名
-- 内连接
select s.sno, s.sname
from sc inner join student s on s.sno = sc.sno
where sc.cno = '02'
-- 嵌套查询
select sno from sc where sc.cno = '02'
select s.sno, s.sname
from student s
where s.sno in (select sno from sc where sc.cno = '02')
-- 相关子查询
select sno, sname from
student where exists(select * from sc where cno ='02' and sno = student.sno)
-- 查询没有选修2号课程的学生学号、姓名
-- 内连接
select s.sno, s.sname
from student s inner join sc on s.sno = sc.sno
where sc.cno <> '02'
-- 嵌套查询
select sc.sno
from sc
where sc.cno = '02'
select s.sno, s.sname
from student s
where s.sno not in (select sc.sno
from sc
where sc.cno = '02')
select * from student inner join sc on student.sno = sc.sno
-- 相关子查询 【正确】
select s.sno, s.sname
from student s
where not exists (select * from sc where sc.sno = s.sno and sc.cno = '02')
-- 查询同时选修了2号和3号课程的学生学号,姓名
-- 集合的交
select s.sno, s.sname
from student s inner join sc on sc.sno = s.sno
where sc.cno = '02'
intersect
select s.sno, s.sname
from student s inner join sc on sc.sno = s.sno
where sc.cno = '03'
-- 相关子查询
select s.sno, s.sname
from student s
where
exists(select * from sc where sc.sno = s.sno and sc.cno = '02')
and
exists(select * from sc where sc.sno = s.sno and sc.cno = '03')
-- 查询95001号学生没有选修的课
-- 相关子查询
select * from course c
where not exists (
select * from sc
where sc.cno = c.cno and sc.sno = '95001')
-- 查询选修了全部课程的学生信息
-- 方式1
-- 不能存在没有选修的课,也就是全部课程都选修了
select * from student s
where not exists (
select * from course c
where not exists
(select * from sc
where sc.cno = c.cno and sc.sno = s.sno)
)
-- 方式2
-- 1)查询所有课程的数目
-- 2)通过学号进行分组,查询学号
-- 3)所有课程的数目 = 该学生选修课程数目
-- 4)判断学号是否在查询结果里面
select * from student s where s.sno in (
select sno from sc group by sno
having count(distinct cno) = (select count(cno) from course)
)
-- 怎样查找一门课,这门课95001选修了,95002没有选修
select * from course c
where
exists (select * from sc where sc.sno = '95001'and c.cno = sc.cno)
and
not exists(select * from sc where sc.sno = '95002' and c.cno = sc.cno)
-- 查询选修了95001选修的课,但是没有选修其他人选的课的学生信息
select * from student s where sno <> '95001' and not exists (
select * from course c
where
exists (select * from sc where sc.cno = c.cno and sc.sno = '95001')
and
not exists(select * from sc where sc.cno = c.cno and sc.sno = s.sno)
)
3.5、数据更新
3.5.1、插入数据(insert)
两种插入数据方式:
- 插入单个元组
- 插入子查询结果
- 可以一次插入多个元组
DBMS在执行插入语句时会检查所插元组是否破坏表上已定义的完整性规则
- 实体完整性
- 参照完整性
- 用户定义的完整性
- 对于有 unique 约束的属性列是否提供了非重复值
- 对于有 check 约束的属性列所提供的属性值是否满足 check 条件
1、插入单个元组
语句格式:
insert into 表名[(属性列1[,属性列2]…)]
values (值1 [,值2]…)
功能:插入一条指定好值的元组
insert into student(sno, sname, ssex, borndate, sdept)
values ('95007', '王大明', 1, '2010-10-20', 'CS' )
-- 不写属性列,默认将该表中的所有属性列都插入值
insert into student
values ('95009', '王小明', 1, '1997-07-02', 'CS' )
-- 只插入部分属性列
insert into student(sno, sname, ssex, sdept)
values ('95008', '王大明', 1, 'CS' )
INSERT INTO SC( Sno, Cno, examdate, grade )
VALUES ('95007', '01', '2020-04-03 10:00:00',85)
注意:
-
可以只指定部分属性列
未指定的属性列上如果没有默认约束的话,插入元组在这些属性列取空值
未指定属性列上如果有默认约束的话,插入元组在这些属性列取默认值
-
插入元组时,要保证在定义为 not null 的属性列上不能取空值。
-
属性列的顺序可以为任意顺序,但要注意数据与属性列的对应关系
2、插入子查询
语句格式:
insert into 表名[(属性列1[,属性列2]…)]
(子查询)
功能:把子查询结果中的若干条元组插入到指定的表中
-- 第一步:建表
CREATE TABLE savg(
sno CHAR(5),
avggrade INT,
constraint PK_savg_sno primary key(sno),
constraint FK_savg_sno foreign key(sno) references student(sno)
)
-- 第二步:插入查询后的元组
-- 子查询目标列必须与 into 子句中的属性列匹配(个数和类型)
insert into savg(sno, avggrade)
select sno, avg(grade) from sc group by sno
3.5.2、修改数据(update)
语句格式:
update <表名>
set <列名>=<表达式> [,<列名>=<表达式>]…
[where <条件>];
<表达式>也可以是一个返回单个值的子查询
功能:修改指定表中满足 where 子句条件的元组,省略 where 子句表示修改表中所有元组。
三种修改方式:
- 修改某一个元组的值
- 修改多个元组的值
- 带子查询的修改语句
1、修改某一个元组的值
-- 修改一个属性列的值
update student set borndate = '1981-10-10' where sno='95001';
-- 修改多个属性列的值
update student set borndate = '1983-10-10', ssex = 1 where sno='95002';
2、修改多个个元组的值
-- 将所有学生的成绩开方乘10。(省略了where子句)
update sc set grade = sqrt(grade) * 10;
3、带子查询的修改语句
-- 计算机系的学生成绩上浮5%
update sc set sc.grade = sc.grade * 1.05
where sc.sno in (select sno from student s where s.sdept = '计算机系')
-- 内连接方式更新
update sc set sc.grade = sc.grade * 1.05
from sc inner join student s
on s.sno = sc.sno
where s.sdept = '计算机系'
-- 将95001号学生各门课程的成绩改为该学生的平均成绩
update sc set sc.grade = (select avg(grade) from sc where sc.sno = '95001') where sc.sno = '95001'
DBMS在执行修改语句时会检查修改操作是否破坏表上已定义的完整性规则。
-- 将SC表中学生95001的选课记录改为学生95020的选课记录。
UPDATE sc SET sno = '95020' WHERE sno='95001'; -- 【失败】
3.5.3、删除数据(delete)
语句格式:
delete from <表名>
[where <条件>];
功能:
- 删除指定表中满足 where 子句条件的元组
- 省略 where 子句表示要删除表中的全部元组,表的定义仍在数据字典中。
删除元组是也不能破坏数据库的完整性
如果sc表存在与95001相关的记录,系统对于上面的语句有两种处理方式。
-
宣布上面语句执行失败。
-
系统首先自动地删除sc表中与95001相关的记录,然后执行上面语句
-- 删除学号为95001的学生记录
delete from sc where sno = '95001'
delete from student where sno = '95001' -- 级联
3.6、视图
3.6.1、视图概述
视图的特点:
-
视图是一个虚表
-
数据库中只存放视图的定义;
-
视图对应的数据仍存放在原来的表中;
-
随着表中数据的变化,视图的数据随之变化;
-
对视图的查询最终转变成对基本表的查询;
-
可以在视图上建立视图;
例如:某一用户关心被选修的各门课的平均分
select cno, avg(grade) from sc group by cno
如果建个表存储上面信息,产生数据冗余,可以建立一个视图来方便查询
create view cgrade( cno, avgg )
as
select cno,avg(grade)
from sc group by cno
select * from cgrade
3.6.2、定义视图
1、创建视图
语句格式:
create view <视图名>[(<列名>[,<列名>]…)]
as <子查询>
[WITH CHECK OPTION];
2、删除视图
drop view <视图名>
- 删除视图是从数据字典中删除指定的视图定义,并不删除数据
- SQLServer中,删除视图后,由该视图导出的其他视图定义仍在数据字典中,但已不能使用,必须显式删除
- SQLServer中,删除基表时,由该基表导出的所有视图仍在数据字典中,但已不能使用,也必须显式删除
示例:
-- 建立计算机系学生的视图,包括学号、姓名、年龄
create view v1(sno, sname, sage)
as select sno, sname, Datediff(year, borndate, GETDATE()) from student where sdept = '计算机系'
select * from v1
-- 建立计算机系选修了02号课程的学生的视图,包括学号、姓名。(基于多表的视图)
create view cs_v1(sno, sname)
as
select s.sno, s.sname from student s inner join sc on sc.sno = s.sno where sc.cno = '02' and s.sdept = '计算机系'
-- 建立计算机系选修了02号课程且成绩在80分以上的学生的视图,包括学号、姓名。(基于已有的视图再创建视图)
create view cs_v2(sno, sname)
as
select distinct c.sno, c.sname from cs_v1 c inner join sc on sc.sno = c.sno where sc.grade > 80
-- 查询视图
select * from cs_v2
-- 删除视图
drop view cs_v1
注意:多表连接时选出了几个同名列作为视图的字段时, 要全部指定组成视图的所有属性列名
组成视图的属性列的名子可以全部省略或全部指定,不能部分指定
- 省略:视图的属性列的名子和子查询中各个目标列的名字相同。
- 下面情况下全部指定组成视图的所有属性列名
- 多表连接时选出了几个同名列作为视图的字段
- 子查询中的某个目标列是集函数或列表达式,并且没有定义别名。
- 需要在视图中为某个列启用新的更合适的名字
3.6.3、查询视图
和对基本表的查询基本一致;
3.6.4、更新视图
-
通过视图更新某些元组时,这些元组要满足视图定义才能被更新(元组在视图中)
-
通过视图更新数据时,不能更改不在视图定义中的的属性
-
DBMS允许用户对视图进行更新,对视图的更新最终会转化为对基本表的更新。
-
并不是所有的视图都可以更新
select * from cs_v1
-- 更新视图的方式和更新基本表基本一样
update cs_v1 set sname = '郭靖1' where sno = '10000'
select * from student where sno = '10000'
select * from cgrade
-- 【执行失败】
update cgrade set avgg = 90
WHERE cno = '01'
3.6.5、视图的作用
- 视图对数据库提供了一定程度的逻辑独立性
Student(Sno,Sname,Ssex,Sage,Sdept)
“垂直”地分成两个基本表:
SX(Sno,Sname,Sage)
和
SY(Sno,Ssex,Sdept)
select * from student; 语句失效。
SX ∞ SY
- 视图能够对机密数据提供安全保护
- 对不同用户定义不同视图,使每个用户只能看到他有权看到的数据
- student表涉及到15个院系的学生,可以建立15个视图,每个视图只包含一个院系中的学生数据,对每个系主任只授予查询和修改本系视图的权限