Database:数据库查询

Floria ·
更新时间:2024-11-01
· 996 次阅读

学习笔记:使用SQL语句进行数据查询 文章目录学习笔记:使用SQL语句进行数据查询@[toc]准备数据基本查询条件查询常用的条件表达式投影查询排序分页查询注意聚合查询分组多表查询连接查询如果你的MySQL不支持FULL OUT JOIN(非常重要)连接图示结果集

本文由华中科技大学人工智能与自动化学院魏靖旻排版,改编自廖雪峰老师的SQL教程。
其中的所有代码都经本人运行以及比对结果过,不同结果也在文章中注明。
如有任何错误或侵权现象,请联系作者。
其中带颜色的是SQL语言代码。

准备数据

students表存储了学生信息:

id class_id name gender score
1 1 小明 M 90
2 1 小红 F 95
3 1 小军 M 88
4 1 小米 F 73
5 2 小白 F 81
6 2 小兵 M 55
7 2 小林 M 85
8 3 小新 F 91
9 3 小王 M 89
10 3 小丽 F 85

classes表存储了班级信息:

id name
1 一班
2 二班
3 三班
4 四班

新建init-test-data.sql的关系数据库代码:

-- 如果test数据库不存在,就创建test数据库: CREATE DATABASE IF NOT EXISTS test; -- 切换到test数据库 USE test; -- 删除classes表和students表(如果存在): DROP TABLE IF EXISTS classes; DROP TABLE IF EXISTS students; -- 创建classes表: CREATE TABLE classes ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 创建students表: CREATE TABLE students ( id BIGINT NOT NULL AUTO_INCREMENT, class_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, gender VARCHAR(1) NOT NULL, score INT NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 插入classes记录: INSERT INTO classes(id, name) VALUES (1, '一班'); INSERT INTO classes(id, name) VALUES (2, '二班'); INSERT INTO classes(id, name) VALUES (3, '三班'); INSERT INTO classes(id, name) VALUES (4, '四班'); -- 插入students记录: INSERT INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'M', 90); INSERT INTO students (id, class_id, name, gender, score) VALUES (2, 1, '小红', 'F', 95); INSERT INTO students (id, class_id, name, gender, score) VALUES (3, 1, '小军', 'M', 88); INSERT INTO students (id, class_id, name, gender, score) VALUES (4, 1, '小米', 'F', 73); INSERT INTO students (id, class_id, name, gender, score) VALUES (5, 2, '小白', 'F', 81); INSERT INTO students (id, class_id, name, gender, score) VALUES (6, 2, '小兵', 'M', 55); INSERT INTO students (id, class_id, name, gender, score) VALUES (7, 2, '小林', 'M', 85); INSERT INTO students (id, class_id, name, gender, score) VALUES (8, 3, '小新', 'F', 91); INSERT INTO students (id, class_id, name, gender, score) VALUES (9, 3, '小王', 'M', 89); INSERT INTO students (id, class_id, name, gender, score) VALUES (10, 3, '小丽', 'F', 85); -- OK: SELECT 'ok' as 'result:';

1.在命令行里执行该脚本(以我的个人路径为例):

注意记得用编译器(我用的Notepad++)转成utf-8编码

mysql -h localhost -u root -p <C:\Users\魏靖旻\Desktop\数据库与SQL\init-test-data.sql Enter password: #输入root用户的密码。

2.在mysql编译环境下执行该脚本

mysql>source /C:/Users/魏靖旻/Desktop/数据库与SQL/init-test-data.sql

检验数据库是否成功

基本查询

使用SELECT * FROM students时,SELECT是关键字,表示将要执行一个查询,*表示“所有列”,FROM表示将要从哪个表查询,本例中是students表。

进入bin目录后

net start mysql #如果没有启动mysql的话 mysql -u root -p #进入登录状态并输入密码 show databases; use test; #test为数据库名,根据你写的sql代码里的数据库的名字 SELECT * FORM students; SELECT * FORM classes;

SELECT语句其实并不要求一定要有FROM子句。

上述查询会直接计算出表达式的结果。虽然SELECT可以用作计算,但它并不是SQL的强项。但是,不带FROM子句的SELECT语句有一个有用的用途,就是用来判断当前到数据库的连接是否有效。许多检测工具会执行一条SELECT 1;来测试数据库连接。

条件查询

SELECT语句可以通过WHERE条件来设定查询条件,查询结果是满足查询条件的记录。例如,要指定条件“分数在80分或以上的学生”,写成WHERE条件就是SELECT * FROM students WHERE score >= 80

