Yuens' blog
内容参考《李兴华Oracle数据库》的子查询(链接见文末参考),本节主要包括:[toc] 子查询是整个复杂查询的最重要的一个环节,可以这么说,如果不会子查询,相当于工作都没法干。子查询并不是一个新概念,他是一种概念的融合。
所谓子查询,是指在一个查询中嵌入若干个小的查询。给出的语法如下:
SELECT [DISTINCT] * | 列名称 [别名], 列名称 [别名], … | 统计函数, ( SELECT [DISTINCT] * | 列名称 [别名], 列名称 [别名], … | 统计函数 FROM 数据表 [别名], 数据表 [别名], … [WHERE 条件(s)] [GROUP BY 分组字段, 分组字段,…] [HAVING 分组后过滤] [ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], …]) FROM 数据表 [别名], 数据表 [别名], …, ( SELECT [DISTINCT] * | 列名称 [别名], 列名称 [别名], … | 统计函数 FROM 数据表 [别名], 数据表 [别名], … [WHERE 条件(s)] [GROUP BY 分组字段, 分组字段,…] [HAVING 分组后过滤] [ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], …]) [WHERE 条件(s), ( SELECT [DISTINCT] * | 列名称 [别名], 列名称 [别名], … | 统计函数 FROM 数据表 [别名], 数据表 [别名], … [WHERE 条件(s)] [GROUP BY 分组字段, 分组字段,…] [HAVING 分组后过滤] [ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], …])] [GROUP BY 分组字段, 分组字段,…] [HAVING 分组后过滤, ( SELECT [DISTINCT] * | 列名称 [别名], 列名称 [别名], … | 统计函数 FROM 数据表 [别名], 数据表 [别名], … [WHERE 条件(s)] [GROUP BY 分组字段, 分组字段,…] [HAVING 分组后过滤] [ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], …])] [ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], …];
关于子查询的时候,个人可以给出一些参考方式,供选择(95%的问题都可以解决):
在SELECT子句中也可出现子查询,但很少这样做,同时所有的子查询出现时,一定要有“()”括号。
WHERE的主要功能是控制数据行,那么在WHERE子句中,子查询返回的结果一般是单行单列、多行单列、单行多列数据。
第一步:应该统计出公司的平均工资,返回的是一个数值(单行单列)
SELECT AVG(sal) FROM emp;
第二步:以上的查询返回单行单列数据,可以直接在WHERE子句中使用
SELECT * FROM emp WHERE sal>( SELECT AVG(sal) FROM emp);
第一步:要统计出最早的雇佣日期,使用MIN()函数计算
SELECT MIN(hiredate) FROM emp;
第二步:以上的查询返回的是单行单列,所以在WHERE子句中使用
SELECT * FROM emp WHERE hiredate=( SELECT MIN(hiredate) FROM emp);
第一步:统计出公司最早雇佣日期和最低的工资,此处正好都是一条数据
SELECT MIN(hiredate), MIN(sal) FROM emp;
第二步:返回单行多列,只能够在WHERE子句里使用
SELECT * FROM emp WHERE (hiredate, sal)=( SELECT MIN(hiredate), MIN(sal) FROM emp);
第一步:找到SCOTT的工资和职位
SELECT sal, job FROM emp WJHERE ename='SCOTT';
第二步:在WHERE子句中使用以上查询结果
SELECT * FROM emp WHERE (sal, job)=( SELECT sal, job FROM emp WHERE ename='SCOTT') AND ename<>'SCOTT';
如果子查询返回多行单列,实际上相当于数据的查询范围,如果要想针对范围查询,则要使用三个查询符号:IN、ANY、ALL。
此功能与前面讲的IN是相同的,指的是在指定范围内。
SELECT * FROM emp WHERE sal IN ( SELECT sal FROM emp WHERE job='MANAGER');
SELECT * FROM emp WHERE sal NOT IN( SELECT sal FROM emp WHERE job='MANAGER');
此时就有一个需要注意的小问题了:在使用NOT IN的时候子查询中禁止有NULL,否则不会由任何数据返回。
SELECT * FROM emp WHERE sal=ANY( SELECT sal FROM emp WHERE job='MANAGER');
SELECT * FROM emp WHERE sal>ANY ( SELECT sal FROM emp WHERE job=’MANAGER’);
SELECT * FROM emp WHERE sal<ANY( SELECT sal FROM emp WHERE job=’MANAGER’);
SELECT * FROM emp WHERE sal>ALL( SELECT sal FROM emp WHERE job=’MANAGER’);
SELECT * FROM emp WHERE sal<ALL( SELECT sal FROM emp WHERE job=’MANAGER’);
第一步:找到30部门的工资
SELECT sal FROM emp WHERE deptno=30;
第二步:使用>ALL操作
SELECT * FROM emp WHERE sal>ALL( SELECT sal FROM emp WHERE deptno=30);
对于给定的多行单列查询,只能够利用以上的三个符号完成。
如果使用了HAVING子句就意味着进行了分组,而且进行了统计查询。在HAVING中出现的子查询只能够返回单行单列的数据。
第一步:计算拿出公司的平均工资
SELECT AVG(sal) FROM emp;
第二步:要在分组后进行过滤
SELECT deptno, AVG(sal) FROM emp GROUP BY deptno HAVING AVG(sal)>( SELECT AVG(sal) FROM emp);
第一步:找到平均工资最低的职位数据,这个操作需统计函数嵌套。
SELECT MIN(AVG(sal)) FROM emp GROUP BY job;
第二步:统计函数嵌套后无法再出现分组字段,可将以上单行单列结果出现HAVING子句之中。
SELECT job, AVG(sal) FROM emp GROUP BY job HAVING AVG(sal)=( SELECT MIN(AVG(sal)) FROM emp GROUP BY job);
首先明确一点,在SELECT子句之中使用子查询,只有一种情况下使用(而且此种情况完全是自己没事找事):进行行列转置(行变列,列变行)。
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子句里出现的子查询,其返回的结果一定是多行多列数据。
SELECT d.deptno, dname, d.loc, COUNT(e.empno), AVG(e.sal) FROM emp e, dept d WHERE e.deptno(+)=d.deptno GROUP BY d.deptno, d.dname, d.loc;
SELECT deptno, COUNT(empno), AVG(sal) FROM emp GROUP BY deptno;
SELECT d.deptno, d.dname, d.loc, temp.count, temp.avg FROM dept d, ( SELECT deptno, COUNT(empno) count, AVG(sal) FROM emp GROUP BY deptno) temp WHERE d.deptno=temp.deptno(+);
通过第二种实现,但相对于第一种,代码变复杂了,为了分析好处,假设数据扩大1000倍,即:emp表中有14000条数据,dept表中有4000条数据。
范例:要求查询出高于公司平均工资的雇员姓名、职位、工资、部门名称、部门人数、部门平均工资、工资等级、此等级的雇员人数、领导姓名、领导职位
第一步:找到公司的平均工资
SELECT AVG(sal) FROM emp;
第二步:找到高于此平均工资的雇员姓名、职位、工资,直接将第一步的查询作为WHERE的限定条件
SELECT e.ename, e.job, e.sal FROM emp e WHERE e.sal>( SELECT AVG(sal) FROM emp);
第三步:找到领导姓名和领导职位,直接使用emp表作为自身关联使用
SELECT e.ename雇员姓名, e.job 雇员职位, e.sal 雇员工资, m.ename 领导姓名, m.job 领导职位 FROM emp e, emp m WHERE e.sal>( SELECT AVG(sal) FROM emp); AND e.mgr=m.empno(+);
第四步:加入dept表,找到部门名称,
SELECT e.ename雇员姓名, e.job 雇员职位, e.sal 雇员工资, m.ename 领导姓名, m.job 领导职位, d.dname 部门名称 FROM emp e, emp m, dept d WHERE e.sal>( SELECT AVG(sal) FROM emp); AND e.mgr=m.empno(+) AND e.dpetno = d.deptno;
第五步:统计部门人数,但是就当前给出的查询,现在不可能在SELECT子句里编写COUNT函数了(COUNT要么单独使用,要么结合GROUP BY使用,唯一可能出现在SELECT里的只有统计函数与分组字段)。所以,次数需要统计查询,但又无法使用统计函数,那就编写FROM子句中的子查询,然后再在FROM子句里查询部门人数:
SELECT deptno dnp, COUNT(empno) count FROM emp GROUP BY deptno;
将该查询结果作为上面FROM子句里的内容:
SELECT e.ename雇员姓名, e.job 雇员职位, e.sal 雇员工资, m.ename 领导姓名, m.job 领导职位, d.dname 部门名称, dtemp.count 部门人数 FROM emp e, emp m, dept d, ( SELECT deptno dno, COUNT(empno) count FROM emp GROUP BY deptno) dtemp WHERE e.sal>( SELECT AVG(sal) FROM emp); AND e.mgr=m.empno(+) AND e.dpetno = d.deptno AND dtemp.dno=d.deptno;
第六步:找到工资等级,直接加入salgrade即可
SELECT e.ename 雇员姓名, e.job 雇员职位, e.sal 雇员工资, m.ename 领导姓名, m.job 领导职位, d.dname 部门名称, dtemp.count 部门人数, s.grade 工资等级 FROM emp e, emp m, dept d, ( SELECT deptno dno, COUNT(empno) count FROM emp GROUP BY deptno) dtemp, salgrade s WHERE e.sal>( SELECT AVG(sal) FROM emp); AND e.mgr=m.empno(+) AND e.dpetno = d.deptno AND dtemp.dno=d.deptno AND e.sal BETWEEN s.losal AND s.hisal;
第七步:找出工资等级的雇员人数,依然需要编写一个子查询进行统计
SELECT s1.grade sg, COUNT(e1.empno) count FROM emp e1, salgrade s1 WHERE e1.sal BETWEEN s1.losal AND s1.hisal GROUP BY s1.grade;
这个是工资等级对应的人数,我们现在将工资等级和人数表与刚刚的结果吧表穿插到一起。
SELECT e.ename雇员姓名, e.job 雇员职位, e.sal 雇员工资, m.ename 领导姓名, m.job 领导职位, d.dname 部门名称, dtemp.count 部门人数, s.grade 工资等级, stemp.count 等级人数 FROM emp e, emp m, dept d, ( SELECT deptno dno, COUNT(empno) count FROM emp GROUP BY deptno) dtemp, salgrade s, ( SELECT s1.grade sg, COUNT(e1.empno) count FROM emp e1, salgrade s1 WHERE e1.sal BETWEEN s1.losal AND s1.hisal GROUP BY s1.grade) stemp WHERE e.sal>( SELECT AVG(sal) FROM emp); AND e.mgr=m.empno(+) AND e.dpetno = d.deptno AND dtemp.dno=d.deptno AND e.sal BETWEEN s.losal AND s.hisal AND s.grade=stemp.sg;
以上就是一个复杂查询,通过本程序可得到一个结论:复杂查询 = 限定查询 + 多表查询 + 统计查询。这部分的内容是最有可能出现在笔试中的题目,而且现在的表还是在你熟悉的情况下,如果是不熟悉的表,只能够需要根据表结构进行分析、掌握分析方法。
No. | 方法名称 | 类型 |
1 | 字符串 UPPER(字符串 | 数据列) | 将特定的字符串或者指定的列数据变为大写 |
2 | 字符串 LOWER(字符串 | 数据列) | 将特定的字符串或者指定的列数据变为小写 |
3 | 字符串 INITCAP(字符串 | 列) | 将特定字符串或者指定列的数据的首字母大写,其余字母小写 |
4 | 数字 LENGTH(字符串 | 列) | 计算出指定字符串或者数据列的数据长度 |
5 | 字符串 REPLACE(字符串 | 列,要替换的内容,新的内容) | 将指定字符串或者数据列中的数据按照指定的新内容 |
6 | 字符串 SUBSTR(字符串 | 数据列,截取开始索引) | 由指定位置截取到结尾 |
7 | 字符串 SUBSTR(字符串 | 数据列,截取开始索引,截取结束索引) | 指定截取的开始和结束位置 |
8 | 字符串 TRIM(字符串 | 列) | 去掉左右空格函数 |
9 | 数字 ROUND(数字 | 列 [,保留小数位]) | 四舍五入操作 |
10 | 数字 TRUNC(数字 | 列 [,保留小数位]) | 截取小数 |
11 | 数字 MOD(数字1 | 列1,数字2 | 列2) | 求模(余数) |
12 | 数字MONTHS_BETWEEN(日期1 | 列1,日期2 | 列2) | 计算两个日期之间所经历的月数 |
13 | ADD_MONTHS(日期 | 列,月数) | 在指定日期上增加若干月之后的日期 |
14 | 日期 NEXT_DAY(日期 | 列,一周时间数) | 求出指定的下一个一周时间数的日期 |
15 | 日期 LAST_DAY(日期 | 列) | 求出指定日期所在月的最后一天日期 |
16 | 字符串 TO_CHAR(数字 | 日期 | 列,转换格式) | 转字符串数据 |
17 | 日期 TO_DATE(字符串,转换格式) | 转日期数据 |
18 | 数字 TO_NUMER(字符串,转换格式) | 转数字数据 |
19 | 数字NVL(列 | 数据,默认值) | 处理NULL数据 |
20 | 数据DECODE(字段 | 数据,判断数据1,显示数据1,判断数据2,显示数据2,…[默认显示]) | 多数据判断、 |
SELECT [DISTINCT] * | 列名称 [别名], 列名称 [别名], … | 统计函数 FROM 数据表 [别名], 数据表 [别名], … [WHERE 条件(s)] [GROUP BY 分组字段, 分组字段,…] [HAVING 分组后过滤] [ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], …] | 5.确定数据列 |
1.数据源 | |
2.过滤数据行 | |
3.执行分组 | |
4.针对分组过滤 | |
6.数据排序 |
查询就以上的几个子句,使用熟练。