《数据库系统概念》读书笔记,第三章、SQL
引入:
这一章主要内容就是在说SQL的基本操作:定义关系模式,查询指定元组集合,删除添加修改关系模式信息和元组信息
一、SQL数据定义
数据库中的关系集合需要由数据定义语言(DDL)指定,特殊的对于SQL的DDL,除了定义一组关系还可以定义每个关系的如下信息:
- 每个关系的模式
- 每个属性的取值类型
- 完整性约束
- 每个关系维护的索引集合
- 每个关系的安全性和权限信息
- 每个关系在磁盘上的物理存储结构
本章只讨论关系的基本模式的定义
这里说一下我理解的什么是关系的模式,我理解的就是这个关系的每一个属性和每一个属性对应的值域集合
1. 数据类型
SQL标准支持的多种固有的类型:
- char(n)
- varchar(n)
- int
- smallint
- numeric(p,d)
- real,double precision
- float(n)
注:提倡用varchar代替char,避免匹配复制等会出现的问题
2. 模式的定义
创建表的通用结构:
create table r
(A1 D1,
A2 D2,
...,
An Dn,
<完整性约束1>,
...,
<完整性约束k>);
注:r是关系名称,Ai是第i个属性的属性名,Di是第i个属性的数据类型(或者说是第i个属性的域)。
关于完整性约束,我们只介绍少数几个比较简单的:
- primary key(Aj1,Aj2,..,Ajm) 表示属性 Aj1,Aj2,..,Ajm 共同组成这个关系的主码。
- foreign key(Ak1,Ak2,...Akn) reference R 表示任意元组在属性 (Ak1,Ak2,...Akn) 上的取值必须对应于关系R中某元组在主码属性上的取值,这个称为外码声明
- A1D1not null,表示 A1 属性取值不可以为 null。
下面给出对于如下属性结构的定义,注意后面章节中也会用到这一套统一的关系模式。
department ( dept_name, building, budget )
course ( course_id, title, dept_name, credits )
instructor ( ID, name, dept_name, salary )
section ( course_id, sec_id, semester, year, building, room_number, time_slot_id )
teaches ( ID, course_id, sec_id, semester, year )
SQL代码:
create table department
(dept_name varchar(20),
building varchar(15),
budget numeric(12,2),
primary key (dept_bname));
create table course
(course_id varchar(7),
title varchar(50),
dept_name varchar(20),
credits numeric(2,0),
primary key (course_id),
foreign key (dept_name) references department );
create table instructor
(ID varchar(5),
name varchar(20) not null,
dept_name varchar(20),
salary numeric(8,2),
primary key (ID),
foreign key (dept_name) references department );
create table section
(course_id varchar(8),
sec_id varchar(8),
semester varchar(6),
year numeric(4,0),
building varchar(15),
room_number varchar(7),
time_slot_id varchar(4),
primary key (course_id, sec_id, semester, year),
foreign key (course_id) references course );
create table teaches
(ID varchar(5),
course_id varchar(8),
sec_id varchar(8),
semester varchar(6),
year numeric(4,0),
primary key (ID, course_id, sec_id, semester, year),
foreign key (course_id, sec_id, semester, year) references section,
foreign key (ID) references instructor );
)
个人问题存留:这里有一个问题,我实际声明的时候,同一个属性会在多个关系中出现,如果我在两个关系中声明同一个属性的时候为它设置的取值域(数据类型)不同,会怎样?
注:注意SQL禁止会破坏完整性约束的任何数据库更新操作。
二、SQL数据查询的基本结构
1. 基本查询(单关系 → 多关系 → 自然连接)
对于查询最基本的结构就是如下表达:
select D1,D2,…,Dn
from R1,R2,…,Rm
where P1 and P2 and … and Pk ;
注:D是属性,R是关系,P是谓词
-
引入关键词:
- distinct:声明要把查询出来的元组去重。
例: select distinct dept_name from instructor ;
- all:声明要把查询出来的元组保留重复。(默认即为不去除重复)
例:select all dept_name from instructor ;
-
引入多关系查询:
因为有时候我们需要查询的所有属性分布在不同的关系之中,所以我们要首先将多个关系进行连接(笛卡尔积),然后在进行查询。就是 from 后面的所有关系连续进行笛卡尔积计算。例:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID ;
注意这里一般笛卡尔积后会进行一定的限制,最常见的就是限制两个关系的相同属性的值保持一致的元组才可以组合。
-
引入自然连接(natural join、using):
我们发现很多时候我们进行笛卡尔积需要在where子句中限制的内容都是类似的,所以我们进一步对多关系查询的内容进行封装。就产生了自然连接的结构,它只考虑那些在两个关系模式中都出现的属性上取值相同元组对,将他们连接并加入到结果中。例:
select name, course_id
from instructor natural join teaches ;扩展:除此之外我们还可以限定自然连接时,只考察某些指定的属性,将在这些属性上取值相同的元组对进行连接并加入的结果中。
例:
select name, title
from ( instructor natural join teaches ) join course using ( course_id ) ; -
小细节总览:
(1)select 后面的属性可以是表达式,比如:salary/12
(2)默认不对于查询出的元组进行去重
2. 附加的基本运算
-
更名(as)
格式:odd-name as new-name
这里的 as 关键字不仅可以重命名属性,也可以重命名关系。例:
select T.name, S.course_id
from instructor as T, teaches as S
T.ID = S.ID ; -
字符串模式匹配(like)
对于 where 子句中的谓词,经常会用到字符串的模式匹配,但是 “ = ” 在很多情况下不足以满足我们的使用需求,所以我们引入了 like 。
引入特殊字符:
百分号( % ):匹配任意子串。
下划线( _ ):匹配任意一个字符。
引入关键字(escape)定义转移字符:
例:like ‘ab\cd%’ escape ‘’ ,匹配所有以 “ab\cd” 开头的字符串。例:
select dept_name
from department
where building like ‘%Watson%’ ; -
对查询select子句的限制(*)
selcet子句中,“ * ” 可以表示选定所有属性例:
select instructor.*
from instructor, teaches
where instructor.ID = teaches.ID ; -
排列元组的显示次序(order by、desc、asc)
都是很显然的内容,直接上例子select *
from instructor
order by salary desc, name asc ;注:desc 表示降序,asc 表示升序。
-
属性组合
用记号 (v1,v2,...,vn) 表示一个分量值分别为: v1,v2,...,vn 的 n 维元组。例:
select name, course_id
from instructor, teaches
where (instructor.ID, dept_name) = (teaches.ID, ‘Biology’) ;
3. 查询之间的集合运算
因为一个关系也可以看成是一个元组的集合(有可能是多重集),所以对于查询的结果,我们也可以把它当做元组的集合进行一些集合的运算,包括:并运算,交运算,差运算
- 并运算(union)
将两个查询结果进行集合的并运算
例:
(select course_id from section where semester = ‘Fall’ and year = 2009)
union
(selecr course_id from section where semester = ‘Spring’ and year = 2010)
注:union 运算自动去重,如果要保留重复可以使用 union all,结果中重复元组列出的次数等于两个集合中出现次数的总和。
- 交运算(intersect)
将两个查询结果进行集合的交运算
例:
(select course_id from section where semester = ‘Fall’ and year = 2009)
intersect
(selecr course_id from section where semester = ‘Spring’ and year = 2010)
注:intersect 运算自动去重,如果要保留重复可以使用 intersect all,结果中重复元组列出的次数等于两个集合中出现次数的较小值。
- 差运算(except)
A except B,返回 A 中出现并且没在 B 中出现的元组。
例:
(select course_id from section where semester = ‘Fall’ and year = 2009)
except
(selecr course_id from section where semester = ‘Spring’ and year = 2010)
注:except 运算之前,先把两个集合自动去重,在进行运算。如果要保留重复可以使用 except all,结果中重复元组列出的次数等于第一个集合中出现的次数减去第二个集合中出现的次数(如果小于 0 就不出现)
4. 空值的引入
为了更符合实际情况中的使用,引入一个新的值叫做空值,记为:null。但是引入之后因为会参与到各种运算中,所以会产生各种各样的问题,下面说明一下 null 值的运算规则。这里引入另一个关键词 unknown。
- null 参与算数表达式($ +\ -\ * \ / $)的运算结果都是 unknown
- and运算: true and unknown 结果是 unknown;false and unknow 结果是 false;unknow and unknow 结果是 unknown;
- or运算: true or unknown 结果是 true;false or unknow 结果是 unknow;unknow or unknow 结果是 unknown;
- not运算:not unknow 结果是 unknow;
- 如果 where 中的谓词结果是 unknow 的话,该元组不可加入到结果中。
- null = null 会返回 unknow,但是元组( ‘A’ , null )和( ‘A’ , null )在去重时被看做相同。
5. 聚集函数
为了满足更多地计算需求,SQL封装了更多地内置计算函数,包括:平均值(avg),最小值(min),最大值(max),总和(sum),计数(count)。这些函数用在 select 子句中,写法上括号中是一个属性,传入的是一个数据集合,返回的是一个数字。
例:
select avg(salary)
from instructor
where dept_name = ‘Comp.Sci.’ ;
-
分组聚集(group by)
现在又有了新的需求,就是我们希望把查询得到的结果按照一定的规则进行分组,然后对于同一个组的信息使用聚集函数计算结果。例:
select dept_name, avg (salary)
from instructor
group by dept_name ;
注意:SQL中,select语句中出现,但是没有被聚集的属性,必须出现在group by 子句中! -
having子句
既然 where 是可以限制选择元组的,那么我希望对于分组也进行限制,只选择符合我要求的分组进行显示。于是就有了 having 子句。例:
select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name
having avg(salary) > 42000 ;
注意:SQL中,having 子句中出现,但是没有被聚集的属性,必须出现在 group by 子句中!
6. 嵌套子句
现在就来到了最复杂的查询的嵌套。有的查询如果不掌握方法,不管是写起来还是读起来,都非常烧脑,所以对于这一部分,理清逻辑结构是很重要的。
现在先提几点对于SQL的理解,方便后面内容的解释。
-
where子句中嵌套子查询
-
集合成员资格,集合的比较
因为一个查询返回的结果是一个元组集合,而 where 子句中的谓词限制都是元素之前的关系比较,那么是怎么把子查询引入的 where 子句呢?这里就自然而然的引入了关键词:in、not in、some、all。
这里直接举两个例子,请自行举一反三。– in 的使用
select count (distinct ID)
from takes
where (course_id, sec_id, semester, year)
in ( select course_id, sec_id, semester, year
from teaches
where teaches.ID = 10101 ) ;
– all 的使用
select name
from instructor
where salary > all ( select salary
from instructor
where dept_name = 'Biology ') ;总结:in用于测试属性集合是否在查询结果中出现;not in是in查询结果的取反;all是检测是否被测试属性大于或者小于(取决于操作符)子查询结果的所有元组,类似于全称量词;some检测是否被检测属性大于或者小于子查询结果的某一个元组,类似于存在量词。
个人理解:这里的子查询都是用查询出来的结果去和整体的查询进行信息交互的,也就是说,子查询可以单独作为一个整体存在,而它的内部不会和整体查询产生关联。所以这个我们也可以单独把子查询拿出来用一个临时关系变量进行存储,再放回整体查询中。 -
空关系测试,重复元组存在性测试
那除了引入关系还有什么办法把子查询引入到 where 结构的谓词里面呢?那就是我们还经常希望对查询的出来的元组进行一定的测试,这个测试可能比较复杂就会需要子查询的帮助,这就引入了第二类(我自己分的)子查询引入结构。相关关键词:exists、not exists、unique。
-
-
from子句中嵌套子查询
-
with子句
-
标量子查询