其中,WHERE关键字后面的score >= 80就是条件。score是列名,该列存储了学生的成绩,因此,score >= 80就筛选出了指定条件的记录:

SELECT * FROM students WHERE score >= 80; SELECT * FROM WHERE

条件表达式可以用 AND 表达满足条件1并且满足条件2。例如,符合条件“分数在80分或以上”,并且还符合条件“男生”,把这两个条件写出来:

条件1:根据score列的数据判断:score >= 80; 条件2:根据gender列的数据判断:gender = 'M',注意gender列存储的是字符串,需要用单引号括起来。

就可以写出WHERE条件:score >= 80 AND gender = 'M'

第二种条件是 OR ,表示满足条件1或者满足条件2。例如,把上述AND查询的两个条件改为OR,查询结果就是“分数在80分或以上”或者“女生”,满足任意之一的条件即选出该记录:

第三种条件是NOT ,表示“不符合该条件”的记录。例如,写一个“不是2班的学生”这个条件,可以先写出“是2班的学生”:class_id = 2,再加上NOTNOT class_id = 2

上述NOT条件NOT class_id = 2其实等价于class_id 2,因此,NOT查询不是很常用。

要组合三个或者更多的条件,就需要用小括号()表示如何进行条件运算。例如,编写一个复杂的条件:分数在80以下或者90以上,并且是男生:

如果不加括号,条件运算按照NOTANDOR的优先级进行,即NOT优先级最高,其次是AND,最后是OR。加上括号可以改变优先级。

常用的条件表达式
条件 表达式举例1 表达式举例2 说明
使用=判断相等 score = 80 name = ‘abc’ 字符串需要用单引号括起来
使用>判断大于 score > 80 name > ‘abc’ 字符串比较根据ASCII码,中文字符比较根据数据库设置
使用>=判断大于或相等 score >= 80 name >= ‘abc’
使用<判断小于 score < 80 name <= ‘abc’
使用<=判断小于或相等 score <= 80 name <= ‘abc’
使用判断不相等 score 80 name ‘abc’
使用LIKE判断相似 name LIKE ‘ab%’ name LIKE ‘%bc%’ %表示任意字符,例如’ab%‘将匹配’ab’,‘abc’,‘abcd’

例:查询60-90之间的:

SELECT * FROM students WHERE score >= 60 AND score <= 90 SELECT * FROM students WHERE score BETWEEN 60 AND 90 投影查询

使用SELECT * FROM WHERE 可以选出表中的若干条记录。我们注意到返回的二维表结构和原表是相同的,即结果集的所有列与原表的所有列都一一对应。

如果我们只希望返回某些列的数据,而不是所有列的数据,我们可以用SELECT 列1, 列2, 列3 FROM ...,让结果集仅包含指定列。这种操作称为投影查询。

例如,从students表中返回idscorename这三列:

SELECT id, score, name FROM students;

这样返回的结果集就只包含了我们指定的列,并且,结果集的列的顺序和原表可以不一样。

使用SELECT 列1, 列2, 列3 FROM ...时,还可以给每一列起个别名,这样,结果集的列名就可以与原表的列名不同。它的语法是SELECT 列1 别名1, 列2 别名2, 列3 别名3 FROM ...

例如,以下SELECT语句将列名score重命名为points,而idname列名保持不变:

SELECT id, score point, name FROM students

投影查询同样可以接WHERE条件,实现复杂的查询:

SELECT id, score point, name FROM students WHERE gender = 'M'; 排序

我们使用SELECT查询时,细心的读者可能注意到,查询结果集通常是按照id排序的,也就是根据主键排序。这也是大部分数据库的做法。如果我们要根据其他条件排序怎么办?可以加上ORDER BY子句。例如按照成绩从低到高进行排序:

SELECT id, score, name FROM students ORDER BY score;

如果要反过来,按照成绩从高到底排序,我们可以加上DESC表示“倒序”:

SELECT id, score, name FROM students ORDER BY score DESC;

如果score列有相同的数据,要进一步排序,可以继续添加列名。例如,使用ORDER BY score DESC, gender表示先按score列倒序,如果有相同分数的,再按gender列排序:

SELECT id, score, name FROM students ORDER by score DESC, gender;

默认的排序规则是ASC:“升序”,即从小到大。ASC可以省略,即ORDER BY score ASCORDER BY score效果一样。

如果有WHERE子句,那么ORDER BY子句要放到WHERE子句后面。例如,查询一班的学生成绩,并按照倒序排序:

SELECT id, score, name FROM students where class_id = 1 ORDER BY score DESC; 分页查询

