三个概念

  • DB: 数据库

  • DBMS: 数据库管理系统(例如MySQL,SQL Server)

  • SQL: 语法

分类有两种

  • 关系型数据库

  • 非关系型数据库(NoSQL)

ORM(Object Relational Mapping) 思想

- 数据库中的一个表 <---> Java或Python中的一个类

- 表中的一条数据 <---> 类中的一个对象(或实例)

- 表中的一个列 <----> 类中的一个字段、属性(field)

表和表之间的关系

- 一对一

    - 应用: 在实际的开发中应用不多,因为一对一可以创建成一张表

        - 举例:设计学生表,学号、姓名、手机号码、班级、系别、身份证号码、家庭住址、籍贯、紧急联系人...

        - 为什么要这么设计: 即一张表能搞定事情,设计成两张表?

            - 查询一次的时候,需要查询整行的记录,而往往只需要查询几个字段而已,所以性能的开销大(但是查询操作简单)

            - 拆为两个表:两个表的记录是一一对应关系。
            - 基础信息表(常用信息):学号、姓名、手机号码、班级、系别
            - 档案信息表(不常用信息):学号、身份证号码、家庭住址、籍贯、紧急联系人、...

- 一对多(多对一)

    - 举例: 员工表 和 部门表

- 多对多

    - 举例: 学生表 和 课程表

    - 必须创建第三张表(联接表)

    - 它将"多对多"关系划分为"两个 一对多关系"

    - 把两个表的主键都插入到'联接表'

- 自我引用

    - 举例: 员工表里面,有 员工编号 和 主管编号(引用'员工编号')

MySQL 安装问题

  • 故障代码

ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded:

- 解决办法一: 不使用cmd命令行,而是使用mysql自带的命令行,就不会报错了

- 解决办法二: 把MySQL8用户登录密码加密规则还原成mysql_native_password

#使用mysql数据库
USE mysql;
#修改'root'@'localhost'用户的密码规则和密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'abc123';
#刷新权限
FLUSH PRIVILEGES;

进入MySQL命令(参数之间的位置可以自由调整,不是固定死的)

mysql -h 主机名 -P 端口号 -u 用户名 -p密码

-- 例如
mysql -h localhost -P 3306 -u root -pabc123 # 这里我设置的root用户的密码是abc123

常用的查询命令

-- show create table table_name; # 查看'表'是如何创建

-- show create database database_name; # 查看'数据库'是如何创建

-- show variables like 'character_%'; # 查看编码集

导入数据库命令

  • 方式一,命令行进入mysql,敲以下命令
mysql> source d:\mysqldb.sql
  • 方式二,使用图形化工具,执行sql脚本

select语句之'dual(伪表)'

  • dual 的意义在于'测试'
# 把'from dual'删去,效果是一样的
SELECT 1+1,2+2 FROM DUAL

字段取别名,可以有三种方式

  • 规范使用 双引号 把别名包裹起来(使用单引号是不规范的)

  • 而在处理日期字段的时候,规范使用 单引号 (使用双引号是不规范的)

SELECT last_name 姓名 FROM employees; # field define_name

SELECT last_name '姓名' FROM employees; # field 'define_name'

SELECT last_name  AS '姓名' FROM employees; # field as ...

DISTINCT: 去重

  • 最好只搭配一个字段,多个字段的结果一般没有意义

# 正确用法:查询所有的部门ID
SELECT DISTINCT department_id FROM employees;

# 错误用法: 搭配两个字段,没有意义(这里salary若有重复的,会被删除,也只保留一个salary)
select distinct department_id,salary from employees;

null参与运算

  • 若null参与运算,返回的结果还是'null'

  • 一般搭配 ifnull()函数使用

# 计算年薪示例                               若 commission_pct为null,则替换为'0'
SELECT last_name 姓名,salary 月薪,salary*(1+IFNULL(commission_pct,0))*12 年薪  FROM employees;

着重号``

  • 用来包裹'sql关键字',避免歧义
# order 本身就是mysql的关键字,不加着重号包裹起来,就会报错!
SELECT * FROM `order`;

命令,描述表的结构信息

desc table_name

SQL 运算注意事项

- '+'号只有一个作用,就是运算

