SQL 查询
1 限定查询:
// 查询
SELECT job,sal*15 + 200*12 + 200*12 + 300*4 From emp;
// 去重查询
// 使用DISTINCT 进行去除重复内容
SELECT DISTINCT job FROM emp;
- 查询基本工资小于1600的全部雇员信息
SELECT * FROM emp WHERE sal<1600;
- 查询姓名是SMITH的雇员信息
字符串需要使用一对单引号或双引号包裹。
SELECT * FROM emp WHERE ename=='SMITH';
- 查询所有职位不是销售人员的信息
SELECT * FROM emp WHERE job!='SALESMAN';
【练习】查询工资范围在1200-1500之间的雇员信息。
SELECT * FROM emp WHERE sal BETWEEN 1200 AND 1500;
2 基数范围运算:
基数范围运算使用关键字IN,可以提供几个数据,只要有一个数据匹配,结果就可以匹配。
【例子】查询出雇员编号为7369,7566,7839,8899(不存在)的雇员信息。
SELECT * FROM emp WHERE empno IN (7369,7566,7839,8899);
// 不是这几个编号的
SELECT * FROM emp WHERE empno NOT IN (7369,7566,7839,8899);
3 模糊查询
SELECT * FROM emp WHERE ename LIKE 'A%';
使用两个通配符配合LIKE关键字使用:
● _
匹配任意一个字符
● %
匹配任意多个(0,1,2,...,n)字符
【例子】
- 查询所有姓名包含A字母的雇员信息。
SELECT * FROM emp WHERE ename LIKE '%A%';
- 查询姓名是A开头的雇员信息。
SELECT * FROM emp WHERE ename LIKE 'A%';
【练习】
- 查询姓名的第二个字母是A的雇员信息。
SELECT * FROM emp WHERE ename LIKE '_A%'
- 查询姓名最后一个字母是S的雇员信息。
SELECT * FROM emp WHERE ename LIKE '%S';
4 空信息
NULL 判断需要使用`
IS
关键字 --
IN
`关键词也不适合NULL
为空: IS NULL
不为空: IS NOT NULL
NOT IS NULL
// 佣金为NULL
SELECT * FROM emp WHERE comm IS NULL;
// 佣金不为NULL -- 两种方式
SELECT * FROM emp WHERE comm IS NOT NULL;
SELECT * FROM emp WHERE NOT comm IS NULL;
5 逻辑运算
AND
OR
NOT
// 1. 查询出工资范围不在1200~1300(闭区间)之间的员工信息。
SELECT * FROM emp WHERE sal NOT BETWEEN 1200 AND 1300;
// 2. 查询出10部门的经理信息。
SELECT * FROM emp WHERE job = 'MANAGER' AND deptno = 10;
// 3. 查询出工资高于3000,或者职位是柜员的全部雇员信息。
SELECT * FROM emp WHERE sal > 3000 OR job = 'CLERK';
// 4. 查询出所有职位不是柜员的信息
SELECT * FROM emp WHERE NOT job = 'CLERK';
6 查询顺序
ORDER BY
指定结果按照顺序,如果指定数据相同,可以继续指定数据
升序:ASC
降序:DESC
【例子】查询所有雇员的信息,按照工资从高到低排序。
SELECT * FROM emp ORDER BY sal DESC;
【例子】查询所有雇员的信息,按照工资从高到低排序,如果工资相同,则按照雇佣日期从早到晚排序。
SELECT * FROM emp ORDER BY sal DESC,hiredate ASC;
作业
【Day1作业】
-
选择部门30的所有员
-
列出所有柜员(CLERK)的姓名、编号和部门编号。
-
列出佣金高于薪金30%的员工姓名、职位和收入(薪金+佣金);
-
找出部门30中所有经理和部门20中所有柜员的详细资料。
-
找出部门30中所有经理、部门20中所有柜员以及既不是经理又不是柜员并且薪金大于等于2000的员工的详细资料。
-
收取佣金的工作有哪些?
-
找出不收取佣金或佣金低于100的员工。
-
显示不带有A的员工的姓名
-
显示姓名字段的任何位置包含E的所有员工的详细资料,显示的结果按照基本工资从高到低排序,如果基本工资相同则按照雇佣时间从早到晚排序,如果雇佣的日期相同,则按照职位的字母顺序排序。
-
显示非经理员工的姓名、编号、部门编号和总收入INCOME(薪金+佣金),按照总收入从高到低排序;如果总收入相同,按照薪金从高到低排序;如果薪金相同,按照部门号从小到大排序。
SELECT * FROM emp WHERE deptno == 30;
SELECT ename,empno,deptno FROM emp WHERE job == 'CLERK';
SELECT ename,job,sal + comm 收入 FROM emp WHERE comm >= sal * 0.3;
SELECT * FROM emp WHERE deptno == 30 AND job == 'MANAGER' OR deptno == 20 AND job =='CLERK';
SELECT * FROM emp WHERE deptno == 30 AND job == 'MANAGER' OR deptno == 20 AND job =='CLERK' OR job NOT IN ('MANAGER','CLERK') AND sal >= 2000;
SELECT * FROM emp WHERE comm IS NOT NULL;
SELECT * FROM emp WHERE comm IS NULL OR comm <= 100;
SELECT * FROM emp WHERE NOT ename LIKE '%A%';
SELECT * FROM emp WHERE ename LIKE '%E%' ORDER BY sal DESC , hiredate ASC,ename ASC;
SELECT ename,empno,deptno,sal + COALESCE(comm, 0) INCOME FROM emp WHERE job != 'MANAGER' ORDER BY sal + COALESCE(comm, 0) DESC,sal DESC,deptno ASC;
7 分页查询 :
LIMIT
表示查询结果数量
OFFSET
表示丢弃前几条数据
【例子】
- 查询前五个雇员的信息。
SELECT * FROM emp LIMIT 5;
函数
1 大小写转换
输入参数:字符串数据或字符串类型列
函数名称:UPPER(大写)、LOWER(小写)
返回值:全大写或小写的字符串
【例子】使用SQL语句,把'Hello World'分别转换为全大写和全小写。
SELECT UPPER('Hello World'),LOWER('Hello World');
查询姓名职位,并输出为小写
SELECT LOWER (ename),LOWER (job)FROM emp;
2 字符串长度
输入参数:字符串数据或字符串类型列
函数名称:LENGTH
返回值:字符串的长度,一个整数
【例子】计算'Hello World'的长度。
SELECT LENGTH('Hello World');
3 字符串截取
输入参数1:字符串数据或字符串类型列
输入参数2:截取起始点
[输入参数3]:截取的长度,此参数不传递表示截取到最后
函数名称:SUBSTR
返回值:截取的子字符串
【例子】从'Hello World'中截取'World'
SELECT SUBSTR('Hello World',7,5);
数据库中序号从1开始,而不是从0开始。
4 数字函数
四舍五入
输入参数1:要处理的数字或数字类型列
[输入参数2]:保留几位小数,如果不传递表示保留整数位
函数名称:ROUND
返回值:四舍五入后的数字
【例子】对123.456进行四舍五入,要求保留2位小数。
SELECT ROUND(123.456,2);
其他函数
● 求模 MOD
● 绝对值 ABS
5 日期和时间计算
如果单独处理日期,使用DATE函数,如果单独处理时间使用TIME函数,如果同时处理日期和时间使用DATETIME函数。
有了日期和时间就可以单独提取日期、时间。
【例子】只显示每个雇员雇佣的日期,不显示时间。
SELECT DATE(hiredate) FROM emp;
【例子】只显示每个雇员雇佣的时间,不显示日期。
SELECT TIME(hiredate) FROM emp;
获取本地日期时间
SELECT DATETIME ('now','localtime');
儒略日
传入数据:指定日期,当前日期用 ==now==
输入参数:指定的日期,当前日期使用'now'
函数名称:JULIANDAY
返回值:指定日期到儒略日的天数(公元前4713年1月1日)
【例子】今天到儒略日的天数。
SELECT JULIANDAY('now');
【例子】求出每个雇员雇佣的姓名和年数。
SELECT ename,(JULIANDAY('now')-JULIANDAY(hiredate))/365 FROM emp;
时间格式化
输入参数1:替换符,字符串类型
输入参数2:要提取数据的日期时间
函数名称:STRFTIME
返回值:格式化后的数据,==类型为字符串==
【例子】查询所有在上半年雇佣的雇员信息。
SELECT * FROM emp
WHERE STRFTIME('%m',hiredate)<='06';
- 查询1981年雇佣的雇员信息。
SELECT * FROM emp WHERE STRFTIME('%Y',hiredate)=='1981';
- 查询冬天(11-2)雇佣的雇员信息。
SELECT * FROM empWHERE STRFTIME('%m',hiredate) BETWEEN "11" AND "12"OR STRFTIME('%m',hiredate) BETWEEN "01" AND "02";
注意
:转换完成的数据为字符串类型,比较时应该与字符串进行对比
6 空值函数
输入参数1:可能为空的数据列
输入参数2:替换值
函数名称:IFNULL
返回值:如果输入参数1为空,返回值为输入参数2;如果输入参数1不为空,返回值为输入参数1
【练习】计算佣金500以下的雇员信息,NULL算作500以下。
SELECT * FROM emp WHERE IFNULL(comm,0) < 500;
7 统计函数
SUM
求和
AVG
平均值
COUNT
计数
MAX
最大值
MIN
最小值
统计函数的主要用途是与后面要学习的GROUP BY
子句联动。
【例子】统计公司雇员人数、支付的总薪金、平均薪金、最高薪金和最低工资。
SELECT COUNT(*),SUM(sal),AVG(sal),MAX(sal),MIN(sal) FROM emp;
【思考】COUNT(*)、COUNT(字段)、COUNT(DISTINCT 字段)的区别。
-- emp表中有几条数据
SELECT COUNT(*) FROM emp;
-- 有几个人有领导
SELECT COUNT(mgr) FROM emp;
-- 有几个人拿佣金
SELECT COUNT(comm) FROM emp;
-- 公司领导有几个人
SELECT COUNT(DISTINCT mgr) FROM emp;
多表查询
1 笛卡尔积
当查询多个表会产生笛卡尔积,类似于两表相乘,因此,需要进行where条件限定,进行过滤冗余数据
【例子】查询每个雇员的编号、姓名、职位、工资、部门编号、部门名称、位置。
分析:
- 确定FROM子句
emp表:empno,ename,job,sal,deptno
dept表:dname,loc
FROM emp e,dept d
- 确定WHERE子句
之前如果查询所有人就不使用WHERE筛选了,多表查询会产生笛卡尔积,需要用WHERE筛选行数。
WHERE e.deptno=d.deptno
- 确定SELECT子句
SELECT empno,ename,job,sal,e.deptno,dname,loc
- 确定ORDER BY子句
- 确定其他子句
- 拼接
SELECT empno,ename,job,sal,e.deptno,dname,loc
FROM emp e,dept d
WHERE e.deptno=d.deptno;
2 内连接 和外连接
内连接
之前的多表查询都是内连接,并且都使用的是SQL89标准,除此之外还有SQL99标准。
【例子】查询所有雇员的姓名和部门名称。
-- SQL89
SELECT ename,dname FROM emp e,dept d WHERE e.deptno=d.deptno;
-- SQL99
SELECT ename,dname
FROM emp e JOIN dept d ON e.deptno=d.deptno;
建议后续使用99标准,原因有二:
● SQL99标准可以解放WHERE子句,简化WHERE逻辑
● SQL99标准更适合后续学习的外连接
外连接
数据库中通常把外连接分为2-3种:
● 左外连接:SQLite支持的外连接
● 右外连接:左外连接相反的方向,SQLite不支持,但是与左外连接可以互相转换
● 全外连接:左外连接+右外连接
3 其他连接
交叉连接
==CROSS==
生成笛卡尔积不消除也被称为“交叉连接”。
下面两种写法等效:
SELECT * FROM emp,dept;
SELECT * FROM emp CROSS JOIN dept;
自然连接
==NATURAL==
多表查询时,自动找到相关联字段,并消除笛卡尔积
SELECT * FROM emp NATURAL JOIN dept;
相当于
SELECT *FROM emp e JOIN dept d ON e.deptno = d.deptno;
但是,不是万能的
非等式关系不会生效
USING 关键字
==USING==
使用USING 关键字消除笛卡尔积,这种方法基于内链接
SELECT * FROM emp JOIN dept USING(deptno);
-- 相当于
SELECT * FROM emp e JOIN dept d ON e.deptno = d.deptno;
交集与并集
如图,交叉部分(橙色)为交集, 全部颜色为并集
UNION
操作表示公共区域叠加一次的==并集==。
【例子】查询公司部门20和基本工资高于1500的雇员信息。
-- 部门20的雇员信息
SELECT * FROM emp WHERE deptno=20;
-- 基本工资高于1500的雇员信息
SELECT * FROM emp WHERE sal>1500;
-- 使用UNION并集操作
SELECT * FROM emp WHERE deptno=20
UNION
SELECT * FROM emp WHERE sal>1500;
UNION ALL
相比 UNION ,交集部分会被叠加为两次
INTERSECT
==INTERSECT==
显示交集
【例子】查询公司部门20且工资高于1500的雇员信息。
SELECT * FROM emp WHERE deptno=20
INTERSECT
SELECT * FROM emp WHERE sal>1500;
分组统计
1 分组依据
对数据进行分组的前提是某些数据之间具有相同的特征,例如emp表中,job、deptno、mgr这三个字段存在不同雇员的重复。
按照job分组,可以分为销售组、经理组......
按照deptno分组,可以分为10部门组、20部门组......
按照mgr分组,可以分为7902组、7698组.......
理论上按照ename这种字段分组是可行的,但是每个组中只有一条记录,这样分组没有意义。
2 分组查询
基本语法
==SELECT== 列名, 聚合函数(列名)
==FROM== 表名
==WHERE== 条件
==GROUP BY== 分组列
==HAVING== 分组过滤条件
==ORDER BY== 排序列;
【例子】按照职位分组,统计出每个职位的平均工资、最高工资、最低工资和人数。
分析:通常分组查询都会伴随分组统计,即分组后使用统计函数(SUM、AVG、COUNT、MAX、MIN)。因为分组后不存在个体了,对于群体而言,无法使用单一的字段(例如ename)进行描述(销售组的员工姓名是什么????)。
- 确定FROM子句
FROM emp
- 确定WHERE子句
所有人都要参与统计,不需要使用WHERE
- 确定GROUP BY子句
GROUP BY job
- 确定SELECT子句
-- 分组之后可以使用分组字段提升分组后查询数据可读性
SELECT job,AVG(sal),MAX(sal),MIN(sal),COUNT(empno)
- 确定ORDER BY子句
- 确定其他子句
- 拼接
SELECT job,AVG(sal),MAX(sal),MIN(sal),COUNT(empno)
FROM emp
GROUP BY job;
3分组筛选
不能在WHERE子句中使用统计函数
对于分组之后的筛选,应该通过HAVING子句实现。
【总结】WHERE与HAVING的区别:
1 WHERE在分组之前使用,是对个体进行筛选;HAVING是在分组之后使用,是对群体进行筛选。
2 WHERE不支持统计函数,HAVING支持统计函数。
3 WHERE可以单独使用,HAVING必须结合GROUP BY使用。
例子
--1.查询出所有雇员和其领导的姓名和年薪。
SELECT e.ename , e.sal*12 + IFNULL(e.comm,0)*12,m.ename , m.sal*12 + IFNULL(m.comm,0)*12 FROM emp e LEFT JOIN emp m ON e.mgr = m.empno;
--2.列出至少有四个员工的所有部门编号、部门名称、部门人数
SELECT e.deptno,dname,COUNT(*) FROM dept d JOIN emp e ON d.deptno = e.deptno GROUP BY d.deptno HAVING COUNT(*)>=4;
--3.列出雇佣日期早于其领导的所有员工的编号,姓名,部门名称,领导名称。
SELECT e.empno,e.ename,d.dname,m.ename FROM emp e JOIN emp m ON e.mgr = m.empno JOIN dept d ON e.deptno = d.deptno WHERE e.hiredate < m.hiredate;
--4.列出所有部门名称和这些部门的员工信息(人数、平均工资、平均服务年限、最高最低工资),同时列出那些没有员工的部门。
SELECT dname,AVG(sal + IFNULL(comm,0)),AVG((JULIANDAY('now') - JULIANDAY(hiredate))/365),MIN(sal + IFNULL(comm,0)) FROM dept d LEFT JOIN emp e ON e.deptno = d.deptno GROUP BY e.deptno;
--5.列出最低工资大于1500的职位名称和以及此职位的人数。
SELECT job,COUNT(*) FROM emp GROUP BY job HAVING MIN(sal) > 1500;
--6.列出各个部门的CLERK(柜员)的最低薪金。
SELECT d.dname, MIN(sal) FROM emp e JOIN dept d ON e.deptno = d.deptno WHERE job = 'CLERK' GROUP BY e.deptno;
--7.求出部门名称中带‘S’字符的部门员工的工资合计和部门的人数
SELECT dname , SUM(sal + IFNULL(comm,0)) ,COUNT(e.empno) FROM dept d LEFT JOIN emp e ON e.deptno = d.deptno WHERE dname LIKE '%S%' GROUP BY deptno;
子查询
子查询指的是在一个查询中嵌入若干个小的查询,也可以理解为查询的嵌套。
绝大多数情况下,子查询具有以下规律:
● 子查询返回的是单行单列(一个数据本身):HAVING WHERE
● 子查询返回单行多列(一条数据):WHERE
● 子查询返回多行多列(一个表):FROM
WHERE 子查询
WHERE的功能主要是控制数据行,对个体数据进行筛选与过滤,WHERE子查询的返回结果一般都是:
● 单行单列
● 多行单列
● 单行多列
返回单行单列
【例子】统计出所有高于公司平均工资的雇员信息。
分析:
第一步,计算公司的平均工资。
SELECT AVG(sal) FROM emp;
第二步,统计公司工资高于2073.2142的雇员信息。
SELECT * FROM emp WHERE sal>2073.2142;
第三步,把第一步的结果嵌套。
SELECT * FROM emp WHERE sal>(SELECT AVG(sal) FROM emp);
【练习】统计出公司最早雇佣的雇员信息,要求使用子查询完成。
select *
from emp
where hiredate=(select min(hiredate) from emp);
返回单行多列
【例子】显示出公司雇佣最早且工资最低的雇员信息。
分析:
第一步,查询出公司最早雇佣的日期和最低的工资。
SELECT MIN(hiredate) FROM emp;
SELECT MIN(sal) FROM emp;
第二步,把上面的结果嵌套在WHERE子句中进行查询。
SELECT * FROM emp
WHERE hiredate=(SELECT MIN(hiredate) FROM emp)
AND sal=(SELECT MIN(sal) FROM emp);
【例子】查询与SCOTT工资且职位相同的雇员信息。
SELECT * FROM emp
WHERE sal=(SELECT sal FROM emp WHERE ename='SCOTT')
AND job=(SELECT job FROM emp WHERE ename='SCOTT')
AND ename<>'SCOTT';
返回多行单列
一个简单的多行单列返回结果如下:
相当于提供了同一个字段的多个数据,可以配合之前的IN操作。
【例子】显示工资跟各个经理相同的雇员信息(包含各个经理)。
分析:
第一步,查询各个经理的工资:
SELECT sal FROM emp WHERE job='MANAGER';
第二步,把上一步的内容作为WHERE的子查询,配合IN进行操作。
SELECT * FROM emp
WHERE sal IN(SELECT sal FROM emp WHERE job='MANAGER');
需要注意的是,IN不能处理NULL。
【练习】查询与每个分析师部门相同的雇员信息,要求不包含分析师。
SELECT *
FROM emp
WHERE deptno IN (SELECT deptno FROM emp WHERE job='ANALYST')
AND job !='ANALYST';
HAVING 子查询
如果有HAVING子查询,一定有GROUP BY子句,并且会有统计函数。
HAVING子查询几乎只支持返回单行单列的结果。
【例子】查询出高于公司平均工资的部门编号和这些部门的平均工资。
分析:
第一步,计算公司的平均工资。
SELECT AVG(sal) FROM emp;
第二步,使用HAVING子查询。
SELECT deptno,AVG(sal)
FROM emp
GROUP BY deptno
HAVING AVG(sal)>(SELECT AVG(sal) FROM emp);
【思考】查询出平均工资最低的职位信息,此职位的人数,此职位的平均工资。
法一:使用本节方法——HAVING子查询
第一步,查询每个职位的平均工资。
SELECT AVG(sal) FROM emp GROUP BY job;
第二步,查询平均最低的职位的平均工资。
SELECT MIN(avs)
FROM (SELECT AVG(sal) avs FROM emp GROUP BY job);
第三步,使用HAVING子查询。
SELECT job,COUNT(empno),AVG(sal)
FROM emp
GROUP BY job
HAVING AVG(sal)=(SELECT MIN(avs)
FROM (SELECT AVG(sal) avs FROM emp GROUP BY job));
虽然上面的做法比较麻烦,但是通用性好。
法二:可以使用之前的LIMIT关键字解决。
第一步,查询所有职位的人数和平均工资。
SELECT job,COUNT(empno),AVG(sal)
FROM emp
GROUP BY job;
第二步,按照平均工资升序。
SELECT job,COUNT(empno),AVG(sal)
FROM emp
GROUP BY job
ORDER BY AVG(sal) ASC;
第三步,直接使用LIMIT保留第一条记录。
SELECT job,COUNT(empno),AVG(sal)
FROM emp
GROUP BY job
ORDER BY AVG(sal) ASC
LIMIT 1;
SELECT 子查询
【例子】显示所有雇员的姓名、职位、部门名称和部门位置。
分析:正常的解法是使用多表查询,关联deptno字段。
SELECT ename,job,dname,loc
FROM emp e LEFT JOIN dept d ON e.deptno=d.deptno;
本节使用子查询实现,不是问题的最优解。
SELECT e.ename,e.job,
(SELECT dname FROM dept WHERE deptno=e.deptno),
(SELECT loc FROM dept WHERE deptno=e.deptno)
FROM emp e;
上面的做法可以训练思维,实际使用中没有任何价值。
FROM 子查询
FROM子句的主要功能是确定查询的数据表来源,表是一种行列的集合,因此FROM子句出现的子查询结果通常是多行多列的。
【例子】查询出每个部门的编号、名称、位置、部门人数和平均工资。
法一:直接使用多表查询。
SELECT d.deptno,dname,loc,COUNT(empno),AVG(sal)
FROM dept d LEFT JOIN emp e ON d.deptno=e.deptno
GROUP BY dname;
emp表中有14条数据,dept表中有4条数据,直接使用多表查询,虽然消除了笛卡尔积,但是中间的过程仍然生成笛卡尔积,数据量=14x4=56
法二:使用子查询
第一步,只考虑emp表,原题删减为:
查询出每个部门的编号、名称、位置、部门人数和平均工资。
SELECT deptno,COUNT(empno),AVG(sal)
FROM emp
GROUP BY deptno;
第二步,把第一步查询的结果看做是一张表——temp表,使用temp表与dept表进行多表查询,完成原题的功能。
查询出每个部门的编号、名称、位置、部门人数和平均工资。
SELECT d.deptno,dname,loc,temp.部门人数,temp.平均工资
FROM dept d LEFT JOIN temp ON d.deptno=temp.deptno;
上面的语句无法运行,把temp的子查询套入。
SELECT d.deptno,dname,loc,temp.ce,temp.av
FROM dept d LEFT JOIN (
SELECT deptno,COUNT(empno) ce,AVG(sal) av
FROM emp
GROUP BY deptno) temp
ON d.deptno=temp.deptno;
第一步子查询,针对emp单表查询,最多操作14条数据,返回了3条数据(最多4条)。
第二步子查询(内嵌的子查询与dept表关联),也生成笛卡尔积,最多4*4=16条数据。
两步总计操作数据量=14+16=30条
性能差距54/30=1.8倍。
以上数据量比较小,如果emp表和dept表的数据量扩充100倍,性能差距会更大(自己可以算算)。
总结
除了FROM子查询以外,其他的子查询:
● 训练思维
● 提升编写效率
FROM子查询除了具有以上两个优势外,还可以提升执行效率。