使用SELECT查询时,如果结果集数据量很大,比如几万行数据,放在一个页面显示的话数据量太大,不如分页显示,每次显示100条。

要实现分页功能,实际上就是从结果集中显示第1~100条记录作为第1页,显示第101~200条记录作为第2页,以此类推。

因此,分页实际上就是从结果集中“截取”出第M~N条记录。这个查询可以通过LIMIT OFFSET子句实现。我们先把所有学生按照成绩从高到低进行排序:

SELECT id, name, gender, score FROM students ORDER BY score DESC;

现在,我们把结果集分页,每页3条记录。要获取第1页的记录,可以使用LIMIT 3 OFFSET 0

SELECT id, name ,gender, score FROM students ORDER BY score DESC LIMIT 3 OFFSET 0;

上述查询LIMIT 3 OFFSET 0表示,对结果集从0号记录开始,最多取3条。注意SQL记录集的索引从0开始。

如果要查询第2页,那么我们只需要“跳过”头3条记录,也就是对结果集从3号记录开始查询,把OFFSET设定为3:

SELECT id, name ,gender, score FROM students ORDER BY score DESC LIMIT 3 OFFSET 3;

类似的,查询第3页的时候,OFFSET应该设定为6:

SELECT id, name ,gender, score FROM students ORDER BY score DESC LIMIT 3 OFFSET 6;

查询第4页的时候,OFFSET应该设定为9:

SELECT id, name ,gender, score FROM students ORDER BY score DESC LIMIT 3 OFFSET 3;

由于第4页只有1条记录,因此最终结果集按实际数量1显示。LIMIT 3表示的意思是“最多3条记录”。

可见,分页查询的关键在于,首先要确定每页需要显示的结果数量pageSize(这里是3),然后根据当前页的索引pageIndex(从1开始),确定LIMITOFFSET应该设定的值:

LIMIT总是设定为pageSizeOFFSET计算公式为pageSize * (pageIndex - 1)

这样就能正确查询出第N页的记录集。

如果原本记录集一共就10条记录,但我们把OFFSET设置为20,会得到什么结果呢?

SELECT id, name ,gender, score FROM students ORDER BY score DESC LIMIT 3 OFFSET 20;

OFFSET超过了查询的最大数量并不会报错,而是得到一个空的结果集。

注意

OFFSET是可选的,如果只写LIMIT 15,那么相当于LIMIT 15 OFFSET 0

在MySQL中,LIMIT 15 OFFSET 30还可以简写成LIMIT 30, 15

使用LIMIT OFFSET分页时,随着N越来越大,查询效率也会越来越低。

聚合查询

如果我们要统计一张表的数据量,例如,想查询students表一共有多少条记录,难道必须用SELECT * FROM students查出来然后再数一数有多少行吗?

这个方法当然可以,但是比较弱智。对于统计总数、平均数这类计算,SQL提供了专门的聚合函数,使用聚合函数进行查询,就是聚合查询,它可以快速获得结果。

仍然以查询students表一共有多少条记录为例,我们可以使用SQL内置的COUNT()函数查询:

SELECT COUNT(*) FROM students;

COUNT(*)表示查询所有列的行数,要注意聚合的计算结果虽然是一个数字,但查询的结果仍然是一个二维表,只是这个二维表只有一行一列,并且列名是COUNT(*)

通常,使用聚合查询时,我们应该给列名设置一个别名,便于处理结果:

-- 使用聚合查询并设置结果集的列名为num: SELECT COUNT(*) num FROM students;

COUNT(*)COUNT(id)实际上是一样的效果。另外注意,聚合查询同样可以使用WHERE条件,因此我们可以方便地统计出有多少男生、多少女生、多少80分以上的学生等:

SELECT COUNT(*) boy FROM students WHERE gender = 'M';

除了COUNT()函数外,SQL还提供了如下聚合函数:

函数 说明
SUM 计算某一列的合计值,该列必须为数值类型
AVG 计算某一列的平均值,该列必须为数值类型
MAX 计算某一列的最大值
MIN 计算某一列的最小值

注意,MAX()MIN()函数并不限于数值类型。如果是字符类型,MAX()MIN()会返回排序最后和排序最前的字符。

要统计男生的平均成绩,我们用下面的聚合查询:

SELECT AVG(score) boyaverscore FROM students WHERE gender = 'M';

要特别注意:如果聚合查询的WHERE条件没有匹配到任何行,COUNT()会返回0,而SUM()AVG()MAX()MIN()会返回NULL

SELECT AVG(score) average FROM students WHERE gender = 'X';

每页3条记录,如何通过聚合查询获得总页数?