SELECT 100 + '1' FROM DUAL # 结果为 101,sql会作'隐式转换'

SELECT 100 + 'a' FROM DUAL # 结果为 100,'a'被转换为 0

SELECT 100 + NULL FROM DUAL # 结果为 null,null 参与运算,一定为 null

比较运算符

  • 只返回三种结果

    • 0,条件不成立

    • 1,条件成立

    • null,有null参与

SELECT 0='a'; # 结果为 1,'a'被隐式转换成0

SELECT 0=NULL; # 结果为 null,有null参与,立即返回null
# 结果: 一行数据都没有,只有三个字段名
SELECT last_name,salary,commission_pct FROM employees WHERE commission_pct=NULL;
'''
- 解析: where条件"commission_pct=NULL" 就相当于'比较运算符',若比较的结果为1,那么就把该条记录展示出来,其他结果不予展示

- 显然,每个字段和 null 进行比较,根本不会返回1,所以,一行数据都不会显示
'''
  • 安全等于"<=>": 为 null 而生
- 按照以前的惯例,只要null参与运算,那么结果一定为null

- 使用 "<=>" 以后,null参与运算,就不一定返回 null,也可以返回'1',例如 null<=>null 结果为 1

### 上述示例把'='换成'<=>',就有结果了
SELECT last_name,salary,commission_pct FROM employees WHERE commission_pct<=>NULL;

### 还可以有以下两种写法:
SELECT last_name,salary,commission_pct FROM employees WHERE commission_pct IS NULL; # 推荐
                                             # 和 IFNULL(commission_pct,0) 区别开来
SELECT last_name,salary,commission_pct FROM employees WHERE ISNULL(commission_pct);

least/greatest: 最小值和最大值

SELECT LEAST('a','b','c') AS 最小值,GREATEST('a','b','c') AS 最大值

最小值     最大值
    a       c


### max和min有类似的用法
SELECT max(salary) AS 最大值,min(salary) AS 最小值 from employees;

between...and...:查找一定范围内的条件

  • 包含左右边界(边界的顺序不可随便调换,会报错)
# 查询工资在6000~8000的员工信息
SELECT last_name,salary FROM employees WHERE salary BETWEEN 6000 AND 8000

in/not in...: 查找满足某个点的条件

# 查找 员工编号为 10/20/30的员工信息
SELECT last_name,department_id FROM employees WHERE department_id IN (10,20,30)

like: 模糊匹配

  • '%': 任意多个字符(0个,1个,多个)

  • '_': 任意单个字符

  • '': 转义(也可以使用escape关键字来指明'转义字符',比如换成'$')

# 名字包含'a'的员工信息
SELECT last_name FROM employees WHERE last_name LIKE '%a%';

# 第二个字符是'a'的员工信息
SELECT last_name FROM employees WHERE last_name LIKE '_a%';

# 第二个字符是'_'的员工信息(注意是反斜杠'\')
SELECT last_name FROM employees WHERE last_name LIKE '_\_%';

# 替换默认的转义字符'\'为'$'(一般不这么搞)
SELECT last_name FROM employees WHERE last_name LIKE '_$_%' ESCAPE '$';

regexp \ rlike: 正则表达式(先简单了解一下)

#             1                          1                               0
SELECT 'shkdfs' REGEXP '^s','sdfsjdsft' REGEXP 't$','shksdfsdf' REGEXP 'hx' FROM DUAL;

"XOR":表示'异或'

  • 比如有两个条件,那么满足条件1以后,就不能满足条件2(一个为真以后,另外一个必定为假)
# 如果 department_id=50,那么该条记录的 salary 一定小于/等于 6000
# 如果 salary>6000,那么该条记录的 department_id 一定不等于50
SELECT last_name,department_id,salary FROM employees WHERE department_id=50 XOR salary>6000

练习示例

### 查询名字中包含'a'和'k'的员工信息
# 或者这么写也可以: WHERE last_name LIKE '%a%' AND last_name LIKE '%k%'
SELECT last_name FROM employees WHERE last_name LIKE '%a%k%' OR last_name LIKE '%k%a%'


### 查询名字中以'e'结尾的员工信息
SELECT last_name FROM employees WHERE last_name LIKE '%e'
SELECT last_name FROM employees WHERE last_name REGEXP 'e$'

