825 lines
26 KiB
Markdown
825 lines
26 KiB
Markdown
---
|
||
title: 数据库基础SQL查询
|
||
date: 2023-04-27 11:38:38.451
|
||
updated: 2023-04-27 11:38:38.451
|
||
url: /archives/query
|
||
categories:
|
||
- 数据库
|
||
tags:
|
||
- DB
|
||
---
|
||
|
||
> 数据查询语言DQL:Data query language
|
||
|
||
## 基础查询
|
||
|
||
> 注意:每个SQL语句后加上英文符号`;` 养成良好习惯,避免不必要问题
|
||
>
|
||
> 建议1:SQL例如SELECT、FROM、WHERE等关键字使用大写
|
||
>
|
||
> 建议2:所有的例如逗号、括号、引号等等符号全部使用英文符号,避免不必要问题
|
||
|
||
- 查询所有列
|
||
|
||
```sql
|
||
select * from tableName;
|
||
```
|
||
|
||
- 查询指定列
|
||
|
||
```sql
|
||
select columnName1,columnName2 from tableName;
|
||
```
|
||
|
||
- 列数值计算(在列名后直接跟上运算)
|
||
|
||
```sql
|
||
select columnName1*12,columnName2 from tableName;
|
||
```
|
||
|
||
- 列字符串拼接(多列拼接作为一列显示)
|
||
|
||
```sql
|
||
-- 注意:Oracle的字符串只能使用单引号
|
||
select columnName1||' '||columnName2 from tableName;
|
||
```
|
||
|
||
- 列别名
|
||
|
||
```sql
|
||
select columnName1 As alias1,columnName2 As alias2 from tableName;
|
||
```
|
||
|
||
```sql
|
||
-- As也可以省略不写
|
||
select columnName1 alias1,columnName2 alias2 from tableName;
|
||
```
|
||
|
||
- 去除重复记录
|
||
|
||
```sql
|
||
-- 在列名前使用关键字 DISTINCT 去除这列重复的记录
|
||
select DISTINCT columnName1 from tableName;
|
||
```
|
||
|
||
### 精准查询
|
||
|
||
> 条件查询使用where子句
|
||
|
||
- 基本语法
|
||
|
||
```sql
|
||
select columnName1,columnName2 from tableName where 条件表达式(可以是一个条件也可以是多个条件);
|
||
```
|
||
|
||
- 精准查询
|
||
|
||
```sql
|
||
-- 比较运算符
|
||
-- 注释:在SQL的一些版本中,<>可被写成!=
|
||
-- 当比较值为数值时不需要引号
|
||
-- 当比较值为字符串时必须使用单引号或者双引号
|
||
= > >= < <= != <> ^=
|
||
```
|
||
|
||
示例
|
||
|
||
```sql
|
||
-- 以工资salary大于一万作为条件执行查询
|
||
select salary from tableName where salary>10000;
|
||
```
|
||
|
||
- 范围查询 BETWEEN 下边界 AND 上边界
|
||
|
||
```sql
|
||
-- 以工资salary介于一万和两万之间作为条件执行查询
|
||
select salary from tableName where salary between 10000 and 20000;
|
||
```
|
||
|
||
- 根据多个值查询 IN(值1, 值2, ... 值N)
|
||
|
||
```sql
|
||
-- 以姓名username'张三','李四','王五'作为条件执行查询
|
||
select username from tableName where salary in('张三','李四','王五');
|
||
```
|
||
|
||
- 逻辑运算符
|
||
|
||
NOT 表示否定 AND 表示并且 OR 表示或者 优先级 NOT > AND > OR
|
||
|
||
逻辑运算符 not and or 优先级为 not>and>or
|
||
|
||
建议:复杂逻辑运算可以加括号避免干扰
|
||
|
||
```sql
|
||
-- 以工资等于一万或者两万作为条件执行查询
|
||
select username from tableName where salary=10000 or salary=20000;
|
||
```
|
||
|
||
- 空值比较
|
||
|
||
IS NULL 表示为空 IS NOT NULL 表示不为空
|
||
|
||
```sql
|
||
-- 以email为空作为条件执行查询
|
||
select email from tableName where email is null;
|
||
```
|
||
|
||
### 模糊查询
|
||
|
||
- like查询
|
||
|
||
两个简单的通配符:`%` 和 `_`
|
||
|
||
`%` 任意匹配,下划线 `_` 匹配一个任意字符
|
||
|
||
注意:Mysql数据库支持正则表达式,但是需要使用`REGEXP `关键字指定正则表达式的字符匹配模式
|
||
|
||
```sql
|
||
-- %a% a% %a 含义分别为包含字符a,以字符a开头,以字符a结尾,以第一个举例
|
||
select name from tableName where name like '%a%';
|
||
```
|
||
|
||
### 伪列
|
||
|
||
- 关键字 ROWNUM
|
||
|
||
**注意:**并非所有的数据库系统都支持 SELECT TOP 语句。
|
||
|
||
Oracle 可以使用 ROWNUM 来选取
|
||
|
||
MySQL 可以使用 LIMIT 语句来选取指定的条数数据,
|
||
|
||
```sql
|
||
-- Oracle语法
|
||
-- 注意运算符只支持 < <=,不支持 > >= =,其中 =1 是例外支持
|
||
select columnName from tableName where rownum <= 10 ; -- 选取前10条数据
|
||
```
|
||
|
||
```sql
|
||
-- Mysql语法
|
||
select columnName from tableName LIMIT 10; -- 选取前10条数据
|
||
```
|
||
|
||
|
||
### 排序查询
|
||
|
||
排序中的空值需要扩展一下:
|
||
|
||
不同的数据库中的null在排序中的默认值是不同的
|
||
|
||
- 在`PostgreSQL`中,null值默认最大
|
||
- 在`Oracle`中,null值默认最大
|
||
- 在`Mysql`和`SQLServer`中,null值默认最小
|
||
|
||
因此建议使用 **nulls last** 和 **nulls first** 来指定这些null值排在前面还是后面,另外该关键字只能搭配order by来使用。
|
||
|
||
`ORDER BY` 子句
|
||
|
||
子句书写顺序 `SELECT -> FROM -> WHERE -> ORDER BY`
|
||
|
||
子句执行顺序 `FROM -> WHERE -> SELECT -> ORDER BY`
|
||
|
||
```sql
|
||
-- 默认就是升序排列
|
||
select salary from tableName where salary > 2000 order by;
|
||
|
||
-- 多排列,先按name升序,再按salary降序
|
||
select name,salary from tableName order by name asc, salary desc;
|
||
```
|
||
|
||
```sql
|
||
-- 空值排在最后
|
||
select email from tableName order by email desc nulls last;
|
||
```
|
||
|
||
### 分组查询
|
||
|
||
#### 分组函数
|
||
|
||
- 分组函数又称为统计函数,用于将查询结果分成几个组,出现与 SELECT 的列和 HAVING 子句中
|
||
- `COUNT` 统计查询出的结果数量
|
||
- `AVG` 求查询结果中某列的平均值
|
||
- `MAX` 求查询结果中某列的最大值
|
||
- `MIN` 求查询结果中某列的最小值
|
||
- `SUM` 求查询结果中某列的总和
|
||
|
||
>分组函数 AVG、SUM 只能统计数值,MAX、MIN 可以统计字符串
|
||
>
|
||
>分组函数 COUNT 统计所有类型,不统计空值
|
||
>
|
||
>COUNT(*) 和 COUNT(1) 统计所有列,返回行数
|
||
>
|
||
>COUNT(字段名) 统计某字段
|
||
|
||
```sql
|
||
-- 统计表中user_id列不为空且不重复的数量
|
||
select count(distinct user_id) FROM tableName;
|
||
```
|
||
|
||
需要注意的是分组函数不能跟普通的列混合,例如下面这个错误例子:
|
||
|
||
```sql
|
||
select count(distinct user_id),name FROM tableName;
|
||
```
|
||
|
||
#### 分组查询
|
||
|
||
- `GROUP BY` 子句
|
||
|
||
- 子句书写顺序 `SELECT -> FROM -> WHERE -> GROUP BY -> ORDER BY`
|
||
|
||
- 子句执行顺序 `FROM -> WHERE -> GROUP BY -> SELECT -> ORDER BY`
|
||
|
||
- GROUP BY 子句将查询结果细分为更小的组,GROUP BY 子句后只能跟字段名
|
||
|
||
- 没有 GROUP BY 子句时 SELECT 子句可以出现分组函数,但不能同时出现其它字段
|
||
|
||
- 有 GROUP BY 子句时 SELECT 子句只允许出现分组函数和分组字段
|
||
|
||
- 在 GROUP BY 子句中若出现多列时是按照多列组合值进行分组
|
||
```sql
|
||
-- 分组查询 关键字 group by + 分组的列
|
||
-- 执行顺序 from->where->group by->_func->select->order by
|
||
-- 分部门查询每个dept_id的最高salary和平均salary
|
||
select dept_id,max(salary),avg(salary)
|
||
from s_emp
|
||
group by dept_id;
|
||
|
||
-- 分部门查询每个dept_id每个title的最高工资、部门id、岗位
|
||
select max(salary) as 最高工资,dept_id as 部门id,title as 岗位
|
||
from s_emp
|
||
group by dept_id,title;-- 分组可以跟多列 先按第一列分组再按第二列分组
|
||
|
||
```
|
||
|
||
#### 分组条件过滤
|
||
|
||
- `having` 子句
|
||
- 子句书写顺序 `SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY`
|
||
- 子句执行顺序 `FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY`
|
||
- HAVING 子句用来对 GROUP BY 的结果集进行条件过滤,只允许出现分组函数和分组字段
|
||
- WHERE 子句中不允许出现分组函数,HAVING 子句不允许使用列的别名
|
||
|
||
```sql
|
||
-- 分组过滤 having 子句用于对group by 子句的过滤
|
||
-- 书写顺序 select->from->where->group by->order by
|
||
-- 执行顺序 from->where->group by->分组函数->having->select->order by
|
||
-- 从员工表中查询出平均工资大于1500的部门编号、平均工资、最高工资,并且按照部门编号升序排列
|
||
-- select dept_id,avg(salary),max(salary) from s_emp where avg(salary)>1500
|
||
group by dept_id; --报错 where后不能使用分组函数(执行顺序)
|
||
select dept_id,avg(salary),max(salary)
|
||
from s_emp
|
||
group by dept_id
|
||
having avg(salary)>1500
|
||
order by dept_id asc;
|
||
-- having 子句中跟分组字段(dept_id) 则与分组字段在where子句中作用是一样的
|
||
-- 从员工表中查询出部门编号大于20的部门编号、平均工资、最高工资
|
||
-- 先分组再按照分组字段对分组结果过滤
|
||
select dept_id,avg(salary),max(salary)
|
||
from s_emp
|
||
group by dept_id
|
||
having dept_id>20;
|
||
-- 先按照分组字段进行过滤 再在过滤结果上进行分组
|
||
select dept_id,avg(salary),max(salary)
|
||
from s_emp
|
||
where dept_id>20
|
||
group by dept_id;
|
||
```
|
||
|
||
### 集合操作
|
||
|
||

