[数据库]MySQL之SQL查询

Posted 千千寰宇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[数据库]MySQL之SQL查询相关的知识,希望对你有一定的参考价值。

一 关系表的介绍

* 课程表
* 学生表
* 选课表

二 SQL查询语句格式

/* SQL查询的一般格式
	SELECT [ALL | DISTINCT] [* | coulmnNameA,coulmnNameB,...] 
		FROM [tableName | viewName | (select语句)] 
		[AS 结果表的别名]
		[WHERE 条件表达式x]
		[GROUP BY coulmnName1 [HAVING 条件表达式y]]
		[ORDER BY coulmnName2 [ASC | DESC]]
		[LIMIT startRow,targetRows]
	【各子句的处理顺序】FROM - WHERE - GROUP BY - HAVING - SELECT - ORDER BY
	【关键词释义】
		+ GROUP BY coulmnName : 将结果按 coulmnName 的值分组·,该属性列中值相等的分为一组
		+ HAVING 条件表达式 : 若 GROUP BY 子句带HAVING短语,则: 只有满足指定条件的组才予以输出
*/

三 查询案例

3.1 单表查询:选择表中的若干列

3.1.1 查询指定列/全部列
-- 1查询全体学生的姓名、学号、所在专业
SELECT s.sname,s.sno,s.sdept 
    FROM student AS s;
3.1.2 查询经过计算后的值的列
-- 2查询全体学生的姓名及其出生年份
SELECT s.sname,year(now())-s.sage 
    FROM student AS s;
-- 3查询全体学生的姓名、出生年份和所在院系(要求:用小写字母表示专业名)
SELECT s.sname,year(now())-s.sage, LOWER(s.sdept) 
    FROM student AS s;

3.2 单表查询:选择表中的若干行(元组/记录)

3.2.1 消除取值重复的行(DISTINCT)

若不消除查询结果中的重复行,则:不声明 DISTINCT ,即使用默认的 ALL

-- 4查询已选过课程的学生学号
SELECT DISTINCT s.sno 
    FROM student AS s;

3.3 WHERE子句 :查询满足条件的若干行(元组/记录)

查询满足指定条件的元组可通过 WHERE 子句 实现。
但,记住:【WHERE子句】中是不能用【聚集函数】作为条件表达式的;
WHERE 子句 常用的查询条件,如下图所示:

-- 5查询计算机科学系(CS)全体学生的名单
SELECT s.sno,s.sname
    FROM student AS s
    WHERE s.sdept=\'CS\'
-- 6查询考试不及格的学生的学号和姓名 (多表查询)
SELECT s.sno, s.sname 
	FROM student AS s
	WHERE s.sno IN (
			SELECT sc.sno 
				FROM select_course AS sc
				WHERE sc.grade<60
	);