排序: order by xxx desc/asc

  • desc: descend

  • asc: ascend(默认行为)

SELECT department_id,last_name,salary FROM employees ORDER BY salary DESC;
  • 取'别名'注意事项,只能在order by 使用,where 中不能用,会报错
# 正确示例
SELECT department_id,last_name,salary AS 工资 FROM employees ORDER BY 工资 DESC;

# 错误示例: Unknown column '工资' in 'where clause'
SELECT department_id,last_name,salary AS 工资 FROM employees WHERE 工资>6000 ORDER BY 工资 DESC;

'''
- 出错的原因: 和 sql 执行的顺序有关系,先执行where语句,然而此时的where语句,根本不知道'工资'是谁,所以报错了
'''

  • 二级排序示例
# 先按照工资降序排,如果工资一样,则按照部门ID升序排
SELECT department_id,last_name,salary AS 工资 FROM employees  ORDER BY 工资 DESC,department_id;

分页: 分批获取数据

# 获取前20条记录
SELECT * FROM employees LIMIT 0,20

# 每页显示20条记录,此时显示第二页的数据
SELECT * FROM employees LIMIT 20,20

# 每页显示20条记录,此时显示第三页的数据
SELECT * FROM employees LIMIT 40,20
  • 小结公式: 从第几页开始,显示多少条数据
# 从第pageNo页开始,显示pageSize条数据
LIMIT (pageNo-1) * pageSize,pageSize

  • 注意where...order by...limit 的声明顺序
# 查询工资最高的那一条记录
# select last_name,max(salary) from employees;

SELECT last_name,salary FROM employees ORDER BY salary DESC LIMIT 1;
  • offset:从第几个位置开始,往后面查询
# 显示第32,33条数据
SELECT * FROM employees LIMIT 31,2
SELECT * FROM employees LIMIT 2 OFFSET 31

多表连接查询之迪尔卡乘积

  • 出错原因: 缺少连接条件
## 以下两种写法都可以
- SELECT department_name,employee_id FROM departments,employees;

- SELECT department_name,employee_id FROM departments CROSS JOIN employees;

  • 纠正: 使用正确的连接条件即可

  • 注意事项: 如果有 n 个表连接查询,至少需要 n-1 个条件

### 这里给'表'取了'别名'以后,都要使用'别名';若继续使用'原名'会报错
SELECT d.department_name,e.employee_id FROM departments AS d,employees AS e
WHERE d.`department_id`=e.`department_id`;

### 练习: 多表查询以下字段: last_name,employee_id,department_name,city
SELECT e.last_name,e.employee_id,d.department_name,l.city FROM employees AS e,departments AS d,locations AS l
WHERE e.`department_id`=d.`department_id` AND d.`location_id`=l.`location_id`

自关联查询

  • 查询 员工的last_name,employee_id以及其管理者的 last_name,employee_id
SELECT em.last_name,em.employee_id,mm.employee_id,mm.last_name FROM employees em,employees mm
WHERE em.employee_id=mm.manager_id

内连接 和 外连接

  • 内连接:两个表的交集部分

  • 外连接:两个表的交集部分+左表/右表 部分 或者干脆两个表的全部

### 典型的内连接示例(sql92语法): 查询所有员工的 last_name 和 department_name
SELECT last_name,department_name FROM employees e,departments d
WHERE e.`department_id`=d.`department_id`

### 典型的内连接示例(sql99语法): 使用 inner join...on 连接条件(inner可以省略)
### 比如这里若设计三表查询,就在 inner join ...on....即可
SELECT last_name,department_name FROM employees e INNER JOIN departments d
ON e.`department_id`=d.`department_id`

### 外连接: left/right [outer] join
# left outer join: 这里 outer 可以省略
# 查询结果: 比起'内连接',多出了一行,null 那一行
SELECT last_name,department_name FROM employees e LEFT JOIN departments d
ON e.`department_id`=d.`department_id`

## 右外连接:结果是122行,employees 表(数据量少)被使用 Null 填充
SELECT last_name,department_name FROM employees e RIGHT JOIN departments d
ON e.`department_id`=d.`department_id`

- 小结: 外连有点类似于'长短脚',如果以'长脚'为主,那么多出来的部分,就会被 null 填充

