前言
数据查询
单表查询
连接查询
自身连接
内连接
外连接
多表连接
嵌套查询
集合查询
数据更新
插入数据
修改数据
删除数据
项目需求
本篇博文主要是数据库SQL语句的总结,其中会有一些经常会忽略的小知识点。这里总结了数据查询和数据更新,其中比较重要且有难度的是连接查询和嵌套查询。最后列出了我在项目中所遇到的问题,但是SQL语句并没有做多少优化。主要是想拿出来做个笔记,同时供大家学习和交流,若是有优化的更好的SQL语句,可以在评论区给出。
数据查询是数据库的核心操作。SQL提供了SELECT语句进行数据查询,该语句具有灵活的使用方式和丰富的功能。其一般格式为:
SELECT [ALL|DISTINCT] <目标列表达式>[,<目标列表达式>]...
FROM <表名或视图名> [,<表名或视图名>...]|(
整个SELECT语句的含义是,根据WHERE子句的条件表达式从FROM子句指定的基本表、视图或派生表中找出满足条件的元组,再按SELECT子句中的目标列表达式选出元组中的属性值形成结果表。如果有GROUP BY子句,则将结果按<列名1>的均值进行分组,该属性列值相等的元组为一个组。通常会在每组中作用聚集函数。如果GROUP BY子句带HAVING短语,则只有满足指定条件的组才予以输出。如果有GROUP BY子句,则结果表还要按<列名2>的值的升序或降序排序。
选择表中的若干列
选择表中的全部或部分列即关系代数的投影运算。
(1)查询指定列
在很多情况下,用户只对表中的一部分属性列感兴趣,这时可以通过在SELECT子句的<目标列表达式>中指定要查询的属性列。
(2)查询全部列
将表中的所有属性列都列出来有两种方法,一种方法就是在SELECT关键字后列出所有列名;如果列的显示顺序与其在表中的顺序相同,也可以简单的使用*。
(3)查询经过计算的值
SELECT子句不仅可以是表中的属性列,也可以是表达式。
SELECT
"采集时间:",
em_air_temperature.DateTime,
"数据:",
em_air_temperature.AirTemperatureData+1
FROM
em_air_temperature
WHERE
em_air_temperature.DateTime >= '2019-07-01'
AND em_air_temperature.DateTime <= '2019-07-17'
选择表中的若干元组
(1)消除取值重复的行
两个本来并不完全相同的元组在投影到指定的某些列上后,可能会变成相同的行。可以用DISTINCT消除它们。如果没有DISTINCT关键字,则默认为ALL,即保留结果表中取值重复的行。
SELECT DISTINCT
"采集时间:",
em_air_temperature.DateTime,
"数据:",
em_air_temperature.AirTemperatureData
FROM
em_air_temperature
WHERE
em_air_temperature.DateTime >= '2019-07-01'
AND em_air_temperature.DateTime <= '2019-07-17'
(2)查询满足条件的元组
①查询满足指定条件的元组可以通过WHERE子句实现。
查询条件 谓词
比较 =,>,<,>=,<=,!=,<>,!>,!<;NOT+上述比较运算符
确定范围 BETWEEN AND,NOT BETWEEN AND
确定集合 IN,NOT IN
字符串匹配 LIKE,NOT LIKE
空值 IS NULL,IS NOT NULL
多重条件 AND,OR,NOT
②确定范围
BETWEEN AND,NOT BETWEEN AND可以用来查找属性值是否在指定范围内,其中BETWEEN后是范围的下限,AND后是范围的上限。
SELECT Sname,Sdept,Sage
FROM Student
WHERE Sage BETWEEN 20 AND 23;
③确定集合
谓词IN可以用来查找属性值属于指定集合的元组。
SELECT Sname,Ssex
FROM Student
WHERE Sdept IN('CS','MA','IS');
④字符匹配
谓词LIKE可以用来进行字符串的匹配。其一般格式如下:
[NOT] LIKE '<匹配串>' [ESCAPE'<换码字符>']
其含义是查找指定的属性列值与<匹配串>相匹配的元组。<匹配串>可以是一个完整的字符串,也可以含有通配符%和_。其中:
% ( 百分号 ) 代表任意长度的字符串。例如a%b表示以a开头,以b结尾的任意长度的字符串。如acb、addgb、ab等都满足该匹配串。
_ ( 下横线 ) 代表任意单个字符。例如a_b表示以a开头,以b结尾的长度为3的任意字符串。如acb、afb等都满足该匹配串。
ESCAPE ' \ '表示" \ "为换码符。这样匹配串中紧跟在" \ "后面的字符" _ "不是在具有通配符的含义,转义为普通的" _ "字符。
ORDER BY子句
用户可以使用ORDER BY子句对查询结果按照一个或多个属性列的升序(ASC)或降序(DESC)排列,默认值为升序。
SELECT Sno,Grade
FROM SC
WHERE Cno='3'
ORDER BY Grade DESC;
聚集函数
为了进一步方便用户,增强检索功能,SQL提供了许多聚集函数,主要有:
COUNT(*) 统计元组个数
COUNT ( [DISTINCT|ALL] <列名> ) 统计一列中值的个数
SUM ( [DISTINCT|ALL] <列名> ) 计算一列值的总和(此列必须是数值型)
AVG ( [DISTINCT|ALL] <列名> ) 计算一列值的平均值(此列必须是数值型)
MAX ( [DISTINCT|ALL] <列名> ) 求一列值中的最大值
MIN ( [DISTINCT|ALL] <列名> ) 求一列值中的最小值
如果指定DISTINCT短语,则表示在计算时要取消指定列中的重复值。当聚集函数遇到空值时,除COUNT(*)外,都跳过空值而只处理非空值。注意:WHERE子句中是不能用聚集函数作为条件表达式的。聚集函数只能用于SELECT子句和GROUP BY中的HAVING子句。
GROUP BY子句
GROUP BY子句将查询结果按某一列或多列的值分组,值相等的为一组。对查询结果分组的目的是为了细化聚集函数的作用对象。如果未对查询结果分组,聚集函数将作用于整个查询结果。分组后聚集函数将作用于每一个组,即每一组都有一个函数值。
SELECT Cno,COUNT(Sno)
FROM SC
GROUP BY Cno;
WHERE子句和HAVING短语的区别在于作用对象不同。WHERE子句作用于基本表或视图,从中选择满足条件的元组。HAVING短语作用于组,从中选择满足条件的组。
前面的查询都是针对一个表进行的。若一个查询同时涉及两个以上的表,则称之为连接查询。连接查询是关系数据库中最主要的查询,包括等值连接查询、自然连接查询、非等值连接查询、自身连接查询、外连接查询和复合条件连接查询等。
等值于非等值连接查询
连接查询的WHERE子句中用来连接两个表的条件称为连接条件或连接谓词,其中比较运算符主要有=,>,<,>=,<=,!=,<>,!>,!<等。当连接运算符为=时,称为等值连接。使用其他运算符称为非等值连接。连接谓词中的列名称为连接字段。连接条件中的各连接字段类型必须是可比的,但名字不必相同。
一条SQL语句可以同时完成选择和连接查询,这时WHERE子句是由连接谓词和选择谓词组成的复合条件。
SELECT Student.Sno,Sname
FROM Student,SC
WHERE Student.Sno=SC.Sno AND SC.Cno='2' AND SC.Grade>90;
该查询的一种优化的执行过程是,先从SC中挑选出Cno = ‘2’ 并且Grade > 90的元组形成一个中间关系,再和Student中满足连接条件的元组进行连接得到最终的结果。
该操作不仅可以在两个表之间进行,也可以是一个表与其自己进行连接,称为表的自身连接。
例:查询每一门课的间接先选修课(即先修课的先修课)。
在Course表中只有每门课的直接先修课信息,而没有先修课的先修课。要得到这个信息,必须先对一门课找到其先修课,再按此先修课的课程号查找它的先修课程。这就要将Course表与其自身连接。为此要为Course表取两个别名,一个是FRIST,另一个是SECOND。
该SQL语句为:
SELECT FIRST.Cno,SECOND.Cpno
FROM Course FIRST,Course SECOND
WHERE FIRST.Cpno=SECOND.Cno;
内连接就是表间的主键与外键相连,只取得键值一致的,可以获取双方表中的数据连接方式。语法如下:
SELECT 列名1,列名2... FROM 表1 INNER JOIN 表2 ON 表1.外键=表2.主键 WHERE 条件语句;
SELECT
Student.Name,
College.CollegeName
FROM
Student
INNER JOIN College ON Student.CollegeId = College.CollegeId;
outer join则会返回每个满足第一个(顶端)输入与第二个(底端)输入的联接的行。它还返回任何在第二个输入中没有匹配行的第一个输入中的行。外连接分为三种:左外连接,右外连接,全外连接。这里只介绍左外连接和右外连接。
左外连接是以左表为标准,只查询在左边表中存在的数据,当然需要两个表中的键值一致。语法如下:
SELECT 列名1 FROM 表1 LEFT OUTER JOIN 表2 ON 表1.外键=表2.主键 WHERE 条件语句;
SELECT
Student.Name,
College.CollegeName
FROM
Student
LEFT OUTER JOIN College ON Student.CollegeId = College.CollegeId;
右连接将会以右边作为基准,进行检索。语法如下:
SELECT 列名1 FROM 表1 RIGHT OUTER JOIN 表2 ON 表1.外键=表2.主键 WHERE 条件语句;
SELECT
Student.Name,
College.CollegeName
FROM
Student
RIGHT OUTER JOIN College ON Student.CollegeId = College.CollegeId;
连接操作除了可以是两表连接、一个表与其自身连接外,还可以是两个以上的表进行连接,后者通常称为多表连接。
例:查询每个学生的学号、姓名、选修的课程名及成绩。
SELECT Student.Sno,Sname,Cname,Grade
FROM Student,SC,Course
WHERE Student.Sno=SC.Sno AND SC.Cno=Course.Cno;
关系数据库管理系统在执行多表连接时,通常是先进行两个表的连接操作,再将其连接结果与第三个表进行连接。
在SQL语言中,一个SELECT-FROM-WHERE语句称为一个查询块。将一个查询块嵌套在另一个查询块的WHERE子句或HAVING短语的条件中查询称为嵌套查询。例如:
SELECT Sname
FROM Student
WHERE Sno IN
(SELECT Sno
FROM SC
WHERE Cno='2');
本例中,下层查询块SELECT Sno FROM SC WHERE Cno='2'是嵌套在上层查询块SELECT Sname FROM Student WHERE Sno IN的WHERE条件中的。上层查询或父查询,下层查询块称为内层查询或子查询。
SQL语言语序多层嵌套查询,即一个子查询中还可以嵌套其他子查询。需要特别指出的是,子查询的SELECT语句中不能使用ORDER BY子句,ORDER BY子句只能对最终查询结果排序。
嵌套查询使用户可以用多个简单查询构成复杂的查询,从而增强SQL的查询能力。以层层嵌套的方式来构造程序正是SQL中"结构化"的含义所在。
带有IN谓词的子查询
在嵌套查询中,子查询的结果往往是一个集合,所以谓词IN是嵌套查询中最经常使用的谓词。
例:查询与刘晨在同一个系学习的学生。
先分步来完成此查询,然后再构造嵌套查询。
(1)确定刘晨所在系名
SELECT Sdept
FROM Student
WHERE Sname='刘晨';
(2)查询所有在CS系学习的学生
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept='CS';
构造嵌套查询如下:
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept IN
(SELECT Sdept
FROM Student
WHERE Sname='刘晨');
本例中,子查询的查询条件不依赖于父查询,称为不相关子查询。本例还可以使用自身连接来完成:
SELECT S1.Sno,S1.Sname,S1.Sdept
FROM Student S1,Student S2
WHERE S1.Sdept=S2.Sdept AND S2.Sname='刘晨';
可见,实现同一个查询请求可以有多种方法,不同的方法其执行效率可能会有差别,所以数据库编程人员应该掌握查询语句的调优技术。
例:查询选修了课程名为"信息系统"的学生学号和姓名。
本查询涉及学号、姓名和课程名三个属性。学号和姓名存放在Student表中,课程名存放在Course表中,但Student与Course两个表之间没有直接联系,必须通过SC表建立它们二者之间的联系。所以本查询涉及三个关系。
SELECT Sno,Sname
FROM Student
WHERE Sdept IN
(SELECT Sno
FROM SC
WHERE Cno IN
(SELECT Cno
FROM Course
WHERE Cname='信息系统')
);
本查询同样可以用连接查询实现:
SELECT Student.Sno,Sname
FROM Student,SC,Course
WHERE Student.Sno=SC.Sno AND
SC.Cno=Course.Cno AND
Course.Cname='信息系统';
有些嵌套查询可以用连接运算替代,有些是不能替代的。上述例子中子查询的查询条件不依赖于父查询,这类子查询称为不相关子查询。不相关子查询是较简单的一类子查询。如果查询的查询条件依赖于父查询,这类子查询称为相关子查询,整个查询语句称为相关嵌套查询语句。
带有比较运算符的子查询
例:找出每个学生超过他自己选修课程平均成绩的课程号
SELECT Sno,Cno
FROM SC x
WHERE Grade >= (SELECT AVG(Grade)
FROM SC y
WHERE y.Sno=x.Sno);
x是表SC的别名,有称为元组变量,可以用来表示SC的一个元组。内层查询是求一个学生所有选修课程平均成绩的,至于是哪个学生的平均成绩要看参数x.Sno的值,而该值是与父查询相关的,因此这类查询称为相关子查询。
这个语句的一种可能的执行过程采用以下三个步骤:
(1)从外层查询中取出SC的一个元组x,将元组x的Sno值传送给内层查询。
SELECT AVG(Grade)
FROM SC y
WHERE y.Sno='';
(2)执行内层查询,得到值,用该值代替内层查询,得到外层查询:
SELECT Sno,Cno
FROM SC x
WHERE Grade >= 88;
(3)执行这个查询,得到相应的结果
然后外层查询取出下一个元组重复做上述(1)至(3)步骤的处理,直到外层的SC元组全部处理完毕。求解相关子查询不能像求解不相关子查询那样一次将子查询求解出来,然后求解父查询。内层查询由于与外层查询有关,因此必须反复求值。
带有ANY(SOME)或ALL谓词的子查询
子查询返回单值时可以用比较运算符,但返回多值时要用ANY(有的系统用SOME)或ALL谓词修饰符。而使用ANY或ALL谓词时则必须同时使用比较运算符。其语义如下所示:
>ANY 大于子查询结果中的某个值
>ALL 大于子查询结果中的所有值
=ANY 大于等于子查询结果中的某个值
>=ALL 大于等于子查询结果中的所有值
<=ANY 小于等于子查询结果中的某个值
<=ALL 小于等于子查询结果中的所有值
=ANY 等于子查询结果中的某个值
=ALL 等于子查询结果中的所有值
!=(或<>)ANY 不等于子查询结果中的某个值
!=(或<>)ALL 不等于子查询结果中的任何一个值
例:查询非计算机科学系中比计算机科学系任意一个学生年龄小的学生姓名和年龄。
SELECT Sname,Sage
FROM Student
WHERE Sage'CS';
SELECT语句的查询结果是元组的集合,所以多个SELECT语句的结果可进行集合操作。集合操作主要包括并操作UNION、交操作INTERSECT和差操作EXCEPT。注意:参加集合操作的各查询结果的列数必须相同;对应项的数据类型也必须相同。
例:查询计算机科学系的学生及年龄不大于19岁的学生。
SELECT *
FROM Student
WHERE Sdept='CS'
UNION
SELECT *
FROM Student
WHERE Sage<=19;
本查询实际上是求计算机科学系的所有学生与年龄不大于19岁的学生的并集。使用UNION将多个查询结果合并起来时,系统会自动去掉重复元组。如果要保留重复元组则用UNION ALL操作符。
例:查询计算机科学系的学生与年龄不大于19岁的学生的交集。
SELECT *
FROM Student
WHERE Sdept='CS'
INTERSECT
SELECT *
FROM Student
WHERE Sage<=19;
例:查询计算机科学系的学生与年龄不大于19岁的学生的差集(查询计算机科学系中年龄大于19岁的学生)。
SELECT *
FROM Student
WHERE Sdept='CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage<=19;
基于派生表的查询
子查询不仅可以出现在WHERE子句中,还可以出现在FROM子句中,这时子查询生成的临时派生表成为主查询的查询对象。
例:找出每个学生超过他自己选修课程平均成绩的课程号。
SELECT Sno,Cno
FROM SC,(SELECT Sno,Avg(Grade) FROM SC GROUP BY Sno)
AS Avg_sc(avg_sno,avg_grade)
WHERE SC.Sno=Avg_sc.avg_sno and SC.Grade>=Avg_sc.avg_grade
这里FROM子句中的子查询将生成一个派生表Avg_sc。该表由avg_sno和avg_grade两个属性组成,记录了每个学生的学号及平均成绩。主查询将SC表与Avg_sc按学号相等进行连接,选出选修课成绩大于其平均成绩的课程号。
数据更新操作有三种:向表中添加若干行数据、修改表中的数据和删除表中的若干行数据。
SQL的数据插入语句INSERT通常有两种形式,一种是插入元组,另一种是插入子查询结果。后者可以一次插入多个元组。
插入元组的INSERT语句的格式为
INSERT
INTO <表名>([属性列1,属性列2...])
VALUES(常量1,常量2...)
例:将一个新学生元组插入到Student表中。
INSERT
INTO Student(Sno,Sname,Ssex,Sdept,Sage)
VALUES('201215128','陈晨','男','IS',18);
在INTO子句中指出了表名Student,并指出了新增加的元组在哪些属性上要赋值,属性的顺序可以与CREATE TABLE中的顺序不一样。VALUES子句对新元组的各属性赋值,字符串常数要用单引号括起来。注意:指明属性列,则插入的信息要和属性一致。不指明属性列,插入信息要包括所有属性。
插入子查询结果
子查询不仅可以嵌套在SELECT语句中用以构造父查询的条件,也可以嵌套在INSERT语句中用以生成要插入的批量数据。
INSERT
INTO <表名>([属性列1,属性列2...])
子查询;
例:对每一个系,求学生的平均年龄,并把结果存入数据库。
INSERT
INTO Dept_age(Sdept,Avg_age)
SELECT Sdept,AVG(Sage)
FROM Student
GROUP BY Sdept;
修改操作又称为更新操作,其语句的一般格式为:
UPDATE <表名>
SET <列名>=<表达式> [,<列名>=<表达式>]...
WHERE <条件>;
其功能是修改指定表中满足WHERE子句条件的元组。其中SET子句给出<表达式>的值用于取代相应的属性列值。如果省略WHERE子句,则表示要修改表中的所有元组。
例:
UPDATE Student
SET Sage=22
WHERE Sno='201215121'
UPDATE Student
SET Sage=Sage+1;
带子查询的修改语句
子查询也可以嵌套在UPDATE语句中,用以构造修改的条件。
例:将计算机科学系全体学生的成绩置为0。
UPDATE SC
SET Grade=0
WHERE Sno IN
(SELECT Sno
FROM Student
WHERE Sdept='CS');
删除语句的一般格式为:
DELETE
FROM <表名>
WHERE <条件>;
DELETE语句的功能是从指定表中删除满足WHERE子句条件的所有元组。如果省略WHERE子句则表示删除表中全部元组,但表的定义仍在字典中。也就是说,DELETE语句删除的是表中的数据,而不是关于表的定义。
例:
DELETE
FROM Student
WHERE Sno='201215128';
DELETE
FROM SC;
带子查询的删除语句
子查询同样也可以嵌套在DELETE语句中,用以构造执行删除操作的条件。
例:删除计算机科学系所有学生的选课记录。
DELETE
FROM SC
WHERE Sno IN
(SELETE Sno
FROM Student
WHERE Sdept='CS');
只查询一条数据
SELECT
em_air_temperature.AirTemperatureData,
em_air_temperature.DateTime
FROM
em_air_temperature
WHERE
ChannalNumber = '1'
ORDER BY
em_air_temperature.DateTime DESC
LIMIT 0,1;
查询一整天的数据
SELECT
*
FROM
em_air_temperature
WHERE
ChannalNumber = '1'
AND em_air_temperature.DateTime >='2019-12-12'
AND em_air_temperature.DateTime <= date_add('2019-12-12', INTERVAL 1 DAY);
模糊查询近几天的数据
SELECT
*
FROM
(
SELECT
em_air_temperature.DateTime,
em_air_temperature.AirTemperatureData,
(@rowNum :=@rowNum + 1) AS rowNo
FROM
em_air_temperature,
(SELECT(@rowNum := 0)) b
WHERE
ChannalNumber = '1'
AND em_air_temperature.DateTime >= date_add('2019-12-12', INTERVAL 1 DAY)
AND em_air_temperature.DateTime <= date_add('2019-12-13', INTERVAL 1 DAY)
ORDER BY
em_air_temperature.Id ASC
) AS a
WHERE
MOD (a.rowNo, 20) = 1;
取每张表的一条数据
SELECT
em_air_temperature.AirTemperatureData,
em_soil_temperature.SoilTemperatureData,
em_light_intensity.LightIntensityData,
em_air_humidity.AirHumidityData,
em_soil_moisture.SoilMoistureData,
em_co2_concentration.Co2ConcentrationData
FROM
em_air_temperature,
em_soil_temperature,
em_light_intensity,
em_air_humidity,
em_soil_moisture,
em_co2_concentration
LIMIT 0,1;
或者也行:
SELECT
em_air_temperature.AirTemperatureData,
em_soil_temperature.SoilTemperatureData,
em_light_intensity.LightIntensityData,
em_air_humidity.AirHumidityData,
em_soil_moisture.SoilMoistureData,
em_co2_concentration.Co2ConcentrationData
FROM
em_air_temperature
INNER JOIN em_soil_temperature
INNER JOIN em_light_intensity
INNER JOIN em_air_humidity
INNER JOIN em_soil_moisture
INNER JOIN em_co2_concentration
LIMIT 0,1;
查询每月的数据
SELECT
*
FROM
(
SELECT
em_air_temperature.DateTime,
em_air_temperature.AirTemperatureData,
(@rowNum :=@rowNum + 1) AS rowNo
FROM
em_air_temperature,
(SELECT(@rowNum := 0)) b
WHERE
ChannalNumber = '1'
AND em_air_temperature.DateTime >= '2019-12-01'
AND em_air_temperature.DateTime <= date_add('2019-12-31', INTERVAL 2 DAY)
ORDER BY
em_air_temperature.Id ASC
) AS a
WHERE
MOD (a.rowNo, 60) = 1;
一年中每月的平均数据
SELECT
MONTH (
em_air_temperature.DateTime
) AS MONTH,
ROUND(
AVG(
em_air_temperature.AirTemperatureData
),1 ) AS AVG
FROM
em_air_temperature
WHERE
YEAR (
em_air_temperature.DateTime
) = '2019'
GROUP BY
MONTH (
em_air_temperature.DateTime);
一个月每天的平均值
SELECT
DAY (
em_air_temperature.DateTime
) AS DAY,
ROUND(
AVG(
em_air_temperature.AirTemperatureData
), 1) AS AVG
FROM
em_air_temperature
WHERE
YEAR (
em_air_temperature.DateTime
) = '2019'
AND MONTH (
em_air_temperature.DateTime
) = '12'
GROUP BY
DAY (
em_air_temperature.DateTime
);