-- 7查询计算机科学专业(CS)、数学专业(MA)和信息专业(IS)学生的姓名和性别
SELECT s.sname, s.ssex 
    FROM student AS s
    WHERE s.sdept IN (\'CS\', \'MA\', \'IS\');

(涉及字符串的匹配)

-- 8查询姓\'刘\'的学生的姓名、学号和性别
SELECT s.sname, s.sno, s.ssex 
    FROM student AS s
    WHERE s.sname LIKE \'刘%\'; -- %表示满足任意长度(length≥0)的字符串
-- 9查询第二个字为\'晨\'的学生的姓名、学号和性别
SELECT s.sname, s.sno, s.ssex 
    FROM student AS s
    WHERE s.sname LIKE \'_晨%\'; -- _表示任意的单个字符

(涉及空值的查询)

-- 10某些学生选修课程后没有参加考试,故存在选课记录;但没有考试成绩。
-- 查询:缺少成绩的学生的学号和对应的课程号
SELECT sc.sno, sc.cno
    FROM select_course AS sc
    WHERE sc.grade IS NULL; /* Grade为空值; 此处 IS 无法用等号(=)替代 */

(涉及同一表中多重条件的查询)

-- 11查询:计算机科学专业中年龄在20岁以下的学生的学号和姓名
SELECT s.sno, s.sname
    FROM student AS s
    WHERE s.sdept=\'CS\' AND s.sage<20;

(涉及聚集函数)
注意1: 【WHERE子句】中是不能用聚集函数作为条件表达式的;
注意2: 聚集函数只能用于【SELECT子句】和【GROUP BY中的HAVING子句】

-- 12查询:选修了课程的学生人数
SELECT COUNT(DISTINCT s.sno) AS 选课总人数
    FROM student AS s;
-- 之所以DISTINCT,是因为1名学生可以选N个课程
-- 13查询:选修1号课程的学生平均成绩
SELECT AVG(sc.grade)
    FROM select_course AS sc
    WHERE sc.cno=\'1\';

3.4 GROUP BY 子句 > HAVING子句

  • GROUP BY子句 将查询结果按某一列或多列的值分组,值相等的为一组。
    • 对查询结果分组的目的是为了细化聚集函数的作用对象。
    • 若未对查询结果分组,聚集函数将作用于整个查询结果。
  • 【分组后,聚集函数将作用于每一个组,即 每个组都有一个函数值】
  • WHERE子句与GROUP BY子句的比较
    • 无GROUP BY子句: 聚集函数将作用于【整个查询结果】
    • 有GROUP BY子句: 聚集函数将作用于【满足条件的分组】
  • WHERE子句与HAVING子句的区别:作用对象不同
    • WHERE子句 作用于: 基表or视图,从中选择满足条件的【元组】
      • 聚集函数不能作用于: WHERE子句
    • HAVING子句 作用于: 查询结果中的分组,从中选择满足条件的【分组】
-- 14查询:求各课程号及相应的选课人数
SELECT sc.cno,COUNT(sc.sno)
    FROM select_course AS sc
    GROUP BY sc.cno;
-- 15查询:选修了3门以内课程的学生学号
SELECT sc.sno
    FROM select_course AS sc
    GROUP BY sc.sno
        HAVING COUNT(*)<=3;
-- 16查询:平均成绩≤90分的学生学号和平均成绩
SELECT sc.sno, AVG(sc.grade)
    FROM select_course AS sc
    GROUP BY sc.sno
        HAVING AVG(sc.grade)>=90;

3.5 连接查询(多表查询)

-- 17查询:每个学生及其选修课程的情况
-- A方式:等值连接
SELECT s.*, c.*, sc.*
    FROM student AS s, select_course AS sc, course AS c
    WHERE s.sno = sc.sno AND sc.cno = c.cno;
-- B方式:自然连接(等值连接中把目标列中重复的属性列去掉)
SELECT s.sno,s.sname,s.ssex,s.sage, c.cno, c.cname, sc.grade AS \'课程成绩\'
    FROM student AS s, select_course AS sc, course AS c
    WHERE s.sno = sc.sno AND sc.cno = c.cno;
-- 18查询:选修2号课程且成绩在85分以上的所有学生的学号、姓名、课程名和成绩
SELECT s.sno,s.sname, c.cname, sc.grade
    FROM student AS s, select_course AS sc, course AS c
    WHERE s.sno = sc.sno AND sc.cno = c.cno AND sc.cno=\'2\' AND sc.grade>85;

(自身连接:1个表与其自身表进行连接,称为:表的【自身连接】)

-- 自身连接
-- 19查询:每一门课的间接先修课(即 先修课的先修课)
SELECT c1.cno AS \'当前课程号(c1)\', c1.cname AS \'当前课程名(c1)\', c1.cpno AS \'当前的先修课号(c1)\', 
       c2.cno AS \'当前的先修课号(c2)\', c2.cname AS \'当前的先修课程名(c2)\', c2.cpno AS \'【当前的先修课程的先修课程号(c2)】\'
    FROM course AS c1, course AS c2
    WHERE c1.cpno = c2.cno;
-- 外连接

-- 左外连接/左连接 :  tableA LEFT [OUTER] JOIN tableB ON tableA.columnX = tableB.columnX;
-- 20查询:每个学生的选课情况。且满足:没有选课的同学也需保留(即 若某个学生没有选课,仍把Student的悬浮元组保留在结果关系中。)
-- (本查询任务,可对比、参见查询17的等值连接、自然连接)
-- 【A简单版本】(仅student左连接select_course)
--     即 :仅显示select_course表中的课程号/成绩,不显示course表中的课程名
SELECT s.sno,s.sname,s.ssex,s.sage, sc.cno, sc.grade
			FROM student AS s 
					 LEFT OUTER JOIN 
					 select_course AS sc 
					 ON s.sno = sc.sno;
-- 【B复杂/详细版本】(student左连接select_course AS TMP(第1重);TMP左连接course (第2重))
--     即: 不但显示select_course表中的课程号/成绩,还显示course表中的课程名
-- 本查询任务,极易错误(一定不能使用WHERE子句的等值查询,一旦使用,就会出现:可能将未选课的学生记录除去,因为它们并没有选课记录)
SELECT *
	FROM
		(SELECT s.sno,s.sname,s.ssex,s.sage, sc.cno, sc.grade
			FROM student AS s 
				 LEFT OUTER JOIN 
				 select_course AS sc 
				 ON s.sno = sc.sno) AS tmp 
		LEFT JOIN course AS c 
		ON tmp.cno = c.cno;

同理,有【左外连接】,便有【右外连接】

-- 右外连接/右连接 :  tableA RIGHT [OUTER] JOIN tableB ON tableA.columnX = tableB.columnX;
-- (案例,暂略)

(补充笔记↓:对自然连接、全外连接的再次案例解释)

-- 回顾:20查询:每个学生的选课情况。且满足:没有选课的同学信息也需保留(即 若某个学生没有选课,仍把Student的悬浮元组保留在结果关系中。)
-- A 自然连接(NATURAL JOIN)
SELECT s.sno,s.sname,s.ssex,s.sage, sc.cno, sc.grade
    FROM student AS s 
         NATURAL JOIN 
         select_course AS sc;
↑可知,通过该自然连接语句,查询任务未能实现
-- B 全外连接(mysql不支持全外连接;但可通过:全外连接=左外连接 UNION 右外连接【间接实现】)
(
SELECT s.sno,s.sname,s.ssex,s.sage, sc.cno, sc.grade
    FROM student AS s 
         LEFT OUTER JOIN
         select_course AS sc 
         ON s.sno=sc.sno
)
UNION
(
SELECT s.sno,s.sname,s.ssex,s.sage, sc.cno, sc.grade
    FROM student AS s 
         RIGHT OUTER JOIN
         select_course AS sc 
         ON s.sno=sc.sno
);
↑可知,通过该自然连接语句,查询任务未能实现

3.6 嵌套查询:查询块 / 外层查询(父查询) / 内层查询(子查询)

  • 在SQL语言中,【1个SELECT-FROM-WHERE语句】称为【1个查询块】。
  • 将1个查询块【嵌套】在【另1个查询块】的WHERE子句或HAVING短语中的条件中的查询,称为:【嵌套查询(nested query)】
  • SQL语言允许多层嵌套查询 即:1个子查询中还可嵌套其他子查询
    • 子查询的SELECT语句中【不能使用 ORDER BY 子句】
      • ∵ORDER BY子句只能对最终查询结果排序
    • 子查询的分类:按照 子查询的查询条件是否依赖于父查询
      • 子查询不依赖父查询:【不相关子查询】(如:查询任务21/22)
      • 子查询有依赖父查询:【相关子查询】 (如:查询任务23)
  • 有些情况下,【连接查询】与【嵌套查询】可相互转换。
3.6.1 带有IN谓词的子查询
-- 21查询:查询与“刘晨”在同一个系学习的学生信息
-- A方式 嵌套查询
SELECT * 
    FROM student AS s
    WHERE s.sdept IN (
        SELECT sdept
            FROM student
            WHERE student.sname = \'刘晨\'
          );

-- B方式 自身连接查询
SELECT s1.sno,s1.sname,s1.sdept
	FROM student AS s1, student AS s2
	WHERE s1.sdept = s2.sdept AND s2.sname=\'刘晨\';
-- 22查询:选修了课程名为“信息系统”的学生的学号、姓名
SELECT s.sno, s.sname # SELECT 1 (第1层 父查询)
	FROM student AS s
	WHERE s.sno IN(
		SELECT sc.sno # SELECT 2 (第2层 子查询)
			FROM select_course AS sc
			WHERE sc.cno IN (
				SELECT cno # SELECT 3 (第2层 子查询的子查询)
					FROM course AS c
					WHERE c.cname = \'信息系统\'
			)
	);
3.6.2 带有比较运算符的子查询
-- 23查询:找出每个学生达到【他们自己】选修课程平均成绩的课程号
SELECT sc.sno, sc.cno, sc.grade
    FROM select_course AS sc
    WHERE sc.grade >= (
        SELECT AVG(sub_sc.grade)
            FROM select_course AS sub_sc
            WHERE sub_sc.sno = sc.sno # 子查询的sub_sc 调用/依赖 了外层父查询的sc的学号信息
    );
3.6.3 带有ANY/SOME或ALL谓词的子查询
-- 24查询:非计算机科学系中不超过计算机科学系任意一个学生年龄的学生姓名和年龄
-- 【A 嵌套查询+比较+ANY谓词法】
SELECT s.sname, s.sage
    FROM student AS s
    WHERE s.sage <= ANY(
        SELECT sage
            FROM student
            WHERE student.sdept=\'CS\'
    ) AND s.sdept <> \'CS\';

-- 【B 嵌套查询+比较+聚集函数法】
SELECT s.sname, s.sage
    FROM student AS s
    WHERE s.sage <= (
        SELECT MIN(student.sage)
            FROM student
            WHERE student.sdept=\'CS\'
    ) AND s.sdept <> \'CS\';

3.6.4 带有EXISTS谓词的子查询

  • EXISTS:代表存在量词∃。
  • 带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值true 或 逻辑假值false
  • 可利用EXISTS来判断:x∈S、S介绍
-- 25查询:所有选修了1号课程的学生姓名
-- 【A 带有EXISTS谓词的子查询】
SELECT s.sno, s.sname
    FROM student AS s
    WHERE EXISTS (
        SELECT * 
        FROM select_course AS sub_sc
        WHERE sub_sc.sno = s.sno AND sub_sc.cno=\'1\'  
    );

-- 【B 普通的连接查询】
SELECT s.sno, s.sname
    FROM student AS s, select_course AS sc
    WHERE s.sno = sc.sno AND sc.cno = \'1\';
-- 26查询:没有选修1号课程的学生姓名
-- 【A 带有EXISTS谓词的子查询】
SELECT s.sno, s.sname
    FROM student AS s
    WHERE NOT EXISTS (
        SELECT * 
        FROM select_course AS sub_sc
        WHERE sub_sc.sno = s.sno AND sub_sc.cno=\'1\'  
    );

-- 【B 普通的连接查询】
SELECT DISTINCT s.sno, s.sname
    FROM student AS s LEFT JOIN select_course AS sc ON s.sno = sc.sno
    WHERE 
        sc.cno IS NULL # sc.cno IS NULL :没有任何选课记录的同学
	OR # 或者
        sc.sno NOT IN ( # 选过课,但没有选过1号课的同学
            SELECT sub_sc.sno # 选过1号课的所有同学的记录
	        FROM select_course AS sub_sc
		WHERE sub_sc.cno = \'1\'
	);
-- 27查询:选修了全部课程的学生姓名 即: 查找没有一门课没有不选的同学
SELECT s.sno, s.sname 
	FROM student AS s
	WHERE NOT EXISTS ( 
		SELECT * FROM course AS c 
			WHERE NOT EXISTS (
				SELECT * FROM select_course AS sc
					WHERE sc.sno = s.sno AND sc.cno = c.cno
		)
);

3.7 集合查询(交 INTERSECT | 并 UNION | 差 EXCEPT)

  • SELECT语句的查询结果是元组的集合,故:多个SELECT语句的结果可进行集合操作
  • 集合操作,主要包括:
    • 并操作 UNION
    • 交操作 INTERSECT
      • 【MYSQL不支持 INTERSECT功能,需其它途径实现(Eg: INNER JOIN、多条件+AND的WHERE子句)】
    • 差操作 EXCEPT
      • 【MYSQL不支持 EXCEPT 功能,需其它途径实现(Eg: 左(外)连接(LEFT OUTER JOIN)、多条件+AND的WHERE子句等)】
  • 参加集合操作的各查询结果的【列数】必须相同;对应项的【数据类型】也必须相同。

(求并集)

-- 28查询: 计算机科学系的学生【和】年龄不大于19岁的学生信息
-- 【A UNION集合操作】
(
SELECT *
    FROM student AS s
    WHERE s.sdept=\'CS\'
)
UNION
(
SELECT *
    FROM student AS s
    WHERE s.sage<=19
);

-- 【B 普通的多条件的WHERE子句】
SELECT *
    FROM student AS s
    WHERE s.sdept=\'CS\' OR s.sage<=19;

(求交集,But:【MYSQL不支持 INTERSECT 功能,需其它途径实现(Eg: INNER JOIN、多条件+AND的WHERE子句)】)

-- 29查询: 计算机科学系的学生【与】年龄不大于19岁的【交集】的学生信息
-- 【A 利用内连接(INNER JOIN) 间接实现 求交集(INTERSECT)功能】
SELECT a.sno, a.sname, a.ssex, a.sdept
FROM
		(
		SELECT *
				FROM student AS s
				WHERE s.sdept=\'CS\'
		) a
		INNER JOIN
		(
		SELECT *
				FROM student AS s
				WHERE s.sage<=19
		) b
		ON a.sno = b.sno;

-- 【B 普通的多条件WHERE子句】
SELECT s.*
	FROM student AS s
	WHERE s.sdept=\'CS\' AND s.sage<=19;

(求差集,But:【MYSQL不支持 EXCEPT 功能,需其它途径实现(Eg: 左(外)连接(LEFT OUTER JOIN)、多条件+AND的WHERE子句等)】)

-- 30查询: 计算机科学系的学生【与】年龄不大于19岁的【差集】的学生信息 即: 查询 计算机科学系中年龄大于19岁的学生
-- 【A 利用左(外)连接(LEFT [OUTER] JOIN) 间接实现 求差集(EXCEPT)功能】
SELECT a.*, b.*
FROM
		(
		SELECT *
				FROM student AS s
				WHERE s.sdept=\'CS\'
		) a
		LEFT OUTER JOIN
		(
		SELECT *
				FROM student AS s
				WHERE s.sage<=19
		) b
		ON a.sno = b.sno
WHERE b.sno IS NULL; # 选择 a 中 不满足 b条件的那部分元组

-- 【B 普通的多条件WHERE子句】
SELECT s.*
	FROM student AS s
	WHERE s.sdept=\'CS\' AND s.sage>19;

3.8 基于【派生表】的查询

  • 子查询不仅可出现在WHERE子句中,还可出现在FROM子句中,这时子查询生成的【临时派生表】成为主查询的查询对象。
-- 【回顾:23查询】23查询:找出每个学生达到【他们自己】选修课程平均成绩的课程号
-- 【A方式 嵌套查询+WHERE子句】
SELECT sc.sno, sc.cno AS \'【课程号(cno)】\', sc.grade
    FROM select_course AS sc
    WHERE sc.grade > ( -- 当前学生的平均成绩
        SELECT AVG(sub_sc.grade)
            FROM select_course AS sub_sc
            WHERE sub_sc.sno = sc.sno # 子查询的sub_sc 调用/依赖 了外层父查询的sc的学号信息
    );

-- 【B方式 嵌套查询+FROM子句(临时派生表查询)】
SELECT sc.sno,sc.cno
    FROM 
        select_course AS sc, 
        (SELECT sub_sc.sno, AVG(sub_sc.grade) as avg_grade
            FROM select_course AS sc
            GROUP BY sub_sc.sno
        ) AS sub_svg_sc
    WHERE sc.sno=sub_svg_sc.sno AND sc.grade >= sub_svg_sc.avg_grade;

四 临时表

参考文献

MySQL 临时表在我们需要保存一些临时数据时是非常有用的。
临时表只在当前连接可见,当关闭连接时,Mysql会自动删除表并释放所有空间。

如果你使用了其他MySQL客户端程序连接MySQL数据库服务器来创建临时表,那么只有在关闭客户端程序时才会销毁临时表,当然你也可以手动销毁。

4.1 创建临时表

CREATE TEMPORARY TABLE SalesSummary (
    -> product_name VARCHAR(50) NOT NULL
    -> , total_sales DECIMAL(12,2) NOT NULL DEFAULT 0.00
    -> , avg_unit_price DECIMAL(7,2) NOT NULL DEFAULT 0.00
    -> , total_units_sold INT UNSIGNED NOT NULL DEFAULT 0
);

4.2 插入临时表记录

INSERT INTO SalesSummary
    -> (product_name, total_sales, avg_unit_price, total_units_sold)
    -> VALUES
    -> (\'cucumber\', 100.25, 90, 2);

4.3 查询临时表记录

SELECT * FROM SalesSummary;

4.4 删除临时表

DROP TABLE SalesSummary;

当你使用 SHOW TABLES命令显示数据表列表时,你将无法看到 SalesSummary表。
如果你退出当前MySQL会话,再使用 SELECT命令来读取原先创建的临时表数据,那你会发现数据库中没有该表的存在,因为在你退出时该临时表已经被销毁了。

五 文献

以上是关于[数据库]MySQL之SQL查询的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis查询mysql数据返回null

JDBC操作数据库之查询数据

[数据库]MySQL之SQL查询

sql注入之mysql的联合查询爆破

MySQL之DQL数据查询操作

SQL注入之MySQL常用的查询语句