### 满外连接"FULL JOIN",所有表的并集: mysql 不支持,会报语法错误
SELECT last_name,department_name FROM employees e FULL JOIN departments d
ON e.`department_id`=d.`department_id`

七种sql join 实现图

union/union all: 实现满外连接

- union: 删除重复的部分,效率相对低

    - A表+重复部分+B表

- union all: 保留重复的部分,效率相对高

    - A表+重复部分+重复部分+B表

- 注意事项:

    - 两个表的 列数和数据类型 必须相同,并且相互对应

- 语法:

    select column,... from tb1
    union [all]
    select column,... from tb2
  • demo演示
# 左外,107条记录
SELECT last_name,employee_id,department_name FROM employees e LEFT JOIN departments d
ON e.`department_id`=d.`department_id`

# 右外,122条记录
SELECT last_name,employee_id,department_name FROM employees e RIGHT JOIN departments d
ON e.`department_id`=d.`department_id`

# 交集部分(内连),106条记录
SELECT last_name,employee_id,department_name FROM employees e JOIN departments d
ON e.`department_id`=d.`department_id`

## 只要左表的部分,其余删除(在"左外"的基础上,减去'交集部分',即减去"内连")
# 107-106=1,这条记录什么呢?原来是 department_id = null 的记录,筛选出来即可
SELECT last_name,employee_id,department_name FROM employees e LEFT JOIN departments d
ON e.`department_id`=d.`department_id`
WHERE e.`department_id` IS NULL

# 只要右表的部分,其余删除:122-106=16
SELECT last_name,employee_id,department_name FROM employees e RIGHT JOIN departments d
ON e.`department_id`=d.`department_id`
WHERE e.`department_id` IS NULL

# 满外连接(所有): 左外+union all+右外: 107+122=229
SELECT last_name,employee_id,department_name FROM employees e LEFT JOIN departments d
ON e.`department_id`=d.`department_id`
UNION ALL
SELECT last_name,employee_id,department_name FROM employees e RIGHT JOIN departments d
ON e.`department_id`=d.`department_id`

# 满外连接(删除重复): 左外+union+右外,重复的部分106条记录,左表只剩1条记录,右表只剩16条记录:1+16+106=123
SELECT last_name,employee_id,department_name FROM employees e LEFT JOIN departments d
ON e.`department_id`=d.`department_id`
UNION
SELECT last_name,employee_id,department_name FROM employees e RIGHT JOIN departments d
ON e.`department_id`=d.`department_id`

# 删除两表交集的部分,其余保留("只要左表部分"+union all +'只要右表部分'):1+16=17条记录

SELECT last_name,employee_id,department_name FROM employees e LEFT JOIN departments d
ON e.`department_id`=d.`department_id`
WHERE e.`department_id` IS NULL
UNION ALL
SELECT last_name,employee_id,department_name FROM employees e RIGHT JOIN departments d
ON e.`department_id`=d.`department_id`
WHERE e.`department_id` IS NULL

自然连接: nature join

  • 可以理解为sql92的'等值连接'

  • 帮你自动查询两张表中所有相同的字段,然后进行等值连接

  • 缺点:查询有局限性,比如以下示例,只仅仅想查询'department_id'相等的,NATURAL 就不适用了

# 32 条记录
SELECT last_name,employee_id,department_name FROM employees e JOIN departments d
ON e.`department_id`=d.`department_id`
AND e.`manager_id`=d.`manager_id`

# 一模一样的效果: NATURAL JOIN
SELECT last_name,employee_id,department_name FROM employees e NATURAL JOIN departments d

using 指定字段实现等值连接

  • 作用: 简化SQL代码,上述示例可以修改成如下这个样子
# 代码简约
SELECT last_name,employee_id,department_name FROM employees e JOIN departments d
USING (department_id,manager_id)

聚合函数: 处理一组数据,只返回一个值

  • 常见的聚合函数
- max

- min

- avg

- sum

- count
  • demo演示
# 操作每组数据,只返回一个结果
SELECT AVG(salary),SUM(salary),MIN(salary),MAX(salary) FROM employees
  • count(): 查询指定字段出现的次数(统计个数,'null'会被忽略)

    • 一般用于统计'行数': 该字段出现多少次,就有多少行!
