嗯……最实用的部分,其他都可以不会,唯独这个必须熟练吧 —— 数据查询
这篇笔记主要是例子,使用的例子是《笔记(三)》中定义的学生选课关系。使用的SQL语句可能与教材上有些出入,因为我使用的是MySQL 5.7,对SQL语言支持可能会有些许差异。
技术文档:https://dev.mysql.com/doc/refman/5.7/en/select.html
“学生选课表”我也贴过来:http://blog.csdn.net/credolhcw/article/details/55803533
SELECT语句的一般格式
SELECT [ ALL | DISTINCT ] < 目标列表达式 > [ 别名 ],…
FROM < 表名或视图名 > [ 别名 ],…
[ WHERE < 条件表达式 > ]
[ GROUP BY < 列名 > [ HAVING < 条件表达式 > ] ]
[ ORDER BY < 列名 > [ ASC | DESC ] ] ;
单表查询
选择表中的若干列
1. 查询指定列
例1、查询全体学生的学号与姓名。
SELECT Sno,Sname
FROM Student;
2. 查询全部列
例2、查询全体学生的详细记录。
SELECT *
FROM Student;
3. 查询经过计算的值
例3、查询全体学生的姓名及出生年份。
SELECT Sname, 2017-Sage Birthyear
FROM Student;
例4、查询全体学生的姓名、出生年份和所在院系,并要求用小写字母表示所有系名。
SELECT Sname,"Year of Birth",2017-Sage,LOWER(Sdept)
FROM Student;
选择表中的若干元组
1. 消除取值重复的行
例5、查询选修了课程的学生学号。
SELECT DISTINCT(Sno)
FROM SC;
2.查询满足条件的元组
比较大小:“=”,“>”,“>=”,“<”,“<=”,“!= 或 <>”
例6、查询计算机科学系全体学生的名单。
SELECT Sname
FROM Student
WHERE Sdept = 'CS';
确定范围:“[ NOT ] BETWEEN … AND …”
例7、查询年龄在20~23岁(包括20岁和23岁)之间的学生的姓名、系别和年龄。
SELECT Sname,Sdept,Sage
FROM Student
WHERE Sage BETWEEN 20 AND 23;
确定集合:“[ NOT ] IN”
例8、查询计算机科学系(CS)、数学系(MA)和信息系(IS)学生的姓名和性别。
SELECT Sname,Ssex
FROM Student
WHERE Sdept IN ('CS','MA','IS');
字符匹配:“[ NOT ] LIKE ‘<匹配串>’ [ ESCAPE ‘<换码字符>’]”
“%”表示任意长度的字符串。
“_”(下划线),表示任意的单个字符。
例9、查询所有姓刘的学生的姓名、学号和性别。
SELECT Sname,Sno,Ssex
FROM Student
WHERE Sname LIKE '刘%';
例10、查询名字中第二个字为‘晨’,且名字长度为两个字的学生的详细信息。
SELECT *
FROM Student
WHERE Sname LIKE '_晨'; /* 我的MySQL使用的字符集是utf8,所以一个汉字也是一格 */
例11、查询以“DB_”开头,倒数第3个字符为i的课程信息。
SELECT *
FROM Course
WHERE Cname LIKE 'DB/_%i__' ESCAPE '/';
ESCAPE ‘/’:表示符号’/’为转义字符,其后所接的字符不具有通配符的作用。
涉及空值的查询:“IS [ NOT ] NULL”
例12、查询没有先行课要求的课程名及学分。
SELECT Cname,Ccredit
FROM Course
WHERE Cpno IS NULL;
多重条件查询:“AND”,“OR”,“NOT”
例13、查询计算机科学系年龄在20岁以下的学生姓名。
SELECT Sname
FROM Student
WHERE Sdept = 'CS' AND Sage < 20;
ORDER BY 子句
对查询结果按照一个或多个属性列的升序(ASC)或降序(DESC)排列,缺省值为升序。
例14、查询选修了3号课程的学生的学号及其成绩,查询结果按分数降序排序。
SELECT Sno,Grade
FROM SC
WHERE Cno = '3'
ORDER BY Grade DESC;
聚集函数(Aggregate Functions)
官方文档:https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html
常用函数及功能:
COUNT( [ DISTINCT | ALL ] * ):统计元组个数
COUNT( [ DISTINCT | ALL ] <列名> ):统计一列值的个数
SUM( [ DISTINCT | ALL ] <列名> ):计算一列值的总和
AVG( [ DISTINCT | ALL ] <列名> ):计算一列值的平均数
MAX( [ DISTINCT | ALL ] <列名> ):求一列值中的最大值
MIN( [ DISTINCT | ALL ] <列名> ):求一列值中的最小值
例15、查询选修了课程的学生人数。
SELECT COUNT(DISTINCT Sno)
FROM SC;
GROUP BY 子句
将查询结果按某一列或队列的值分组,值相等的为一组。通常是为了细化聚集函数的所用范围,当查询结果分组后,聚集函数将作用于每一分组,即每个分组有一个聚集函数对应的计算结果。
例16、求各课程号及相应的选课人数。
SELECT Cno,COUNT(Sno)
FROM SC
GROUP BY Cno;
若需要对分组后的结果进行进一步筛选,那么就可以使用“HAVING”。
例17、查询有至少两个学生选修的课程号,以及选课人数。
SELECT Cno,COUNT(Sno)
FROM SC
GROUP BY Cno
HAVING COUNT(Sno) >= 2;
注:“WHERE”作用于基本表或试图,而“HAVING”作用于分组。
连接查询
针对两张或两张以上表的查询称为连接查询。
官方文档:https://dev.mysql.com/doc/refman/5.7/en/join.html
等值与非等值连接查询
用“=”进行连接操作称为等值连接,用其他运算符时称为非等值连接。
例18、查询每个学生及其选修课情况。
SELECT Student.*,SC.*
FROM Student,SC
WHERE Student.Sno = SC.Sno;
自身连接
自己连自己,一种自嗨的连接。
例19、查询每一门课程的间接先修课(即先修课的先修课)。
SELECT c1.Cno ID,c2.Cpno PPno
FROM Course c1,Course c2 /* 需要取两个别名 */
WHERE c1.Cpno = c2.Cno;
外连接
不满足连接条件的元组也显示出来,并且在对应位置填空值。
例20、查询每个学生的选修课情况。
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student LEFT JOIN (SC) ON (Student.Sno = SC.Sno);
当几张表用于连接的属性名相同时可以使用“USING”。
The USING(column_list) clause names a list of columns that must exist in both tables.
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student LEFT JOIN (SC) USING (Sno);
复合条件连接
例21、查询每个学生的学号、姓名、选修的课程名及成绩。
SELECT Student.Sno,Sname,Cname,Grade
FROM Student,Course,SC
WHERE Student.Sno = SC.Sno AND SC.Cno = Course.Cno;
嵌套查询
注:子查询中不能使用“ORDER BY”
官方文档:https://dev.mysql.com/doc/refman/5.7/en/subqueries.html
带有 IN 谓词的子查询
官方文档:https://dev.mysql.com/doc/refman/5.7/en/any-in-some-subqueries.html
例22、查询与“刘晨”在同一个系学习的学生。
最容易想到的查询步骤是分两步,第一步查询“刘晨”所在的系 D,第二步查询在系 D 就读的所有学生。用嵌套子查询可以很好的描述这个查询步骤。
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept IN (
SELECT Sdept
FROM Student
WHERE Sname = '刘晨');
例23、查询选修了课程名为“信息系统”的学生学号和姓名。
实现这个查询,必须连接3张表。查询步骤:第一步,连接Course和SC两场表,得到选修了“信息系统”的学生学号集合 S。第二步,查询Student中所有学号在集合 S 中的学生学号和姓名。
SELECT Sno,Sname
FROM Student
WHERE Sno IN (
SELECT Sno
FROM SC,Course
WHERE SC.Cno = Course.Cno AND Cname = '信息系统');
带有比较运算符的子查询
当我们明确知道子查询的结果是一个单值的时候可以直接用比较运算符进行连接,这类情况比较简单和前面两个例子是一样的思路。
这里记录一下相关子查询的例子。子查询的条件依赖父查询时,就叫相关子查询。
例24、找出每一个学生超过他选修课程平均成绩的课程号。
乍一看,第一步求出每个学生的平均成绩,第二步找出他们大于其平均成绩的课程号就可以了。但是在这两部之间就有个问题了,我们怎么把第二步的学生学号传到第一步里面从而求得对应学生的平均成绩咧?
相关子查询的语句如下:
SELECT Sno,Cno
FROM SC x
WHERE Grade >= (
SELECT AVG(Grade)
FROM SC y
WHERE y.Sno = x.Sno );
语句中的“x”和“y”既是表SC的别名,也是元组变量可以表示SC表的一个元组。这条语句的可能执行过程是:
①、从父查询中取一个元组x,将该元组的Sno值传入子查询,以 x.Sno = ‘200215121’为例。子查询执行如下语句:
SELECT AVG(Grade)
FROM SC y
WHERE y.Sno = '200215121';
②、得到的平均值为88.33333,外层再以此值进行判断:
SELECT Sno,Cno
FROM SC x
WHERE Grade >= 88.3333; /* x.Sno = '200215121' */
③、查询得到 ( ‘200215121’ , 1 )。
然后x继续取下一个元组进行上述查询步骤,知道父查询取完所有元组为止。
带有 ANY(SOME)或 ALL 谓词的子查询
使用ANY或ALL谓词时必须同时使用比较运算符。
例25、查询其他系中比计算机系某一学生年龄小的学生姓名和年龄。
查询步骤:第一步,查询计算机系所有学生的年龄集合 S 。 第二步,查询其他系年龄比集合 S 中某一个值小的学生。
SELECT Sname,Sage
FROM Student
WHERE Sdept <> 'CS' AND Sage < ANY(
SELECT Sage
FROM Student
WHERE Sdept = 'CS' );
注:使用聚集函数实现此类功能时的执行效率更高。
带有 EXISTS 谓词的子查询
官方文档:https://dev.mysql.com/doc/refman/5.7/en/exists-and-not-exists-subqueries.html
EXISTS 不返回任何数据,只产生逻辑真“true”或逻辑假“false”。所以,子查询的目标列表达式一般用“*”,因为选择目标列不影响返回的结果。
例26、查询所有选修了1号课程的学生姓名。
SELECT Sname
FROM Student
WHERE EXISTS (
SELECT *
FROM SC
WHERE Cno = '1' AND SC.Sno = Student.Sno );
可能的查询执行方式参考前文的相关子查询。
另外,教材上还提到了 谓词演算,之后专门学一下好了(其实是懒,这次不想继续写)。
集合查询
嗯…MySQL貌似只有UNION这一个相关操作。
Selected columns listed in corresponding positions of each SELECT statement should have the same data type.
参加UNION操作的各查询结果的列数必须相同,对应项的数据类型也必须相同。
官方文档:https://dev.mysql.com/doc/refman/5.7/en/union.html
至于教材上提到的INTERSECT、EXCEPT等操作用前面的一些功能也能做到。
例27、查询计算机科学系的学生及年龄不大于19岁的学生。
SELECT *
FROM Student
WHERE Sdept = 'CS'
INTERSECT
SELECT *
FROM Student
WHERE Sage <= 19;