【数据库系统原理】第四章 SQL语言之数据查询与操作

4.1 数据查询与操作

以以下关系为例:

4.1.1 SQL查询

SQL查询的基本结构(三个子句):

SELECT A1,A2,...An --投影 FROM r1,r2,...,rm --笛卡尔积 WHERE P --选择条件 

例1,找出所有教师的名字:

SELECT name FROM instructor; 

Select子句:

例1,查询instructor关系中的所有系名,并去除重复:

/* * SQL允许在关系以及SQL表达式结果中出现重复的元组, * 若要强行去除重复,可在select后加入关键词distinct */ SELECT DISTINCT dept_name --去除重复 FROM instructor; 

例2,查询instructor关系中的所有系名,不去除重复:

/*也允许我们使用关键词all来显式指明不去除重复(SQL默认就是all)*/ SELECT ALL dept_name --不去除重复 FROM instructor; 

例3,查询所有的属性:

SELECT * --查询全部 FROM instructor; 

例4,查询ID,名字,1.05倍后的薪水:

/* * select子句还可带含有+、-、*、/运算符的算术表达式, * 运算对象可以是常数或元组的属性 */ SELECT ID,name,salary*1.05 --薪水乘1.05倍 FROM instructor; 

SQL中的select子句支持关系代数运算符的多重集版本:
例:有表r1,

表r2,

则ΠB(r1)× r2为,

Where子句:
例1,找出所有在Computer Science系并且工资超过70 000美元的教师的姓名:

/*where子句允许我们只选出那些在from子句的结果关系中满足特定谓词的元组*/ SELECT name FROM instructor WHERE dept_name='Comp.Sci.' AND salary>70000; 

例2,找出工资在90 000美元和100 000美元之间的教师的姓名:

/* * SQL允许在where子句中使用逻辑连词and,or和not, * 也可以使用between指定范围查询。 * 逻辑连词的运算对象可以是包含比较运算符<、<=、>、>=、= 和<>的表达式 */ SELECT name FROM instructor WHERE salary>=90000 AND salary<=100000; 

or

SELECT name FROM instructor WHERE salary BETWEEN 90000 AND 100000; 

From子句:
例1,找出关系instructor和teaches的笛卡尔积:

/* * from子句是一个查询求值中需要访问的关系列表, * 通过from子句定义了一个在该子句中所列出关系上的笛卡尔积 */ SELECT * FROM instructor,teaches; 

例2,找出Computer Science系的教师名和课程标识:

SELECT name,course_id FROM instructor,teaches WHERE instructor.ID=teaches AND instructor.dept_name='Com.Sci.'; 

As子句(更名运算):
例1,找出Computer Science系的教师名和课程标识,将属性name重命名为instructor_name:

/* * SQL提供可为关系和属性重新命名的机制,即使用as子句, * old-name as new-name */ SELECT name AS instructor_name,course_id FROM instructor,teaches WHERE instructor.ID=teaches.ID AND instructor.dept_name='Com.Sci.'; 

例2,找出所有教师,以及他们所讲授课程的标识:

/* * as子句既可以出现在select子句中,也可以出现在from子句中。 */ SELECT T.name,S.course_id FROM instructor AS T,teaches AS S WHERE T.ID=S.ID; 

例3,找出所有教师名,他们的工资至少比Biology系某一个教师的工资要高

SELECT DISTINCT T.name FROM instructor AS T,instructor AS S WHERE T.salary>S.salary AND S.dept_name='Biology'; 

字符串运算(Like):

  • 百分号“%”:匹配任意子串
  • 下划线“_”:匹配任意一个字符

例1,找出所在建筑名称中包含子串‘Watson’的所有系名:

SELECT dept_name FROM department WHERE building LIKE '%Watson%'; --匹配子串Watson 
  • 为使模式中能够包含特殊字符(即%和_),SQL允许定义转义字符。我们
    在like比较运算中使用escape关键词来定义转义字符。

例2,使用反斜线(\)作为转义字符:

LIKE 'ab\%cd%' ESCAPE '\' --匹配所有以“ab%cd”开头的字符串 LIKE 'ab\\cd%' ESCAPE '\' --匹配所有以“ab\cd”开头的字符串 
  • SQL还允许在字符串上有多种函数,例如串联(“||”)、提取子串、计算字符串长度、大小写转换(用upper(s)将字符串s 转换为大写或用lower(s)将字符串s 转换为小写)、去掉字符串后面的空格(使用trim(s))等等。

排列元组的显示次序:
例1,按字母顺序列出在Physics系的所有教师:

/* * SQL为用户提供了一些对关系中元组显示次序的控制。 * order by子句就可以让查询结果中元组按排列顺序显示 */ SELECT name FROM instructor WHERE dept_name='Physics' ORDER BY name; 

例2,按salary的降序列出整个instructor关系,如果有几位教师的工资相
同,就将他们按姓名升序排列:

/* * order by子句默认使用升序。 * 要说明排序顺序,我们可以用desc表示降序,或者用asc表示升序 */ SELECT name FROM instructor ORDER BY salary desc,name asc 

集合运算:

  • SQL作用在关系上的union、intersect和except运算对应于数学集合论中∪,∩和-运算。
  • union、intersect和except运算与select子句不同,它们会自动去除重复。
  • 如果想保留所有重复,必须用union all、intersect all和except all。
  • 以section(course_id,sec_id,semester,year,building,room_number,time_slot_id)为例

例1,找出在2009年秋季开课,或者在2010年春季开课或两个学期都开课的所有课程

(SELECT course_id FROM section WHERE semester='Fall' AND year=2009) UNION --并 (SELECT course_id FROM section WHERE semester='Spring' AND year=2010); 

例2,找出在2009年秋季和2010年春季同时开课所有课程:

(SELECT course_id FROM section WHERE semester='Fall' AND year=2009) INTERSECT --交 (SELECT course_id FROM section WHERE semester='Spring' AND year=2010); 

例3,找出在2009年秋季开课,但不在2010年春季开课的所有课程:

(SELECT course_id FROM section WHERE semester='Fall' AND year=2009) EXCEPT --差 (SELECT course_id FROM section WHERE semester='Spring' AND year=2010); 

要注意的是:

  • 在Oracle中,支持union,union ALL,intersect和Minus;但不支持Intersect ALL和Minus ALL。
  • 在SQL Server 2000中只支持union和union A。

聚集函数:.

SQL提供了五个固有聚集函数:

  • 平均值:avg
  • 最小值:min
  • 最大值:max
  • 总和:sum
  • 计数:count

其中,sum和avg的输入必须是数字集,但其他运算符还可作用在非数字数据类型的集合上,如字符串。

例1,找出Computer Science系教师的平均工资:

gavg(salary)dept_name=‘Com.Sci.’(instructor))

SELECT AVG(salary) AS avg_salay --求平均值:avg() FROM instructor WHERE dept_name='Comp.Sci'; 

例2,找出每个系的平均工资:

/* * group by子句中给出的一个或多个属性是用来构造分组的, * 在group by子句中的所有属性上取值相同的元组将被分在一个组中 * 任何没有出现在group by子句中的属性,如果出现在select子句中的话, * 它只能出现在聚集函数内部,否则这样的查询就是错误的! */ SELECT dept_name AVG(salary) AS avg_salary FROM instructor GROUP BY dept_name; 

结果:

例3,找出教师平均工资超过42 000美元的系:

/* * having子句类似于where子句, * 但其是对分组限定条件,而不是对元组限定条件。 * having子句中的谓词在形成分组后才起作用,因此可以使用聚集函数。 * 与select子句的情况类似,任何出现在having子句中,但没有被聚集的属性,必须出现在group by子句中, * 否则这样的查询就是错误的! */ SELECT dept_name AVG(salary) AS avg_salary FROM instructor GROUP BY dept_name HAVING AVG(salary)>42000; 

空值:
例1,找出instructor关系中元组在属性salary上取空值的教师名:

/*在谓词中可以使用特殊的关键词null测试空值, * 也可以使用is not null测试非空值 */ SELECT name FROM instructor WHERE salary IS NULL; --注意区分与salary=NULL的不同 