# 返回结果: 107,35(null被忽略)
SELECT COUNT(salary),COUNT(commission_pct) FROM employees
### count(1)/count(*): 统计'行数'
# 分析:当count(1)/count(*)的时候,该行会返回1,所以累积下来,就是'行数'
### 公式 avg = sum/count,以下两个结果是相等的
SELECT AVG(salary),SUM(salary)/COUNT(1) FROM employees
  • 典型示例:统计所有人的平均奖金率
# 正常思路
SELECT SUM(commission_pct)/COUNT(*) FROM employees

# 有null值的,替换成0再参与计算,否则不准确,因为那些Null的记录不会参与计算,从而无法满足需求
SELECT AVG(IFNULL(commission_pct,0)),SUM(commission_pct)/COUNT(IFNULL(commission_pct,0)) FROM employees

分组函数: group by

  • 作用: 对字段进行分组,每组只返回一个结果

  • 典型示例: 求 各部门的平均工资

SELECT department_id,AVG(salary) FROM employees GROUP BY department_id

### 求各个部门和各个工种(job_id)的平均工资
# 先对部门进行分组,然后再对job_id进行分组,从而出结果
SELECT department_id,job_id,AVG(salary) FROM employees GROUP BY department_id,job_id
### 注意事项,这里如果写成以下的样子,虽然出结果了,但是不准确
# 因为,该结果集部门只对应了一个工种,而实际上,部门里面有多个工种
SELECT department_id,job_id,AVG(salary) FROM employees GROUP BY department_id

### - 规律: select中'非聚合函数'的字段,必须出现在group by 后面,反之,group by 后面的字段可以不出现在 select中
  • with rollup: 在所有查询出的分组记录之后增加一条记录
    该记录对聚合函数的字段,再次汇总计算所有记录一次

    • 注意事项: with rollup 和 order by 互斥,不要一起使用
### 计算各部门的平均工资
# with rollup 多出一行记录,统计 所有部门(所有人)的平均工资
SELECT department_id,AVG(salary) FROM employees GROUP BY department_id WITH ROLLUP
# with rollup相当于: SELECT AVG(salary) FROM employees; 

having: 分组之后再进行过滤数据

  • 引入场景:查询各个部门中最高工资比10000高的部门信息
# 错误演示,报错信息: Invalid use of group function,where 不能接 聚合函数
SELECT department_id,AVG(salary) FROM employees WHERE AVG(salary)>10000 GROUP BY department_id

  • 如果要接聚合函数,必须使用 having,它的位置放在 GROUP BY 后面
SELECT department_id,AVG(salary) FROM employees GROUP BY department_id HAVING AVG(salary)>10000 
  • 注意事项: HAVING 一般都与 GROUP BY 搭配使用,测试中,单独使用 HAVING 是没有意义的

  • 练习: 查询部门id为10,20,30,40部门中最高工资比10000高的部门信息

# 写法一: 在刚才示例基础上,加上where过滤条件即可(推荐)
SELECT department_id,MAX(salary) FROM employees WHERE department_id IN (10,20,30,40) GROUP BY department_id HAVING MAX(salary)>10000

# 写法二: 在having里面写所有的过滤条件(一模一样的结果)
SELECT department_id,MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary)>10000 AND department_id IN (10,20,30,40)
  • 小结:
- 当过滤条件中有聚合函数时,此过滤条件必须写在 having 中

- - 当过滤条件中没有聚合函数时,此过滤条件写在 having/where 都可以,建议写在where(执行效率高)

子查询

  • 引入示例: 谁的工资比Abel高
# 写法一:先查询 Abel 的工资,然后再进行比较
SELECT salary,last_name FROM employees WHERE last_name = 'Abel' # 返回结果:11000
SELECT salary,last_name FROM employees WHERE salary > 11000 # 返回10行记录

# 写法二:自关联
SELECT e.salary,e.last_name FROM employees e,employees em WHERE e.`salary` > em.`salary` AND em.`last_name` = 'Abel' # 返回10行记录

# 写法三:子查询
SELECT salary,last_name FROM employees WHERE salary > (
    SELECT salary FROM employees WHERE last_name = 'Abel' # 这里只能select salary,不能再添加其他字段
);