SELECT CEILING(COUNT(*) / 3) FROM students; 分组

如果我们要统计一班的学生数量,我们知道,可以用SELECT COUNT(*) num FROM students WHERE class_id = 1;。如果要继续统计二班、三班的学生数量,难道必须不断修改WHERE条件来执行SELECT语句吗?

对于聚合查询,SQL还提供了“分组聚合”的功能。我们观察下面的聚合查询:

SELECT COUNT(*) num FROM students GROUP BY class_id;

执行这个查询,COUNT()的结果不再是一个,而是3个,这是因为,GROUP BY子句指定了按class_id分组,因此,执行该SELECT语句时,会把class_id相同的列先分组,再分别计算,因此,得到了3行结果。

但是这3行结果分别是哪三个班级的,不好看出来,所以我们可以把class_id列也放入结果集中:

SELECT class_id, COUNT(*) num FROM students GROUP by class_id;

这下结果集就可以一目了然地看出各个班级的学生人数。我们再试试把name放入结果集:

SELECT name, class_id, COUNT(*) studentnum FROM students GROUP by class_id;

不出意外,执行这条查询我们会得到一个语法错误,因为在任意一个分组中,只有class_id都相同,name是不同的,SQL引擎不能把多个name的值放入一行记录中。因此,聚合查询的列中,只能放入分组的列。

注意:AlaSQL并没有严格执行SQL标准,上述SQL在浏览器可以正常执行,但是在MySQL、Oracle等环境下将报错,请自行在MySQL中测试。

也可以使用多个列进行分组。例如,我们想统计各班的男生和女生人数:

SELECT class_id, gender, COUNT(*) num FROM students GROUP BY class_id, gender;

自我检测:

请使用一条SELECT查询查出每个班级的平均分: SELECT class_id, AVG(score) stuaver FROM students GROUP BY class_id; 请使用一条SELECT查询查出每个班级男生和女生的平均分: SELECT class_id, gender, AVG(score) stuaver FROM students GROUP BY class_id, gender; 多表查询

SELECT查询不但可以从一张表查询数据,还可以从多张表同时查询数据。查询多张表的语法是:SELECT * FROM

例如,同时从students表和classes表的“乘积”,即查询数据,可以这么写:

SELECT * FROM students, classes;

这种一次查询两个表的数据,查询的结果也是一个二维表,它是students表和classes表的“乘积”,即students表的每一行与classes表的每一行都两两拼在一起返回。结果集的列数是students表和classes表的列数之和,行数是students表和classes表的行数之积。

这种多表查询又称笛卡尔查询,使用笛卡尔查询时要非常小心,由于结果集是目标表的行数乘积,对两个各自有100行记录的表进行笛卡尔查询将返回1万条记录,对两个各自有1万行记录的表进行笛卡尔查询将返回1亿条记录。

你可能还注意到了,上述查询的结果集有两列id和两列name,两列id是因为其中一列是students表的id,而另一列是classes表的id,但是在结果集中,不好区分。两列name同理

要解决这个问题,我们仍然可以利用投影查询的“设置列的别名”来给两个表各自的idname列起别名:

SELECT students.id sid, students.name, students.gender, students.score, classes.id cid, classes.name cname FROM students, classes;

部分结果:

注意,多表查询时,要使用表名.列名这样的方式来引用列和设置别名,这样就避免了结果集的列名重复问题。但是,用表名.列名这种方式列举两个表的所有列实在是很麻烦,所以SQL还允许给表设置一个别名,让我们在投影查询中引用起来稍微简洁一点:

-- 该例运行结果与上例一致 SELECT s.id sid, s.name, s.gender, s.score, c.id cid, c.name cname FROM students s, classes c;

注意到FROM子句给表设置别名的语法是FROM , 。这样我们用别名sc分别表示students表和classes表。

多表查询也是可以添加WHERE条件的,我们来试试:

SELECT s.id sid, s.name, s.gender, s.score, c.id cid, c.name cname FROM students s, classes c WHERE s.gender = 'M' AND c.id = 1;

使用多表查询可以获取M x N行记录;

多表查询的结果集可能非常巨大,要小心使用。

连接查询

连接查询是另一种类型的多表查询。连接查询对多个表进行JOIN运算,简单地说,就是先确定一个主表作为结果集,然后,把其他表的行有选择性地“连接”在主表结果集上。

例如,我们想要选出students表的所有学生信息,可以用一条简单的SELECT语句完成:

SELECT s.id, s.name, s.class_id, s.gender, s.score FROM students s;

但是,假设我们希望结果集同时包含所在班级的名称,上面的结果集只有class_id列,缺少对应班级的name列。