|
||
|
||
#### 并集
|
||
|
||
- `UNION` 返回几个查询结果的全部内容,但不显示重复记录
|
||
|
||
- `UNION All` 返回几个查询结果的全部内容,重复记录也会显示
|
||
|
||
```
|
||
-- 并集 union 返回几个查询结果的全部内容 但不显示重复记录 union all 显示重复记录
|
||
select id,first_name from s_emp where id<8;--1 2 3 4 5 6 7
|
||
select id,first_name from s_emp where id between 6 and 10;--6 7 8 9 10
|
||
-- 1 2 3 4 5 6 7 8 9 10
|
||
select id,first_name from s_emp where id<8
|
||
union
|
||
select id,first_name from s_emp where id between 6 and 10;
|
||
-- 1 2 3 4 5 6 7 6 7 8 9 10
|
||
select id,first_name from s_emp where id<8
|
||
union all
|
||
select id,first_name from s_emp where id between 6 and 10;
|
||
```
|
||
|
||
|
||
#### 差集
|
||
|
||
- `MINUS` 返回几个查询结果的不同部分
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--差集minus 返回几个查询结果的不同部分 从第一个集合中去掉第二个集合中的部分
|
||
--1 2 3 4 5
|
||
select id,first_name from s_emp where id<8
|
||
minus
|
||
select id,first_name from s_emp where id between 6 and 10;
|
||
```
|
||
|
||
#### 交集
|
||
|
||
- `INTERSECT` 返回几个查询结果的相同部分
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 交集 intersect 取两个集合一样的行
|
||
-- 6 7
|
||
select id,first_name from s_emp where id<8
|
||
intersect
|
||
select id,first_name from s_emp where id between 6 and 10;
|
||
```
|
||
|
||
|
||
|
||
### 单行函数
|
||
|
||
#### 字符函数
|
||
|
||
Oracle 的 SELECT SQL 语法要求一定要有数据源,不需要数据源可以使用虚拟表 dual
|
||
|
||
- `UPPER` 转换成大写
|
||
|
||
- `LOWER` 转换成小写
|
||
|
||
- `INITCAP` 首字母大写
|
||
|
||
- `CONCAT(字符串1, 字符串2)` 字符串的连接
|
||
|
||
- `SUBSTR(字符串, 下标, 截取长度)` 字符串的截取,下标从1开始
|
||
|
||
- `LENGTH` 获取字符串的长度
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--虚拟表 dual
|
||
select 1+2 from dual;
|
||
|
||
--单行函数
|
||
--字符串函数
|
||
-- 将所有的职位转换成大写输出
|
||
select distinct upper(title) from s_emp; --转大写
|
||
select distinct lower(title) from s_emp; --转小写
|
||
select initcap('mark') from dual;--首字母转大写
|
||
select substr('51testing',1, 6) from dual;--字符串截取substr(字符串, 起始位置, 长度) 字符串下标从1开始
|
||
select substr('51testing',0,4),substr('51testing',-3, 5),substr('51testing', -3) from dual; -- 末尾的下标 -1
|
||
select concat('hello','oracle') from dual; --字符串拼接
|
||
select length('51testing') from dual; --字符串长度
|
||
--忽略大小写
|
||
--HenRy
|
||
select * from s_emp where upper(first_name)='HENRY';
|
||
select * from s_emp where lower(first_name)='henry';
|
||
```
|
||
|
||
#### 通用函数
|
||
|
||
- `NVL(列名,'字符串')` 将列中的空值替换成字符串
|
||
|
||
- `NVL(列名, 数字)` 将列中的空值替换成数值
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--通用函数 NVL(列名,'字符串')NVL(列名,数值) 列名中值为空的使用后面的数据补齐
|
||
select salary, salary+salary*commission_pct/100 from s_emp;
|
||
select salary, salary+salary*nvl(commission_pct,0)/100 from s_emp;
|
||
```
|
||
|
||
|
||
#### 数值函数
|
||
|
||
- `ROUND` 四舍五入
|
||
|
||
- `TRUNC` 截取,不进行四舍五入
|
||
|
||
- `MOD` 取余
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--round(字段,数值)四舍五入 数值可以不传,表示取整 为正,表示小数点后保留几位 为负,表示小数点前向前取整取几位
|
||
select round(1234.567),round(1234.567,1),round(1234.567,-2) from dual; --1235 1234.6 1200
|
||
|
||
--trunc(字段,数值) 截位
|
||
select trunc(1234.567), trunc(1234.567,1),trunc(1234.567,-2) from dual; --1234 1234.5 1200
|
||
|
||
--mod(数值1,数值2) 取余
|
||
select mod(5,2) from dual; --1
|
||
```
|
||
|
||
#### 转换函数
|
||
|
||
- `TO_NUMBER(字符串)` 将一个数值字符串转换成数值
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 转换函数
|
||
-- to_number(字符串)
|
||
select to_number('1234.567') from dual;
|
||
```
|
||
|
||
- `TO_CHAR(数值,'格式')` 数值转换成字符串
|
||
|
||
> **格式**
|
||
> 0:显示数字,如位数不足,则用0补齐。9:显示数字,并忽略前面0。逗号是千分号。L表示本地货币符号,$表示美元符号。
|
||
> 格式最前面加FM可以删除9占位所产生的多余空格或0
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--to_char(数值,'格式') --占位符 0 显示数字如果位数不足使用0补齐 9表示占位 如果位数不足,用0补齐,并且忽略小数点前面的0
|
||
select to_char(1234567.5678,'99,999,999.999'),to_char(1234567.5678,'00,000,000.000') from dual; -- 逗号表示千分位
|
||
--FM 可以删除 9 占位 小数点后的占位 对格式0占位无效的
|
||
--L表示本地货币 $表示美元
|
||
select to_char(1234567.5678, 'FML99,999,999.999999999'),to_char(1234567.5678, 'FML00,000,000.000000000') from dual;
|
||
```
|
||
|
||
- `TO_CHAR(日期,'格式')` 日期转换成字符串
|
||
|
||
> **日期格式模型元素**
|
||
> YYYY 四位数字的年 YY 两位数字的年
|
||
> MM 两位数字的月 MON 月份的缩写 MONTH 月份的全称
|
||
> DD 一个月的第几天 DDD 一年的第几天
|
||
> DAY 星期几的全称 DY 星期几的缩写 D 星期几的数字表示(星期天为1,星期六为7)
|
||
> HH 12小时制 HH24 24小时制 MI 分钟 SS 秒 AM 上午 PM 下午
|
||
> **日期的文字方式显示结果和日期语言相关**
|
||
> ALTER SESSION SET NLS_DATE_LANGUAGE="语言";
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- to_char(日期,'格式')
|
||
select start_date from s_emp;
|
||
alter session set nls_date_language='simplified chinese';--- 设置日期显示的语言格式
|
||
alter session set nls_date_language='english';--- 设置日期显示的语言格式
|
||
|
||
--YYYY四位数字年 MM两位数字月DD表示一个月第几天 HH时MI分SS秒 HH24 24小时制
|
||
select to_char(start_date, 'YYYY/MM/DD HH24:MI:SS') from s_emp;
|
||
--MON 表示月份的简写 DD 表示两位天 YY 表示两位年
|
||
select to_char(sysdate, 'mon-dd-yy') from dual;
|
||
--ddd 表示是一年的第几天 day 表示星期几 dy 星期几的简写 d 表示一周的第几天 会将星期日作为第一天
|
||
select to_char(sysdate, 'yyyy.ddd'),--ddd 表示是一年的第几天
|
||
to_char(sysdate,'day'),--day 表示星期几
|
||
to_char(sysdate, 'dy'),--dy 星期几的简写
|
||
to_char(sysdate,'d'),--d 表示一周的第几天 会将星期日作为第一天
|
||
to_char(sysdate,'yyyy/mm/dd hh:mi:ss pm'), --pm 展示上午下午
|
||
to_char(sysdate, 'mon-dd-yy'), -- MON 表示月份的简写 DD 表示两位天 YY 表示两位年
|
||
to_char(sysdate, 'month') --月份全称
|
||
from dual;
|
||
```
|
||
|
||
- `TO_DATE(字符串,'格式')` 将一个日期字符串转换成日期,日期可以进行加减,不能进行乘除
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
select to_date('2023/3/17','yyyy/mm/dd')-30 from dual;--返回计算后的日期
|
||
```
|
||
|
||
==**练习**==
|
||
|
||
```plsql
|
||
-- 从订单表s_ord中查询出编号sales_rep_id为11销售代表的每个订单金额total.
|
||
-- 金额以美元显示,整数7位,小数两位,小数不足的位数以0填充,有千分位
|
||
SELECT total, TO_CHAR(total, '$9,999,999.00'), TO_CHAR(total, 'FM$9,999,999.00')
|
||
FROM s_ord
|
||
WHERE sales_rep_id = 11;
|
||
|
||
-- 从员工表s_emp中,查询出1991年入职的员工姓名和入职日期,输出日期格式为数字形式的年/月,其中年为4位数字。
|
||
SELECT first_name||' '||last_name 姓名, TO_CHAR(start_date, 'YYYY/MM') 入职日期
|
||
FROM s_emp
|
||
WHERE TO_CHAR(start_date, 'YYYY') = '1991';
|
||
```
|
||
|
||
|
||
|
||
### 多表查询
|
||
|
||
#### 数据库范式
|
||
|
||
- 第一范式:要求表中无重复的列
|
||
|
||
- 第二范式:需满足第一范式,要求表中无重复的行
|
||
|
||
- 第三范式:需满足第二范式,要求一个数据库表中不包含已在其它表中已包含的非主关键字信息
|
||
|
||
`Primary Key` 主键 用来唯一标识表中的每一行数据
|
||
|
||
`Foreign Key` 外键 表与表之间的关联关系
|
||
|
||
|
||
#### 等值连接
|
||
|
||
- 判断某两列是否相等,从而建立连接,通常是主键与外键进行连接
|
||
|
||
- 采用表名加上列名,来标示列名,从而防止出现列名相同的情况
|
||
|
||
- 表可以起别名,表的别名前不能加AS关键字。
|
||
|
||
- 一旦给表起了别名,标识列时只能通过表的别名标识,而不能通过原名标识。
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 多表查询
|
||
--等值连接
|
||
-- 查询所有员工的名字、职位和部门名称
|
||
-- 找到数据源 s_emp s_dept
|
||
-- 找到关联关系 s_emp dept_id 和s_dept id s_emp dept_id = s_dept id
|
||
-- 找到我们要查询的列
|
||
|
||
-- 可以给表起别名 通过表名.的方式使用 如果表有别名 就不再使用原先表名
|
||
-- 列名有冲突的时候使用表名.列名的方式区分 表名 的别名不使用 as
|
||
select * from s_emp;
|
||
select * from s_dept;
|
||
select * from s_emp,s_dept;--笛卡儿积 集合A 1 2 3 集合B a b c <1,a><1,b><1,c><2,a><2,b><2,c><3,a><3,b><3,c>
|
||
select * from s_emp,s_dept where s_emp.dept_id=s_dept.id; -- 关联关系
|
||
select first_name, title, name from s_emp,s_dept where s_emp.dept_id=s_dept.id;--找到查询的列
|
||
|
||
-- 查询部门是41的员工的名字、职位和部门名称
|
||
-- 找到数据源 s_emp s_dept
|
||
-- 对应关系 s_emp.dept_id=s_dept.id
|
||
-- 找列
|
||
select a.id,a.first_name,a.title,b.name,b.id
|
||
from s_emp a,s_dept b
|
||
where a.dept_id=b.id and a.dept_id=41;
|
||
|
||
```
|
||
|
||
#### 内连接
|
||
|
||
- `INNER JOIN` 内连接
|
||
|
||
用INNER JOIN连接表,关联条件写在ON后面,INNER可省略
|
||
|
||
从多个表中返回满足关联条件的所有行
|
||
|
||