单行子查询练习示例

# 查询工资大于149号员工工资的员工的信息
SELECT * FROM employees WHERE salary > (
    SELECT salary FROM employees WHERE employee_id =149
)

# 返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资
SELECT last_name,job_id,salary FROM employees
WHERE job_id = (SELECT job_id FROM employees WHERE employee_id = 141)
AND salary > (SELECT salary FROM employees WHERE employee_id = 143)

# 返回公司工资最少的员工的last_name,job_id和salary
SELECT last_name,job_id,salary FROM employees
WHERE salary = (
    SELECT MIN(salary) FROM employees
)

# 查询与141号或174号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id

SELECT employee_id, manager_id, department_id
FROM employees
WHERE manager_id IN
(SELECT manager_id
FROM employees
WHERE employee_id IN (174,141))
AND department_id IN
(SELECT department_id
FROM employees
WHERE employee_id IN (174,141))
AND employee_id NOT IN(174,141);


# 查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT department_id,MIN(salary) FROM employees
GROUP BY department_id
HAVING MIN(salary) > (SELECT MIN(salary) FROM employees WHERE department_id = 50 GROUP BY department_id)

  • 子查询中的空值问题
# 这里例子不会报错,但是不会返回任何结果, job_id = null,所以没有返回结果
SELECT last_name, job_id FROM employees
WHERE job_id = (
    SELECT job_id
    FROM employees
    WHERE last_name = 'Haas'
);
  • 非法使用子查询
# 报错: subquery returns more than 1 row(子查询返回不止一个值,sql无法处理)
SELECT employee_id, last_name FROM employees
WHERE salary = (SELECT MIN(salary) FROM employees GROUP BY department_id);

多行子查询

  • 多行比较操作符

- IN: 等于列表中的任意一个

- ANY(SOME): 需要和单行比较操作符一起使用,和子查询返回的某一个值比较

- ALL: 需要和单行比较操作符一起使用,和子查询返回的所有值比较

- 注意 IN 和 ANY 的区别

# 返回 其它job_id中 比 job_id为‘IT_PROG’部门任一工资低的员工 的     员工号、姓名、job_id 以及salary
SELECT employee_id,last_name,job_id,salary FROM employees
WHERE salary < ANY( # 注意搭配符号'< ANY',这里若 ANY 丢了,就变成单行子查询,会报错
    SELECT salary FROM employees WHERE job_id = 'IT_PROG'
)
AND job_id <> 'IT_PROG'

# 返回 其它job_id中 比 job_id为‘IT_PROG’部门"所有"工资低的员工 的     员工号、姓名、job_id 以及salary
SELECT employee_id,last_name,job_id,salary FROM employees
WHERE salary < ALL( # 修改之处
    SELECT salary FROM employees WHERE job_id = 'IT_PROG'
)
AND job_id <> 'IT_PROG'


# 查询平均工资最低的部门id
SELECT department_id FROM employees GROUP BY department_id HAVING AVG (salary) =
(SELECT MIN (avg_sal) FROM
(SELECT AVG (salary) avg_sal FROM employees GROUP BY department_id) dept_avg_sal)

# 空值问题
SELECT last_name FROM employees
WHERE employee_id NOT IN (
SELECT manager_id
FROM employees);

关联子查询

  • 定义:如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,
    并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询
# 查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id(看不懂)
SELECT last_name,salary,department_id FROM employees em WHERE salary > (
    SELECT AVG(salary) FROM employees WHERE department_id = em.department_id
)
  • 在from 中使用子查询
SELECT last_name,salary,e1.department_id
FROM employees e1,(SELECT department_id,AVG(salary) dept_avg_sal FROM employees GROUP
BY department_id) e2
WHERE e1.`department_id` = e2.department_id
AND e2.dept_avg_sal < e1.`salary`;
  • 在ORDER BY 中使用子查询
# 查询员工的id,salary,按照department_name 排序
SELECT employee_id,salary
FROM employees e
ORDER BY (
        SELECT department_name
        FROM departments d
        WHERE e.`department_id` = d.`department_id`
    );
  • 小结: 在 SELECT 中,除了 GROUP BY 和 LIMIT 之外,其他位置都可以声明子查询

EXISTS 与 NOT EXISTS关键字