目录
SQL语法基础知识总结
分组
group by
:
group by
子句将记录分组到汇总行中。group by
为每个组返回一个记录。group by
通常还涉及聚合count
,max
,sum
,avg
等。group by
可以按一列或多列进行分组。group by
按分组字段进行排序后,order by
可以以汇总字段来进行排序。
having
:
having
用于对汇总的group by
结果进行过滤。having
一般都是和group by
连用。where
和having
可以在相同的查询中。
having
vs where
:
where
:过滤过滤指定的行,后面不能加聚合函数(分组函数)。where
在group by
前。having
:过滤分组,一般都是和group by
连用,不能单独使用。having
在group by
之后。
分组聚合一定不要忘记加上 group by
,不然只会有一行结果。
函数
文本处理
函数 | 说明 |
---|---|
LEFT() 、RIGHT() | 左边或者右边的字符 |
LOWER() 、UPPER() | 转换为小写或者大写 |
LTRIM() 、RTRIM() | 去除左边或者右边的空格 |
LENGTH() | 长度 |
SOUNDEX() | 转换为语音值 |
其中, SOUNDEX()
可以将一个字符串转换为描述其语音表示的字母数字模式。
SELECT *
FROM mytable
WHERE SOUNDEX(col1) = SOUNDEX('apple')
日期和时间处理
- 日期格式:
YYYY-MM-DD
- 时间格式:
HH:MM:SS
函 数 | 说 明 |
---|---|
AddDate() | 增加一个日期(天、周等) |
AddTime() | 增加一个时间(时、分等) |
CurDate() | 返回当前日期 |
CurTime() | 返回当前时间 |
Date() | 返回日期时间的日期部分 |
DateDiff() | 计算两个日期之差 |
Date_Add() | 高度灵活的日期运算函数 |
Date_Format() | 返回一个格式化的日期或时间串 |
Day() | 返回一个日期的天数部分 |
DayOfWeek() | 对于一个日期,返回对应的星期几 |
Hour() | 返回一个时间的小时部分 |
Minute() | 返回一个时间的分钟部分 |
Month() | 返回一个日期的月份部分 |
Now() | 返回当前日期和时间 |
Second() | 返回一个时间的秒部分 |
Time() | 返回一个日期时间的时间部分 |
Year() | 返回一个日期的年份部分 |
数值处理
函数 | 说明 |
---|---|
SIN() | 正弦 |
COS() | 余弦 |
TAN() | 正切 |
ABS() | 绝对值 |
SQRT() | 平方根 |
MOD() | 余数 |
EXP() | 指数 |
PI() | 圆周率 |
RAND() | 随机数 |
汇总
函 数 | 说 明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
count(*)
,count(列名)
都可以,区别在于,count(列名)
是统计非 NULL 的行数;
AVG()
会忽略 NULL 行。
使用 DISTINCT
可以让汇总函数值汇总不同的值。
SELECT AVG(DISTINCT col1) AS avg_col
FROM mytable
连接
SELECT *
FROM TEACHER JOIN COURSE
ON TEACHER.`TNO` = COURSE.`TNO`
WHERE TEACHER.tno = '825';
ON
和 WHERE
的区别:
- 连接表时,SQL 会根据连接条件生成一张新的临时表。
ON
就是连接条件,它决定临时表的生成。 WHERE
是在临时表生成以后,再对临时表中的数据进行过滤,生成最终的结果集,这个时候已经没有 JOIN-ON 了。
所以总结来说就是:SQL 先根据 ON 生成一张临时表,然后再根据 WHERE 对临时表进行筛选。
SQL 允许在 JOIN
左边加上一些修饰性的关键词,从而形成不同类型的连接,如下表所示:
连接类型 | 说明 |
---|---|
INNER JOIN 内连接 | (默认连接方式)只有当两个表都存在满足条件的记录时才会返回行。 |
LEFT JOIN / LEFT OUTER JOIN 左(外)连接 | 返回左表中的所有行,即使右表中没有满足条件的行也是如此。 |
RIGHT JOIN / RIGHT OUTER JOIN 右(外)连接 | 返回右表中的所有行,即使左表中没有满足条件的行也是如此。 |
FULL JOIN / FULL OUTER JOIN 全(外)连接 | 只要其中有一个表存在满足条件的记录,就返回行。 |
SELF JOIN | 将一个表连接到自身,就像该表是两个表一样。为了区分两个表,在 SQL 语句中需要至少重命名一个表。 |
CROSS JOIN | 交叉连接,从两个或者多个连接表中返回记录集的笛卡尔积。 |
组合
UNION
运算符将两个或更多查询的结果组合起来,并生成一个结果集,其中包含来自 UNION
中参与查询的提取行。
UNION
基本规则:
- 所有查询的 列数和列顺序必须相同。
- 每个查询中涉及表的列的数据类型必须相同或兼容。
- 通常返回的列名取自第一个查询。
默认地,UNION
操作符选取不同的值( 默认去重 )。如果允许重复的值,请使用 UNION ALL
。
SELECT column_name(s) FROM table1
UNION ALL
SELECT column_name(s) FROM table2;
UNION
结果集中的列名总是等于 UNION
中第一个 SELECT
语句中的列名。
JOIN
vs UNION
:
JOIN
中连接表的列可能不同,但在UNION
中,所有查询的列数和列顺序必须相同。UNION
将查询之后的行放在一起(垂直放置),但JOIN
将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。
子查询
子查询可以嵌入 SELECT
、INSERT
、UPDATE
和 DELETE
语句中,也可以和比较运算符(如 =
, <
, >
, <>
等)或逻辑运算符(如 IN
, NOT IN
,BETWEEN, EXISTS
, NOT EXISTS
等)运算符一起使用。
子查询常用在 WHERE
子句和 FROM
子句后边:
- 当用于
WHERE
子句时,根据不同的运算符,子查询可以返回单行单列、多行单列、单行多列数据。子查询就是要返回能够作为 WHERE 子句查询条件的值。 - 当用于
FROM
子句时,一般返回多行多列数据,相当于返回一张临时表,所以需要使用 AS 关键字为该临时表起一个名字,这样才符合FROM
后面是表的规则。这种做法能够实现多表联合查询。
视图(VIEW)
定义:
- 视图是基于 SQL 语句的结果集的可视化的表。
- 视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。
作用:
- 简化复杂的 SQL 操作,比如复杂的联结;
- 只使用实际表的一部分数据;
- 通过只给用户访问视图的权限,保证数据的安全性;
- 更改数据格式和表示。
索引(INDEX)
索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。
优点:
- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点:
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
但是,使用索引一定能提高查询性能吗?
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
约束
SQL 约束用于规定表中的数据规则。
如果存在违反约束的数据行为,行为会被约束终止。
约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。
约束类型:
NOT NULL
- 指示某列不能存储 NULL 值。UNIQUE
- 保证某列的每行必须有唯一的值。PRIMARY KEY
- NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。FOREIGN KEY
- 保证一个表中的数据匹配另一个表中的值的参照完整性。CHECK
- 保证列中的值符合指定的条件。DEFAULT
- 规定没有给列赋值时的默认值。
存储过程
存储过程可以看成是对一系列 SQL 操作的批处理。存储过程可以由触发器,其他存储过程以及 Java, Python,PHP 等应用程序调用。
使用存储过程的好处:
- 代码封装,保证了一定的安全性;
- 代码复用;
- 由于是预先编译,因此具有很高的性能。
创建存储过程:
- 命令行中创建存储过程需要自定义分隔符,因为命令行是以
;
为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 - 包含
in
、out
和inout
三种参数。 - 给变量赋值都需要用
select into
语句。 - 每次只能给一个变量赋值,不支持集合的操作。
-
delimiter // create procedure myprocedure( out ret int ) begin declare y int; select sum(col1) from mytable into y; select y*y into ret; end // delimiter ;
call myprocedure(@ret); select @ret;
需要注意的是:阿里巴巴《Java 开发手册》强制禁止使用存储过程。因为存储过程难以调试和扩展,更没有移植性。
至于到底要不要在项目中使用,还是要看项目实际需求,权衡好利弊即可!
触发器
触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
我们可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。
使用触发器的优点:
- SQL 触发器提供了另一种检查数据完整性的方法。
- SQL 触发器可以捕获数据库层中业务逻辑中的错误。
- SQL 触发器提供了另一种运行计划任务的方法。通过使用 SQL 触发器,您不必等待运行计划任务,因为在对表中的数据进行更改之前或之后会自动调用触发器。
- SQL 触发器对于审计表中数据的更改非常有用。
使用触发器的缺点:
- SQL 触发器只能提供扩展验证,并且不能替换所有验证。必须在应用程序层中完成一些简单的验证。例如,您可以使用 JavaScript 在客户端验证用户的输入,或者使用服务器端脚本语言(如 JSP,PHP,ASP.NET,Perl)在服务器端验证用户的输入。
- 从客户端应用程序调用和执行 SQL 触发器是不可见的,因此很难弄清楚数据库层中发生了什么。
- SQL 触发器可能会增加数据库服务器的开销。
MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。
注意:在 MySQL 中,分号
;
是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。这时就会用到
DELIMITER
命令(DELIMITER 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:DELIMITER new_delemiter
。new_delemiter
可以设为 1 个或多个长度的符号,默认的是分号;
,我们可以把它修改为其他符号,如$
-DELIMITER $
。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了$
,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。
在 MySQL 5.7.2 版之前,可以为每个表定义最多六个触发器。
BEFORE INSERT
- 在将数据插入表格之前激活。AFTER INSERT
- 将数据插入表格后激活。BEFORE UPDATE
- 在更新表中的数据之前激活。AFTER UPDATE
- 更新表中的数据后激活。BEFORE DELETE
- 在从表中删除数据之前激活。AFTER DELETE
- 从表中删除数据后激活。
但是,从 MySQL 版本 5.7.2+开始,可以为同一触发事件和操作时间定义多个触发器。
NEW
和 OLD
:
- MySQL 中定义了
NEW
和OLD
关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 - 在
INSERT
型触发器中,NEW
用来表示将要(BEFORE
)或已经(AFTER
)插入的新数据; - 在
UPDATE
型触发器中,OLD
用来表示将要或已经被修改的原数据,NEW
用来表示将要或已经修改为的新数据; - 在
DELETE
型触发器中,OLD
用来表示将要或已经被删除的原数据; - 使用方法:
NEW.columnName
(columnName 为相应数据表某一列名)
创建触发器
DELIMITER $
CREATE TRIGGER `trigger_insert_user`
AFTER INSERT ON `user`
FOR EACH ROW
BEGIN
INSERT INTO `user_history`(user_id, operate_type, operate_time)
VALUES (NEW.id, 'add a user', now());
END $
DELIMITER ;
注意使用 DELIMITER 命令修改语句结束符
查看触发器
SHOW TRIGGERS;
删除触发器
DROP TRIGGER IF EXISTS trigger_insert_user;
SQL语句练习
构建最经典的“教师-学生-成绩”表练习
构建表插入数据
-- MySQL dump 10.13 Distrib 5.7.17, for macos10.12 (x86_64)
--
-- Host: localhost Database: learn_sql_pdai_tech
-- ------------------------------------------------------
-- Server version 5.7.28
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `COURSE`
--
DROP TABLE IF EXISTS `COURSE`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `COURSE` (
`CNO` varchar(5) NOT NULL,
`CNAME` varchar(10) NOT NULL,
`TNO` varchar(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `COURSE`
--
LOCK TABLES `COURSE` WRITE;
/*!40000 ALTER TABLE `COURSE` DISABLE KEYS */;
INSERT INTO `COURSE` VALUES ('3-105','计算机导论','825'),('3-245','操作系统','804'),('6-166','数据电路','856'),('9-888','高等数学','100');
/*!40000 ALTER TABLE `COURSE` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `SCORE`
--
DROP TABLE IF EXISTS `SCORE`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `SCORE` (
`SNO` varchar(3) NOT NULL,
`CNO` varchar(5) NOT NULL,
`DEGREE` decimal(10,1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `SCORE`
--
LOCK TABLES `SCORE` WRITE;
/*!40000 ALTER TABLE `SCORE` DISABLE KEYS */;
INSERT INTO `SCORE` VALUES ('103','3-245',86.0),('105','3-245',75.0),('109','3-245',68.0),('103','3-105',92.0),('105','3-105',88.0),('109','3-105',76.0),('101','3-105',64.0),('107','3-105',91.0),('101','6-166',85.0),('107','6-106',79.0),('108','3-105',78.0),('108','6-166',81.0);
/*!40000 ALTER TABLE `SCORE` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `STUDENT`
--
DROP TABLE IF EXISTS `STUDENT`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `STUDENT` (
`SNO` varchar(3) NOT NULL,
`SNAME` varchar(4) NOT NULL,
`SSEX` varchar(2) NOT NULL,
`SBIRTHDAY` datetime DEFAULT NULL,
`CLASS` varchar(5) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `STUDENT`
--
LOCK TABLES `STUDENT` WRITE;
/*!40000 ALTER TABLE `STUDENT` DISABLE KEYS */;
INSERT INTO `STUDENT` VALUES ('108','曾华','男','1977-09-01 00:00:00','95033'),('105','匡明','男','1975-10-02 00:00:00','95031'),('107','王丽','女','1976-01-23 00:00:00','95033'),('101','李军','男','1976-02-20 00:00:00','95033'),('109','王芳','女','1975-02-10 00:00:00','95031'),('103','陆君','男','1974-06-03 00:00:00','95031');
/*!40000 ALTER TABLE `STUDENT` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `TEACHER`
--
DROP TABLE IF EXISTS `TEACHER`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `TEACHER` (
`TNO` varchar(3) NOT NULL,
`TNAME` varchar(4) NOT NULL,
`TSEX` varchar(2) NOT NULL,
`TBIRTHDAY` datetime NOT NULL,
`PROF` varchar(6) DEFAULT NULL,
`DEPART` varchar(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `TEACHER`
--
LOCK TABLES `TEACHER` WRITE;
/*!40000 ALTER TABLE `TEACHER` DISABLE KEYS */;
INSERT INTO `TEACHER` VALUES ('804','李诚','男','1958-12-02 00:00:00','副教授','计算机系'),('856','张旭','男','1969-03-12 00:00:00','讲师','电子工程系'),('825','王萍','女','1972-05-05 00:00:00','助教','计算机系'),('831','刘冰','女','1977-08-14 00:00:00','助教','电子工程系');
/*!40000 ALTER TABLE `TEACHER` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-02-06 18:18:25
相关练习
1.查询Score表中的最高分的学生学号和课程号。
select
sno,
CNO
from SCORE
where DEGREE = (
select max(DEGREE)
from SCORE
);
2.查询Score表中至少有5名学生选修的并以3开头的课程的平均分数。
SELECT
AVG(DEGREE) ,
CNO
FROM SCORE
WHERE cno LIKE '3%'
GROUP BY CNO
HAVING COUNT(*)> 5;
3.查询最低分大于70,最高分小于90的Sno列。
SELECT SNO
FROM SCORE
GROUP BY SNO
HAVING MIN(DEGREE) > 70 AND MAX(DEGREE) < 90;
4.查询所有学生的Sname、Cname和Degree列。
SELECT
A.SNAME,
B.CNAME,
C.DEGREE
FROM STUDENT A
JOIN (COURSE B, SCORE C)
ON A.SNO = C.SNO AND B.CNO = C.CNO;
假设使用如下命令建立了一个grade表:
CREATE TABLE `grade` (
`low` NUMERIC(3, 0) NOT NULL,
`upp` NUMERIC(3) NOT NULL,
`rank` VARCHAR(1) NOT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO grade VALUES (90, 100, 'A');
INSERT INTO grade VALUES (80, 89, 'B');
INSERT INTO grade VALUES (70, 79, 'C');
INSERT INTO grade VALUES (60, 69, 'D');
INSERT INTO grade VALUES (0, 59, 'E');
5.现查询所有同学的Sno、Cno和rank列。
SELECT
A.SNO,
A.CNO,
B.RANK
FROM SCORE A, grade B
WHERE A.DEGREE BETWEEN B.LOW AND B.UPP
ORDER BY B.RANK;
6.查询score中选学一门以上课程的同学中分数为非最高分成绩的学生记录
SELECT *
FROM STUDENT WHERE SNO IN (
SELECT SNO
FROM SCORE
WHERE DEGREE < (SELECT MAX(DEGREE) FROM SCORE )
GROUP BY SNO
HAVING COUNT(*) > 1);
7.查询和学号为108的同学同年出生的所有学生的Sno、Sname和Sbirthday列。
SELECT
SNO,
SNAME,
SBIRTHDAY
FROM STUDENT
WHERE YEAR(SBIRTHDAY) = (
SELECT YEAR(SBIRTHDAY)
FROM STUDENT
WHERE SNO = '108'
);
8.查询“张旭“教师任课的学生成绩。
SELECT *
FROM SCORE
WHERE cno = (
SELECT CNO
FROM COURSE
INNER JOIN TEACHER ON COURSE.TNO = TEACHER.TNO AND TNAME = '张旭'
);
/* 两种方式*/
SELECT *
FROM SCORE
WHERE cno = (
SELECT CNO
FROM COURSE
WHERE TNO =( SELECT TNO FROM TEACHER WHERE TNAME = '张旭')
);
9.查询“计算机系”与“电子工程系“不同职称的教师的Tname和Prof
SELECT
tname,
prof
FROM TEACHER
WHERE depart = '计算机系' AND prof NOT IN (
SELECT prof
FROM TEACHER
WHERE depart = '电子工程系'
);
10.查询选修编号为“3-105“课程且成绩至少高于选修编号为“3-245”的同学的Cno、Sno和Degree,并按Degree从高到低次序排序。
SELECT
CNO,
SNO,
DEGREE
FROM SCORE
WHERE CNO = '3-105' AND DEGREE > ANY (
SELECT DEGREE
FROM SCORE
WHERE CNO = '3-245'
)
ORDER BY DEGREE DESC;
11.查询选修编号为“3-105”且成绩高于选修编号为“3-245”课程的同学的Cno、Sno和Degree.
SELECT *
FROM SCORE
WHERE CNO = '3-105' AND DEGREE > ALL (
SELECT DEGREE
FROM SCORE
WHERE CNO = '3-245'
)
ORDER BY DEGREE DESC;
12.查询所有教师和同学的name、sex和birthday.
SELECT
TNAME NAME,
TSEX sex,
TBIRTHDAY birthday
FROM TEACHER
UNION
SELECT
sname NAME,
SSEX sex,
SBIRTHDAY birthday
FROM STUDENT;
13.查询成绩比该课程平均成绩低的同学的成绩表。
SELECT A.*
FROM SCORE A
WHERE DEGREE < (SELECT AVG(DEGREE)
FROM SCORE B
WHERE A.CNO = B.CNO);
/* 下面这种写法是错误的,注意是课程成绩比该课程平均成绩,不是所有课程平均成绩*/
SELECT A.*
FROM SCORE A
WHERE DEGREE < (SELECT AVG(DEGREE)
FROM SCORE B);
14. 查询所有任课教师的Tname和Depart.
/*所有任课教师,需要在课程表里有对应的教师号*/
SELECT
TNAME,
DEPART
FROM TEACHER a
WHERE EXISTS(SELECT *
FROM COURSE b
WHERE a.TNO = b.TNO);
/*或者*/
SELECT
TNAME,
DEPART
FROM TEACHER
WHERE tno IN (SELECT tno FROM COURSE);
15.查询所有未讲课的教师的Tname和Depart.
SELECT
TNAME,
DEPART
FROM TEACHER a
WHERE tno NOT IN (SELECT tno
FROM COURSE);
16.查询至少有2名男生的班号。
SELECT CLASS
FROM STUDENT
WHERE SSEX = '男'
GROUP BY CLASS
HAVING COUNT(SSEX) > 1;
17.查询Student表中每个学生的姓名和年龄
SELECT
SNAME,
YEAR(NOW()) - YEAR(SBIRTHDAY)
FROM STUDENT;
18.查询Student表中最大和最小的Sbirthday日期值。
/* 这样返回的才是两行两个日期类型数据,否则是一行两个,是什么数据类型呢*/
SELECT MIN(SBIRTHDAY) birthday
FROM STUDENT
UNION
SELECT MAX(SBIRTHDAY) birthday
FROM STUDENT;
19.查询和“李军”同性别并同班的同学Sname.
SELECT sname
FROM STUDENT
WHERE (SSEX, CLASS) = (SELECT
SSEX,
CLASS
FROM STUDENT
WHERE SNAME = '李军');
使用函数处理数据
顾客登录名
给出 Customers
表 如下:
cust_id | cust_name | cust_contact | cust_city |
---|---|---|---|
a1 | Andy Li | Andy Li | Oak Park |
a2 | Ben Liu | Ben Liu | Oak Park |
a3 | Tony Dai | Tony Dai | Oak Park |
a4 | Tom Chen | Tom Chen | Oak Park |
a5 | An Li | An Li | Oak Park |
a6 | Lee Chen | Lee Chen | Oak Park |
a7 | Hex Liu | Hex Liu | Oak Park |
【问题】编写 SQL 语句,返回顾客 ID(cust_id
)、顾客名称(cust_name
)和登录名(user_login
),其中登录名全部为大写字母,并由顾客联系人的前两个字符(cust_contact
)和其所在城市的前三个字符(cust_city
)组成。提示:需要使用函数、拼接和别名。
答案:
SELECT cust_id, cust_name, UPPER(CONCAT(SUBSTRING(cust_contact, 1, 2), SUBSTRING(cust_city, 1, 3))) AS user_login
FROM Customers
知识点:
-
截取函数
SUBSTRING()
:截取字符串,substring(str ,n ,m)
(n 表示起始截取位置,m 表示要截取的字符个数)表示返回字符串 str 从第 n 个字符开始截取 m 个字符; -
拼接函数
CONCAT()
:将两个或多个字符串连接成一个字符串,select concat(A,B):连接字符串 A 和 B。 -
大写函数
UPPER()
:将指定字符串转换为大写。
返回 2020 年 1 月的所有订单的订单号和订单日期
Orders
订单表如下:
order_num | order_date |
---|---|
a0001 | 2020-01-01 00:00:00 |
a0002 | 2020-01-02 00:00:00 |
a0003 | 2020-01-01 12:00:00 |
a0004 | 2020-02-01 00:00:00 |
a0005 | 2020-03-01 00:00:00 |
【问题】编写 SQL 语句,返回 2020 年 1 月的所有订单的订单号(order_num
)和订单日期(order_date
),并按订单日期升序排序
答案:
SELECT order_num, order_date
FROM Orders
WHERE month(order_date) = '01' AND YEAR(order_date) = '2020'
ORDER BY order_date
也可以用通配符来做:
SELECT order_num, order_date
FROM Orders
WHERE order_date LIKE '2020-01%'
ORDER BY order_date
知识点:
- 日期格式:
YYYY-MM-DD
- 时间格式:
HH:MM:SS
日期和时间处理相关的常用函数:
函 数 | 说 明 |
---|---|
ADDDATE() | 增加一个日期(天、周等) |
ADDTIME() | 增加一个时间(时、分等) |
CURDATE() | 返回当前日期 |
CURTIME() | 返回当前时间 |
DATE() | 返回日期时间的日期部分 |
DATEDIFF | 计算两个日期之差 |
DATE_FORMAT() | 返回一个格式化的日期或时间串 |
DAY() | 返回一个日期的天数部分 |
DAYOFWEEK() | 对于一个日期,返回对应的星期几 |
HOUR() | 返回一个时间的小时部分 |
MINUTE() | 返回一个时间的分钟部分 |
MONTH() | 返回一个日期的月份部分 |
NOW() | 返回当前日期和时间 |
SECOND() | 返回一个时间的秒部分 |
TIME() | 返回一个日期时间的时间部分 |
YEAR() | 返回一个日期的年份部分 |
使用子查询
返回每个顾客不同订单的总金额
我们需要一个顾客 ID 列表,其中包含他们已订购的总金额。
OrderItems
表代表订单信息,OrderItems
表有订单号:order_num
和商品售出价格:item_price
、商品数量:quantity
。
order_num | item_price | quantity |
---|---|---|
a0001 | 10 | 105 |
a0002 | 1 | 1100 |
a0002 | 1 | 200 |
a0013 | 2 | 1121 |
a0003 | 5 | 10 |
a0003 | 1 | 19 |
a0003 | 7 | 5 |
Orders
表订单号:order_num
、顾客 id:cust_id
order_num | cust_id |
---|---|
a0001 | cust10 |
a0002 | cust1 |
a0003 | cust1 |
a0013 | cust2 |
【问题】
编写 SQL 语句,返回顾客 ID(Orders
表中的 cust_id
),并使用子查询返回 total_ordered
以便返回每个顾客的订单总数,将结果按金额从大到小排序。
答案:
# 写法 1:子查询
SELECT o.cust_id AS cust_id, tb.total_ordered AS total_ordered
FROM (SELECT order_num, Sum(item_price * quantity) AS total_ordered
FROM OrderItems
GROUP BY order_num) AS tb,
Orders o
WHERE tb.order_num = o.order_num
ORDER BY total_ordered DESC
# 写法 2:连接表
SELECT b.cust_id, Sum(a.quantity * a.item_price) AS total_ordered
FROM OrderItems a,Orders b
WHERE a.order_num = b.order_num
GROUP BY cust_id
ORDER BY total_ordered DESC
连接表
返回顾客名称和相关订单号以及每个订单的总价
Customers
表有字段,顾客名称:cust_name
、顾客 id:cust_id
cust_id | cust_name |
---|---|
cust10 | andy |
cust1 | ben |
cust2 | tony |
cust22 | tom |
cust221 | an |
cust2217 | hex |
Orders
订单信息表,含有字段,订单号:order_num
、顾客 id:cust_id
order_num | cust_id |
---|---|
a1 | cust10 |
a2 | cust1 |
a3 | cust2 |
a4 | cust22 |
a5 | cust221 |
a7 | cust2217 |
OrderItems
表有字段,商品订单号:order_num
、商品数量:quantity
、商品价格:item_price
order_num | quantity | item_price |
---|---|---|
a1 | 1000 | 10 |
a2 | 200 | 10 |
a3 | 10 | 15 |
a4 | 25 | 50 |
a5 | 15 | 25 |
a7 | 7 | 7 |
【问题】除了返回顾客名称和订单号,返回 Customers
表中的顾客名称(cust_name
)和 Orders
表中的相关订单号(order_num
),添加第三列 OrderTotal
,其中包含每个订单的总价,并按顾客名称再按订单号对结果进行升序排序。
# 简单的等连接语法
SELECT c.cust_name, o.order_num, SUM(quantity * item_price) AS OrderTotal
FROM Customers c,Orders o,OrderItems oi
WHERE c.cust_id = o.cust_id AND o.order_num = oi.order_num
GROUP BY c.cust_name, o.order_num
ORDER BY c.cust_name, o.order_num
注意,可能有小伙伴会这样写:
SELECT c.cust_name, o.order_num, SUM(quantity * item_price) AS OrderTotal
FROM Customers c,Orders o,OrderItems oi
WHERE c.cust_id = o.cust_id AND o.order_num = oi.order_num
GROUP BY c.cust_name
ORDER BY c.cust_name,o.order_num
这是错误的!只对 cust_name
进行聚类确实符合题意,但是不符合 GROUP BY
的语法。
select 语句中,如果没有 GROUP BY
语句,那么 cust_name
、order_num
会返回若干个值,而 sum(quantity _ item_price)
只返回一个值,通过 group by
cust_name
可以让 cust_name
和 sum(quantity _ item_price)
一一对应起来,或者说聚类,所以同样的,也要对 order_num
进行聚类。
一句话,select 中的字段要么都聚类,要么都不聚类
创建高级连接
返回产品名称和每一项产品的总订单数
Products
表为产品信息表含有字段 prod_id
产品 id、prod_name
产品名称
prod_id | prod_name |
---|---|
a0001 | egg |
a0002 | sockets |
a0013 | coffee |
a0003 | cola |
a0023 | soda |
OrderItems
表为订单信息表含有字段 order_num
订单号和产品 id prod_id
prod_id | order_num |
---|---|
a0001 | a105 |
a0002 | a1100 |
a0002 | a200 |
a0013 | a1121 |
a0003 | a10 |
a0003 | a19 |
a0003 | a5 |
【问题】
使用 OUTER JOIN 联结 Products
表和 OrderItems
表,返回产品名称(prod_name
)和每一项产品的总订单数(不是订单号),并按产品名称升序排序。
SELECT prod_name, COUNT(order_num) AS orders
FROM Products
LEFT JOIN OrderItems
USING(prod_id)
GROUP BY prod_name
ORDER BY prod_name