|
||
|
||
**示例**
|
||
```plsql
|
||
--内连接 表1inner join 表2 on 条件 其中inner可以省略
|
||
-- 查询部门为41的所有员工的名字、职位、部门名称
|
||
select first_name,title,name
|
||
from s_emp a
|
||
join s_dept b
|
||
on a.dept_id=b.id
|
||
where a.dept_id=41;
|
||
-- 查询部门为41的所有员工名字、部门名称、地区名称
|
||
-- 数据源 s_emp s_dept s_region
|
||
-- 关联关系 s_emp.dept_id s_dept.id s_dept.region_id s_region.id
|
||
--
|
||
select a.first_name 员工名字,b.name 部门名称, c.name 地区名称
|
||
from s_emp a
|
||
join s_dept b
|
||
on a.dept_id=b.id
|
||
join s_region c
|
||
on b.region_id=c.id
|
||
where a.dept_id=41;
|
||
--自连接
|
||
--查询部门41的员工的名字、职位和主管名字
|
||
-- 数据源 s_emp a s_emp b
|
||
-- a.manager_id=b.id
|
||
|
||
select a.first_name, a.title, b.first_name
|
||
from s_emp a
|
||
join s_emp b
|
||
on a.manager_id=b.id
|
||
where a.dept_id=41;
|
||
```
|
||
|
||
#### 左外连接
|
||
|
||
- `LEFT OUTER JOIN` 左外连接
|
||
|
||
从左表返回所有的行,即使右表中没有匹配。如果右表中没有匹配,则结果为 NULL。OUTER可省略。
|
||
|
||

