DBMS的用户接口
数据库管理系统作为一个系统软件一定要让用户会用,让用户去管理数据,那么它一定要提供一些访问接口,让用户通过这些接口来使用数据库。
这些接口包括:
- Query Languages(查询语言):基本每种数据库系统都会根据他采用的数据模型,向用户提供某种类型的查询语言,允许用户通过查询语言来访问系统。
1)形式化的查询语言(Formal Query Language):SQL,有严格语法。
2)表格式的查询语言(Tabular Query Language):用填表格的方式来表达要查什么。
3)图形化查询语言(Graphic Query Language)
4)受限的自然语言查询(Limited Natural Language Query Language):我要查年龄大于24岁的学生。 - GUI(图形化用户界面):访问并维护数据库数据。
- APIs:在应用程序中访问数据库(ODBC)
-
Class Library:类裤(Java,C++)
关系型数据库的查询语言
查询语言没有编程能力,不是图灵完备的。
关系型数据库查询语言形式化基础:
数学化查询语言:关系代数&关系演算,SQL就是以关系演算为基础开发出来的。
QL语言就是SELECT语句。
DML语言主要对数据库中的数据进行插入、删除、更改操作。
重要的术语和概念
- Base table(基表):一个基表就是一个关系,物理存在于磁盘上。
- View(视图):虚表,不真实存储在数据库里的数据,是通过计算基表从而得出的结论。
- Data type supported(数据库产品支持的数据类型):短整型,长整型数据。
- NULL(空值):SQL中的一个关键字(保留字)。布尔表达式判断:True、False、NULL。
- UNIQUE:SQL中的一个关键字(保留字)。在数据库中创建一张表时,用UNIQUE说明,某属性不允许在其他表里重复。
- DEFAULT:SQL中的一个关键字(保留字)。为数据库中某张表的某个属性指定一个默认值。
- PRIMARY KEY:指定某个属性是主键。
- FOREIGN KEY:指定某个属性是外键。如果是外键的话,一定是引用了另一张表的主键。一旦某个属性被定义成外键,系统就会自动检查表格之间引用完整性的约束。
-
CHECK:在表中定义约束条件,比如,东南大学的学生入学年龄必须大于15岁。
基本的SQL语句查询
SELECT:target-list:查询的目标属性。加了[DISTINCT]的话,就要求系统消除结果中的重复元组;不加的话,不会删除重复元组。
FROM:relation list:这个查询所涉及到的表。
WHERE:qualification就是一个布尔表达式,表示查询结果应该满足的条件。
在系统内部一条查询是如何执行的
- 做FROM子句里牵扯到的所有表的笛卡尔乘积,把所有表都拼起来,拼成一张大表。
- 利用WHERE子句里布尔表达式的条件,在第一部中得到的大表中去做筛选,把不满足查询要求的元组剔除掉。
- 根据SELECT子句中的target list中我要查的那几个属性,把第二步中筛选出的元组中我不要的投影掉,我要的留下来。
- 如果我加了DISTINCT,系统还会把重复元组去掉;如果没加,第三步就是最终结果了。
实例1
S.sname:大写S:给Sailor这张表起的‘别名’。
S.sid=R.sid:Sailors和Reserves两张表要按sid做连接。
R.bid=103:
因为Reserve这张表没有水手name的信息,为了获得预定了103这条船的水手的name,我必须让Sailors这张表和Reserves这张表做一个连接操作,通过它们之间的sid相等,就可以找到S.sname。
引入别名(Range Variable)
Sailors表的range variable是S,Reserves表的range variable是R。
sid两张表都有,所以需要“表名.关系”,避免引起混淆;像bid和sname只存在在其中一张表中,可以不写别名。但是建议都写。
实例2
查找至少预定过一条船的水手:把Reserves这张表投影到sid这个属性上,只要是在Reserves表上出现,则一定预定过船只。
如果是sid加不加DISTINCT没有影响;如果是name加不加DISTINCT有影响,因为存在重名情况。
一般SELECT S.sid, S.sname是最保险的。
实例3
在SELECT子句中还可以使用一些表达式。
LIKE:模糊查询。
:匹配一个字符。
%:匹配零个或多个字符。
LIKE 'B%B':查找水手的姓名是像,以大写字母B开头,后面跟‘一个到多个’字符,以大写字母B结尾。
age1=S.age-5:满足条件的水手的年龄减5得到的这个结果属性起个名字,叫age1。
2S.age AS age2:和 age2=2S.age表示同样的意义。
实例4
预定过一艘红船‘或者’一艘绿船的水手编号。
如果单纯想得到sid,不需要查询Sailors这张表,但是这里有一个悬念,加上Sailors表还是有用的。
等价于,所有预定过红船的水手编号 UNION 所有预定过绿船的水手编号。
sql用EXCEPT表示集合的差。如果把UNION换成EXCEPT,得到的结果就是,订过红船,但没有订过绿船的水手的编号。
实例5
预定过一艘红船‘和’一艘绿船的水手编号。
不能简单的把(B.color='red' OR B.color='green')里的OR换成AND,这样是错误的,因为如果换成AND,语句意思是“一艘又绿又红的船”,没有这种红配绿的船。
先让Reserves这张表做自连接,这样每一个水手的订船信息就会出现两次。
Sailors S, Boats B1, Reserves R1, Boats B2, Reserves R2:把Boats和Reserves这两张表分别用两次,做他们的自连接。
(B1.color='red' AND B2.color='green'):如果一个水手同时订过红船和绿船,那么他的订船信息就会出现两次,如果出现这样一条元组的话,就找到了对应水手。(一个超长的元组中,前面某列显示它订过红船,后面某列显示它订过绿船)
INTERSECT:集合的交。参与运算的关系必须满足并兼容条件。
非关联嵌套查询
订过103号船的水手的姓名。
(非关联)嵌套子查询:对Reserves表做选择,把所有订过103号水手的信息找出来做投影,投影到sid上,得到一个结果集合。与外面查询无关联。
外面这个查询:对Sailors这张表做扫描,看水手编号是否在“订过103号船的水手编号集合”里,在,就对了,不在,就不理他。
关联嵌套查询
嵌套的子查询条件与外层相关。
如果某个水手预定过103这条船,那么在Reserves表里一定‘存在’一套元组的bid等于103,sid等于水手sid。把Sailors表中每个水手的sid都代入子查询循环查询,S.sid=R.sid了就说明我查到了。
SELECT *:返回所有列,星号是通配符。查找所有属性。
在Reserves表中,NOT EXIST(不存在)另外一条元组,他的sid和目标水手的sid相同,bid是103,他的日期与目标水手的订船日期相等。
查找只有一个水手预定过的船。
Reserves表自连接有R1和R2。
SELECT bid
FROM Reserves R2
WHERE R2.sid != R1.sid:所有除R1.sid之外的其他水手订的船的编号集合。
WHERE bid NOT IN:如果这个水手所定的bid不在其他水手所定的bid集合中,则这条船就他一个人订。
比较运算符
op可以是大于,小于,等于,大于等于,小于等于,不等于
以大于为例:
op ANY:某属性的值要比集合中随便一个值大就行。
op ALL:某属性的值要比集合中所有的值都大。
IN
S.sid=R.sid AND R.bid=B.bid AND B.color='red':把预订了红船的水手的sid找到。
S2.sid=R2.sid AND R2.bid=B2.bid AND B2.color='green':把预订了绿船的水手的sid找到。
S.sid IN:看订了red的水手编号在不在订了green的水手编号集合中。如果在,说明这个水手red green两条船都订了。
What about INTERSECT query?:不能简单替换S.sid by S.sname,会有重名。
除法
查找“所有”。
不存在一条船是我没订过的。
SELECT B.bid
FROM Boats B:所有的船。
SELECT R.bid
FROM Reserves R
WHERE R.sid=S.sid:这个水手预定过的船。
所有的船 EXCEPT 这个水手预定过的船 = 这个水手没订过的船
如果不存在这个水手没订过的船,则,这个水手所有船都订过。
如果考试不允许用EXCEPT, 那么:
如果在Reserves表中查不到“这个水手没订的船”,则这个水手订了所有船。
聚集函数运算
COUNT (*):统计在一个关系中有多少条元组
COUNT ([DISTINCT] A):统计属性A中有多少个值(多少行非空元组)。加DISTINCT则统计属性A有多少个不同的值。
SUM ([DISTINCT] A):把所有元组的属性A的值做求和。加DISTINCT则求和属性A所有不同的值。
AVG ( [DISTINCT] A):对属性A求平均值。加DISTINCT则求属性A所有不同的值的平均值。
MAX (A):求属性A中的最大值。
MIN (A):求属性A中的最小值。
1(左上). 问Sailors这张表中元组的个数。
2(右上). 有好几个水手都叫Bob,所有叫Bob的水手的不同的级别的个数。
3(左中). 查找所有级别为10的水手的平均年龄。
4(右中).查找所有级别为10的水手的不同年龄值的平均值。
5(左下).查找级别最高的水手的姓名。
- SELECT S.sname, MAX (S.age)
FROM Sailors S:错误的。因为MAX (S.age)计算结果是一个单个的值,姓名与max age无法对应。 - SELECT S.sname, S.age
FROM Sailors S
WHERE S.age =
(SELECT MAX (S2.age)
FROM Sailors S2):正确的。 - 换位置也是正确的。
分组聚集函数计算
因为SQL不具备编程功能,所以我们不能for loop。那怎么办呢?
GROUP BY grouping-list:把WHERE得到的符合条件的元组,按grouping-list里的那些属性的值做分组。属性值相等的元组被分到一组。
然后对每一个组,按照SELECT子句里规定的属性和聚集函数做运算,得到一条结果;即,每一个group,产生一条结果元组。
要求SELECT中出现的attribute,必须对每个group只有一个值,否则会error。
HAVING group-qualification:与WHERE相似,对经过GROUP BY分组后的每个group再做检查,把不满足条件的group去掉。
对FROM中出现的表做笛卡尔乘积,把他们拼接起来,然后再按WHERE中的qualification做筛选,不满足条件的元组抛弃掉,然后按分组属性值相等的原则对经过筛选的元组做分组,再按HAVING中group-qualification的条件对每个group做检查,筛选合格group,最后按SELECT中的要求去做相应的计算,每一个group得到一条结果元组。
要求SELECT和HAVING中出现的每一个attribute对分组得到的每个group来讲,它的值必须是单一的。
在SELECT和HAVING中出现的attribute必须是分组属性集grouping-list.的一个子集。
要求SELECT和HAVING中出现的每一个attribute对分组得到的每个group来讲,它值必须是单一的。
对所有年龄大于18岁的水手,查每一个水手人数至少为2的年龄级别组中,年龄大于十八岁的水手中最年轻的水手。
查每一条红船的预定人数是多少。
先连接Reserves表和Boats表,拼起来以后就得到了所有船的预定情况的详细信息,然后做筛选,把所有红颜色船的订单情况筛选出来,按照bid做分组,这样同一条船的订船情况就在一个group中,此时对每一个group做COUNT(*),这样我就知道,每一条船有多少人订过它。
如果在WHERE不做筛选,转而用HAVING对group做筛选:逻辑没问题,但是数据库会报错。
SELECT B.bid, COUNT(*) AS scount
FROM Boats B, Reserves R
WHERE R.bid=B.bid
GROUP BY B.bid
HAVING B.color=‘red’:这就是为什么要求,SELECT和HAVING中出现的每个属性,都必须是GROUP BY属性集合的子集。(B.color不是B.bid的子集)
硬用的话怎么办:
SELECT B.bid, COUNT(*) AS scount
FROM Boats B, Reserves R
WHERE R.bid=B.bid
GROUP BY B.bid, B.color=‘red’ (补上)
HAVING B.color=‘red’:在本例中,每条船跟每条船的颜色都是单一的不同的,所以按编号分按颜色分其实不会改变分组情况。
或者这样写:
SELECT B.bid, COUNT(*) AS scount
FROM Boats B, Reserves R
WHERE R.bid=B.bid
GROUP BY B.bid
HAVING B.bid IN (SELECT bid
FROM Boats B
WHERE B.color=‘red’) 也是对的。
从S2重新开始的原因是S.age>18已经把不满足条件的元组排除掉了,S表不完整了。
按级别分组,查找平均年龄最小的级别是哪一个。
分组聚集函数运算不能嵌套使用。Temp就是一张临时表。
在FROM中出现嵌套查询:
Null Values
空值就是不知道,不是零。
引入空值之后带来的一些问题:
在查询时,需要在WHERE和HAVING的布尔表达式中引入一些特殊的运算符来表达对Null value的判断。
扩充的就不学了,拜拜。