现在问题来了,存放班级名称的name列存储在classes表中,只有根据students表的class_id,找到classes表对应的行,再取出name列,就可以获得班级名称。

这时,连接查询就派上了用场。我们先使用最常用的一种内连接——INNER JOIN来实现:

SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score FROM students s INNER JOIN classes c ON s.class_id = c.id;

注意INNER JOIN查询的写法是:

先确定主表,仍然使用FROM 的语法; 再确定需要连接的表,使用INNER JOIN 的语法; 然后确定连接条件,使用ON ,这里的条件是s.class_id = c.id,表示students表的class_id列与classes表的id列相同的行需要连接; 可选:加上WHERE子句、ORDER BY等子句。

使用别名不是必须的,但可以更好地简化查询语句。

那什么是内连接(INNER JOIN)呢?先别着急,有内连接(INNER JOIN)就有外连接(OUTER JOIN)。我们把内连接查询改成外连接查询,看看效果:

SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score FROM students s RIGHT OUTER JOIN classes c ON s.class_id = c.id;

执行上述RIGHT OUTER JOIN可以看到,和INNER JOIN相比,RIGHT OUTER JOIN多了一行,多出来的一行是“四班”,但是,学生相关的列如namegenderscore都为NULL

这也容易理解,因为根据ON条件s.class_id = c.idclasses表的id=4的行正是“四班”,但是,students表中并不存在class_id=4的行。

有RIGHT OUTER JOIN,就有LEFT OUTER JOIN,以及FULL OUTER JOIN。它们的区别是:

INNER JOIN只返回同时存在于两张表的行数据,由于students表的class_id包含1,2,3,classes表的id包含1,2,3,4,所以,INNER JOIN根据条件s.class_id = c.id返回的结果集仅包含1,2,3。

RIGHT OUTER JOIN返回右表都存在的行。如果某一行仅在右表存在,那么结果集就会以NULL填充剩下的字段。

LEFT OUTER JOIN则返回左表都存在的行。如果我们给students表增加一行,并添加class_id=5,由于classes表并不存在id=5的行,所以,LEFT OUTER JOIN的结果会增加一行,对应的class_nameNULL

-- 先增加一列class_id=5:(直接使用mysql命令或者在init-test-data.sql中添加该句代码) INSERT INTO students (class_id, name, gender, score) values (5, '新生', 'M', 88); -- 使用LEFT OUTER JOIN SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score FROM students s LEFT OUTER JOIN classes c ON s.class_id = c.id; 如果你的MySQL不支持FULL OUT JOIN(非常重要)

最后,我们使用FULL OUTER JOIN,它会把两张表的所有记录全部选择出来,并且,自动把对方不存在的列填充为NULL:

SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score FROM students s FULL OUTER JOIN classes c ON s.class_id = c.id;

但是运行结果是这样的:

上网搜索得到解决方案是使用union进行全连接:

SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score FROM students s LEFT OUTER JOIN classes c ON s.class_id = c.id UNION SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score FROM students s RIGHT OUTER JOIN classes c ON s.class_id = c.id;

对于这么多种JOIN查询,到底什么使用应该用哪种呢?其实我们用图来表示结果集就一目了然了。

连接图示结果集

假设查询语句是:

SELECT ... FROM tableA ??? JOIN tableB ON tableA.column1 = tableB.column2;

我们把tableA看作左表,把tableB看成右表,那么INNER JOIN是选出两张表都存在的记录:

LEFT OUTER JOIN是选出左表存在的记录:

RIGHT OUTER JOIN是选出右表存在的记录:

FULL OUTER JOIN则是选出左右表都存在的记录:

JOIN查询需要先确定主表,然后把另一个表的数据“附加”到结果集上;

INNER JOIN是最常用的一种JOIN查询,它的语法是SELECT ... FROM INNER JOIN ON

=“https://weijingmin.oss-cn-shenzhen.aliyuncs.com/sql/image-20200110003603328.png” style=“zoom:50%;” />

LEFT OUTER JOIN是选出左表存在的记录:

RIGHT OUTER JOIN是选出右表存在的记录:

FULL OUTER JOIN则是选出左右表都存在的记录:

JOIN查询需要先确定主表,然后把另一个表的数据“附加”到结果集上;

INNER JOIN是最常用的一种JOIN查询,它的语法是SELECT ... FROM INNER JOIN ON

JOIN查询仍然可以使用WHERE条件和ORDER BY排序。


作者:帅气的小魏



DATABASE 数据 数据库

需要 登录 后方可回复, 如果你还没有账号请 注册新账号