|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--左外连接 left outer join on outer可以省略
|
||
-- 以左表为准,左表中所有的数据都会被展示,如果在右表中没有匹配的数据,也展示,右表数据展示为null
|
||
--查询员工的姓名、职位和客户姓名
|
||
--数据源 s_emp s_customer
|
||
--关系 s_emp.id s_customer.sales_rep_id
|
||
|
||
select a.first_name,a.title,b.name
|
||
from s_emp a, s_customer b
|
||
where a.id=b.sales_rep_id;
|
||
|
||
select a.first_name,a.title,b.name
|
||
from s_emp a
|
||
left outer join s_customer b
|
||
on a.id=b.sales_rep_id;
|
||
```
|
||
|
||
#### 右外连接
|
||
|
||
- `RIGHT OUTER JOIN` 右外连接
|
||
|
||
从右表返回所有的行,即使左表中没有匹配。如果左表中没有匹配,则结果为 NULL。OUTER可省略。
|
||
|
||

|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 右外连接 以右表为准返回所有行 即使左表中查询不到相关数据 仍然显示但是有表数据显示为Null
|
||
--right outer join on outer可以省略
|
||
select a.first_name,a.title,b.name
|
||
from s_emp a
|
||
right outer join s_customer b
|
||
on a.id=b.sales_rep_id;
|
||
```
|
||
|
||
#### 全外连接
|
||
|
||
- `FULL OUTER JOIN` 全外连接
|
||
|
||
只要左表和右表其中一个表中存在匹配,则返回行。OUTER可省略。
|
||
|
||