聚集函数根据以下原则处理空值:

  • 除了count(*)外所有的聚集函数都忽略输入集合中的空值。
  • 规定:空集的count运算值为0,其他所有聚集运算在输入为空集的情况下返回一个空值。

例2,计算所有教师工资总和:

/* * sum运算符会忽略输入中的所有空值 * 如果instructor关系中所有元组在salary上的取值都为空, * 则sum运算符返回的结果即为null */ SELECT SUM(salary) FROM instructor 

4.1.2 嵌套子查询

  • 子查询是嵌套在另一个查询中的select-from-where表达式。
  • 子查询嵌套在where子句中,通常用于对集合的成员资格、集合的比较以及集合的基数进行检查。

集合成员资格:
例1,找出在2009年秋季和2010年春季学期同时开课的所有课程

/*连接词in测试元组是否是集合中的成员, * 集合是由select子句产生的一组值构成的,对应的还有not in */ SELECT DISTINCT course_id FROM SECTION WHERE semester='Fall' AND year=2009 AND course_id IN ( SELECT course_id FROM section WHERE semester='Spring' AND year=2010); 

集合的比较:
例1,找出(不同的)学生总数,他们选修了ID为10101的教师所讲授的课程:

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); 

例2,找出满足下面条件的所有教师的姓名,他们的工资至少比Biology系某一个教师的工资要高:

/* * 短语“至少比某一个要大”在SQL中用>some表示,意为对于C>some r,是否存在一个t属于r,令C>t */ SELECT name FROM instructor WHERE salary > SOME(SELECT salary FROM instructor WHERE dept_name='Biology'); 

例3,找出满足下面条件的所有教师的姓名,他们的工资比Biology系每个教师的工资都高:

SELECT salary FROM instructor WHERE salary > ALL(SELECT salary FROM instructor WHERE dept_name='Biology'); 

例4,找出平均工资最高的系:

SELECT dept_name FROM instructor GROUP BY dept_name HAVING AVG(salary) > (SELECT AVG(salary) FROM instructor GROUP BY dept_name); 

空关系测试:
例1,找出在2009年秋季学期和2010年春季学期通识开课的所有课程,使用exists结构,重写该查询:

SELECT course_id FROM section AS S WHERE semester = 'Fall' AND year=2009 AND EXISTS (SELECT * FROM section AS T WHERE semester='Spring' AND year=2010 AND S.course_id=T.course_id); 

例2,找出选修了Biology系开设的所有课程的学生:

SELECT DISTINCT S.ID,S.name FROM student AS S WHERE NOT EXISTS ((SELECT course_id FROM course WHERE dept_name='Biology') EXCEPT (SELECT T.course_id FROM takes AS T WHERE S.ID=T.ID)); 

重复元组存在性测试:
例1,找出所有在2009年最多开设一次的课程:

/*SQL提供一个布尔函数,用于测试在一个子查询的结果中是否存在重复元组。 * 如果作为参数的子查询结果中没有重复的元组unique结构将返回true值 */ SELECT T.course_id FROM course AS T WHERE UNIQUE (SELECT R.course_id FROM section AS R WHERE T.course_id=R.course_id AND R.year=2009); 

例2,找出所有在2009年最少开设两次的课程:

SELECT T.course_id FROM course AS T WHERE NOT UNIQUE(SELECT R.course_id FROM section AS R WHERE T.course_id=R.course_id AND R.year=2009); /*unique, not unique 在oracle8,sql server7中不支持*/ 

From子句中的子查询:
例1,找出系平均工资超过42 000美元的那些系中教师的平均工资:

/* * SQL允许在from子句中使用子查询表达式。任何select-from-where表达式 * 返回的结果都是关系,因而可以被插入到另一个select-from-where中任何 * 关系可以出现的位置 */ /* * 在前面的聚集函数中,我们使用了having写此查询。 * 现在,我们用在from子句中使用子查询重写这个查询: */ SELECT salary FROM (SELECT dept_name AVG(salary) FROM instructor GROUP BY dept_name) AS dept_avg(dept_name,avg_salary) WHERE avg_salary>42000 

例2,找出在所有系中工资总额最大的系的工资总额:

/* * 在此,having子句是无能为力的。 * 但我们可以用from子句的子查询轻易地写出如下查询: */ SELECT MAX(tot_salary) FROM ( SELECT dept_name,sum(salary) FROM instructor GROUP BY dept_name ) AS dept_total(dept_name,totsalary); 

例3,找出具有最大预算值得系:

/* * with子句提供定义临时关系的方法,这个定义只对包含with子句的查询有效 */ WITH max_budget(value) AS (SELECT MAX(budget) FROM department) SELECT budget FROM department,max_budget WHERE department.budget=max_budget.value; 

例4,找出工资总额大于平均值的系

WITH dept_total(dept_name,value) AS (SELECT dept_name,sum(salary) --每个系的工资总和 FROM instructor GROUP BY dept_name), dept_total_avg(value) AS --所有系的平均工资 (SELECT AVG(value) FROM dept_total) SELECT dept_name FROM dept_total A,dept_total_avg B WHERE A.value>=B.value; 

4.1.3 数据库的修改

删除:

  • 只能删除整个元组,而不能只删除某些属性的值。
  • 基本格式:DELETE FROM r WHERE p;

例1,从instructor 关系中删除与Finance系教师相关的所有元组

/* * p代表谓词 * r代表关系 */ DELETE FROM instructor WHERE dept_name='Finance'; 

例2,从instructor关系中删除所有这样的教师元组,他们在位于Watson大楼
的系工作:

DELETE FROM instructor WHERE dept_name in (SELECT dept_name FROM department WHERE building='Watson') 

例3,删除工资低于大学平均工资的教师记录:

DELETE FROM instructor WHERE salary < (SELECT AVG(salary) FROM instructor); /* * 引入一个问题:当我们每次从instructor关系中删除元组时,平均工资会改变吗? * SQL中的解决方案: * 首先,计算出平均工资,并找出要删除的所有元组 * 然后,删除上述找到的所有元组(不重新计算平均工资,也不重新测试元组是否符合删除件) * 在同一SQL语句内,除非外层查询的元组变量引入内层查询,否则内层查询只进行一次 */ 

插入:
例1,假设我们要插入的信息是Computer Science系开设的名为“Database Systems”的课程CS-437,它有4个学分 :

INSERT INTO course VALUE ('CS-437','Database Systems','Comp.Sci',4); /* SQL允许在insert语句中指定属性,所以上述语句还可写为: */ INSERT INTO course(course_id,title,dept_name,credits) VALUE ('CS-437','Database Systems','Comp.Sci',4); /*若上例中, Database Systems”课程的学分未知,插入语句还可写为: */ INSERT INTO course(course_id,title,dept_name) VALUES('CS-437','Database Systems','Comp.Sci.'); 

例2,假设我们想让Music系每个修满144学分的学生成为Music系的教师,其工资为18 000美元

INSERT INTO instructor SELECT ID,name,dept_name,18000 FROM student WHERE dept_name='Music' AND tot_cred >144; 

更新:
例1,假设给工资超过100 000美元的教师涨3%的工资,其余教师涨5% :

UPDATE instructor SET salary = salary*1.03 WHERE salary > 100 000; UPDATE instructor SET salary =salary*1.05 WHERE salary<=100 000; /*我们也可以使用SQL提供的case结构,避免更新次序引发的问题*/ UPDATE instructor SET salary = CASE WHEN salary <= 100 000 THEN salary*1.05 ELSE salary * 1.03 END 

4.1.4 SQL查询语句总结

通用形式:

SELECT <[DISTINCT] c1,c2,...> FROM <r1,...> [WHERE <condition>] [GROUP BY <c1,c2,...> [HAVING <cond2>]] [ORDER BY <c1[desc],[c2[desc|asc],...]>] 

执行顺序:

  • FROM -> WHERE -> GROUP -> HAVING -> SELECT ->ORDER BY

(完)