|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--全外连接
|
||
-- full outer join on 其中 outer 可以省略
|
||
-- 只要左表和右表中其中有一个存在匹配 返回该行
|
||
select a.first_name,a.title,b.name
|
||
from s_emp a
|
||
full join s_customer b
|
||
on a.id=b.sales_rep_id;
|
||
```
|
||
|
||
### 子查询(嵌套查询)
|
||
|
||
- 子查询是一个完整的 SELECT 语句,可以拥有 GROUP BY、HAVING 子句,可以使用组函数,可以从多个表查询结果
|
||
|
||
- 子查询一般不包含 ORDER BY 子句,除非需要进行排名前几位的查询(Top-N)
|
||
|
||
- 如果子查询的外围语句是 SELECT 语句,则子查询内容必须用小括号界定,子查询里面不用分号结尾
|
||
|
||
- 子查询的作用:
|
||
|
||
1. 方便理解
|
||
|
||
2. 实现更复杂的查询
|
||
|
||
3. 提高查询效率
|
||
|
||
- 当直接查询某些数据很困难或办不到时,可以通过从“查询结果集”中再次提取数据集来实现复合查询。
|
||
|
||
- 子查询的位置通常出现在 WHERE 子句、HAVING 子句、FROM 子句
|
||
|
||
|
||
#### WHERE 子句单行子查询
|
||
|
||
- 子查询返回结果是单行单列,使用单行比较符 `= != > >= < <=`
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- where 子查询
|
||
-- 单行子查询(单行单列)
|
||
-- 查询最低工资的员工姓名、职位、工资
|
||
--步骤1:找到最低工资
|
||
select min(salary) from s_emp; -- 750
|
||
--步骤2:找到对应的员工姓名、职位、工资
|
||
select first_name,title,salary from s_emp where salary=750;
|
||
-- 步骤3:做子查询替换
|
||
select first_name,title,salary from s_emp where salary=(select min(salary) from s_emp);
|
||
```
|
||
|
||
#### WHERE 子句多行子查询
|
||
|
||
- 子查询返回多行单列,使用多行比较符 `IN ANY ALL`
|
||
|
||
>IN 等于列表中的任意一个
|
||
>ALL 和子查询返回的所有值比较(大于最大的,小于最小的)
|
||
>ANY 和子查询返回的任意一个值比较(大于最小的,小于最大的)
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 多行子查询(多行单列) in all any
|
||
-- 查询部门名称是Sales的部门所有员工姓名、职位
|
||
--1.找到name是Sales的部门 的部门编号
|
||
select id from s_dept where name='Sales';-- [31,32,33,34,35]
|
||
--2.查找部门编号dept_id是 [31,32,33,34,35] 的姓名和职位
|
||
select first_name,title from s_emp where dept_id in (31,32,33,34,35);
|
||
--3.做子查询语句替换
|
||
select first_name,title from s_emp where dept_id in (select id from s_dept where name='Sales');
|
||
-- all 跟子查询中所有的值作比较 >all 大于最大值 <all小于最小值
|
||
-- 查询部门编号比Sales部门都小的部门的所有员工的姓名和职位
|
||
select id from s_dept where name='Sales';--[31,32,33,34,35]
|
||
select first_name,title from s_emp where dept_id<all(select id from s_dept where name='Sales');
|
||
-- any 跟子查询中所有的值作比较 >any 大于最小值 <any 小于最大值
|
||
-- 查询部门编号比Sales部门中其中一个大的部门的所有员工姓名和职位
|
||
select first_name,title from s_emp where dept_id>any(select id from s_dept where name='Sales');
|
||
```
|
||
|
||
#### HAVING 子句子查询
|
||
|
||
- 子查询返回结果是单行单列或多行单列,也可以使用在 HAVING 子句
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
--Having 子查询
|
||
-- 查询部门平均工资比公司平均工资高的部门编号
|
||
-- 1.查询公司的平均工资
|
||
select avg(salary) from s_emp; --1255.08
|
||
-- 2.查询部门的平均工资
|
||
select dept_id, avg(salary)
|
||
from s_emp
|
||
group by dept_id
|
||
having avg(salary)>1255.08;
|
||
-- 3.替换
|
||
select dept_id, avg(salary)
|
||
from s_emp
|
||
group by dept_id
|
||
having avg(salary)>(select avg(salary) from s_emp);
|
||
```
|
||
|
||
#### FROM 子句子查询
|
||
|
||
- 子查询返回结果是多行多列,可以使用在 FROM 子句作为数据源
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- from 子查询(数据源)
|
||
-- 子查询的结果是多行多列
|
||
-- 查询工资从高到低排名前5的员工名字和职位
|
||
select first_name,title
|
||
from s_emp
|
||
where rownum<6 order by salary desc; -- 执行顺序 from->where->select->order by
|
||
-- 1.找到员工的姓名和职位 工资从高到低
|
||
select first_name,title from s_emp order by salary desc; --子查询的结果 是一个两列(first_name,title)的临时表
|
||
-- 2.截取前5个
|
||
select * -- 可以用*获取数据源所有列即(first_name,title)
|
||
from (select first_name,title from s_emp order by salary desc) -- 子查询的结果作为数据源
|
||
where rownum<6;
|
||
```
|
||
|
||
#### 子查询和多表查询结合
|
||
|
||
- 多行多列的子查询结果可以作为多表查询的数据源
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 多表查询和子查询相结合
|
||
-- 多行多列的子查询结果可以作为多表查询的数据源
|
||
-- 子查询产生的表如果列有分组函数 必须加别名
|
||
-- 查询出每个部门名称、最高工资和人数
|
||
--1.数据源 s_dept a s_emp b
|
||
-- 部门的编号,最高工资 和人数
|
||
select dept_id,max(salary),count(*) from s_emp group by dept_id;
|
||
--2.关系 a.id b.dept_id
|
||
select a.name 部门名称, c.maxsal 最高工资, c.count 人数
|
||
from s_dept a
|
||
left join (select dept_id deptid,max(salary) maxsal,count(*) count from s_emp group by dept_id) c
|
||
on a.id=c.deptid;
|
||
```
|
||
|
||
#### 子查询嵌套
|
||
|
||
- 子查询可以多层嵌套
|
||
|
||
**示例**
|
||
|
||
```plsql
|
||
-- 子查询嵌套
|
||
--查询工资比Henry所在部门平均工资高的所有员工的名字、职位、工资
|
||
--1 求henry所在部门id
|
||
select dept_id from s_emp where first_name='Henry';
|
||
--2 部门的平均工资
|
||
select avg(salary) from s_emp where dept_id=(select dept_id from s_emp where first_name='Henry');
|
||
--3 以2为条件 过滤 取字段
|
||
select first_name,title,salary
|
||
from s_emp
|
||
where salary>(select avg(salary) from s_emp where dept_id=(select dept_id from s_emp where first_name='Henry'));
|
||
```
|
||
|
||
==**练习**==
|
||
|
||
```plsql
|
||
-- 从订单表s_ord中查询出比销售代表编号sales_rep_id为11的所有订单金额total都低的订单信息
|
||
SELECT * FROM s_ord WHERE total < (SELECT MIN(total) FROM s_ord WHERE sales_rep_id=11);
|
||
``` |