DQL :数据查询语言,用于检索数据库中的数据,主要是SELECT语句;
1、基础查询
语法格式如下:
1、查询多个字段
SELECT 字段列表 FROM 表名;
SELECT * FROM 表名; -- 查询所有数据
2、去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
3、起别名
AS:新名字 (AS也可以省略)
导入SQL语句:
-- 如果这张表存在则删掉它
DROP TABLE IF EXISTS `stu`;
-- 创建学生表
CREATE TABLE `stu`(
`id` INT NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(20) COMMENT '姓名',
`age` int COMMENT'年龄',
`sex` VARCHAR(5) COMMENT '性别',
`address` VARCHAR(100) COMMENT '地址',
`math` DOUBLE(5,2) COMMENT '数学成绩',
`english` DOUBLE(5,2) COMMENT '英语成绩',
`hire_date` DATE COMMENT '入学时间',
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8; -- 设置引擎和字符集
-- 添加数据
INSERT INTO `stu`(`name`,`age`,`sex`,`address`,`math`,`english`,`hire_date`) VALUES
('张三',18,'男','杭州',66,78,'2021-2-19'),
('李四',19,'女','北京',76,98,'2021-2-19'),
('王五',20,'男','苏州',86,78,'2021-2-19'),
('赵六',22,'男','重庆',46,98,'2021-2-19'),
('陈七',19,'女','重庆',56,68,'2021-2-19'),
('白豆五',18,'男','哈尔滨',100,100,'2021-2-20');
【示例】:查询表中所有列的数据
select * from stu;
【示例】:查询表中列为 “name” 和 “age” 的数据
select `name`,`age` from stu;
【示例】:查询地址信息并去除重复记录
select distinct`address` from stu;
【示例】查询学生的姓名和数学成绩和英语成绩
select name as 姓名,math as 数学成绩,english as 英语成绩 from stu;
2、条件查询 (WHERE)
语法格式如下:
SELECT 字段列表 FROM 表名 WHERE 条件列表;
条件:
【示例】查询年龄大于等于20岁的学生信息
select * from stu where age>=20;
【示例】查询年龄大于等于20岁,并且年龄小于等于30岁的学生信息
select * from stu where age>=70 and age<=30;
select * from stu where age>=70 && age<=30;
select * from stu where age between 20 and 30;
【示例】查询数学成绩在80到100之间的同学信息
select * from stu where math between 80 and 100
【示例】查询数学或英语成绩及格的同学
select * from stu where math>=60 or english>=60
LIKE:模糊查询
LIKE 关键字支持百分号%和下划线_通配符。
% : 代表任意个数字符
_ : 代表单个任意字符
【示例】查询姓 ’ 张 ’ 的同学
select * from stu where name like '张%';
【示例】查询第二个字是 ’ 豆 ’ 的同学
select * from stu where name like '_豆%';
【示例】查询名字中包含 ’ 五 ’ 的同学
select * from stu where name like '%五%';
3、分组查询(GROUP BY)
聚合函数
聚合函数:将一列数据作为一个整体,进行纵向计算;
聚合函数分类:
函数名 | 功能 |
---|---|
count(列名) | 统计数量(一般选用不为null的列) |
max(列名) | 最大值 |
min(列名) | 最小值 |
sum(列名) | 求和 |
avg(列名) | 平均值 |
聚合函数语法:
SELECT 聚合函数名(列名) FROM 表名;
注:null 值 不参与所有聚合函数运算。
【示例】 统计班级一共有多少个学生
SELECT count(id) as 人数 from stu; -- count取值: count(*)、 count(主键)
【示例】查询数学成绩的最高分
select max(math) as 数学成绩最高分 from stu;
【示例】查询数学成绩的总分
select sum(math) as 数学成绩的总分 from stu;
【示例】查询数学的平均成绩
select avg(math) as 数学的平均成绩 from stu;
分组查询
语法格式如下:
SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名 [HAVING 分组后条件过滤];
注:分组之后,查询的字段为 聚合函数 和 分组字段 ,查询其他字段无任何意义。
where 和 having的区别:
- 执行时机不一样:where是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。
- 可判断的条件不一样:where不能对聚合函数进行判断,having可以。
执行顺序:where > 聚合函数 > having。
【示例】查询男同学和女同学各自的数学的平均分
select sex,avg(math) from stu group by sex;
【示例】查询男同学和女同学各自的数学平均分,以及各自人数
select sex,avg(math),count(*) from stu group by sex;
【示例】查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组
select sex,avg(math),count(*) from stu where math>=70 group by sex;
【示例】查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于60分的不参与分组,分组之后人数大于2个
select sex,avg(math),count(*) from stu where math>=60 group by sex having count(*)>2
4、排序查询(ORDER BY)
语法格式如下:
SELECT 字段列表 FROM 表名 ORDER BY 排序字段名1 [排序方式1],排序字段名2 [排序方式2]...;
排序方式:
ASC:升序排序 (默认值)
DESC:降序排序
注: 如果有多个排序条件,当前边的条件值一样时,才会根据第二个条件进行排序
【示例】查询学生信息,按照年龄升序排列
select * from stu order by age asc;
select * from stu order by age;
【示例】查询学生信息,按照数学成绩降序排列
select * from stu order by math desc;
【示例】查询学生信息,按照数学成绩降序排列,如果数学成绩一样,在按照英语成绩升序排列
select * from stu order by math desc,english asc;
5、分页查询(LIMIT)
语法格式如下:
SELECT 字段列表 FROM 表名 LIMIT 起始索引,查询条目数;
-- 起始索引:从0开始
计算公式:起始索引 = (当前页码 - 1)* 每页显示的条数;
【示例】从0开始查询,查询3条数据
select * from stu limit 0,3;
【示例】每页显示3条数据,查询第1页数据
select * from stu limit 0,3;
【示例】每页显示3条数据,查询第2页数据
select * from stu limit 3,3;
6、约束
-
约束是作用于表中列上的规则,用于限制加入表中的数据。
-
约束的存在保证了数据库中数据的正确性、有效性和完整性。
约束的分类:
约束名称 | 描述 | 关键字 |
---|---|---|
非空约束 | 保证列中所有数据不能有null值 | NOT NULL |
唯一约束 | 保证列中所有数据各不相同 | UNIQUE |
主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 | PRIMARY KEY |
检查约束 (MySQL不支持) | 保证列中的值满足某一条件 | CHECK |
默认约束 | 保存数据时,未指定值则采用默认值 | DEFAULT |
外键约束 | 外键用来让两个表中的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY |
示例:
USE school;
DROP TABLE IF EXISTS emp;-- 创建员工表
CREATE TABLE emp (
`id` INT PRIMARY KEY auto_increment COMMENT '员工id',-- 员工id,主键自增且自增长
`ename` VARCHAR ( 50 ) NOT NULL UNIQUE COMMENT '员工姓名',-- 员工姓名,非空并且唯一
`joindate` date NOT NULL COMMENT '入职日期', -- 入职日期,非空
`salary` DOUBLE ( 7, 2 ) NOT NULL COMMENT '工资', -- 工资,非空
`bonus` DOUBLE ( 7, 2 ) DEFAULT 0 COMMENT '奖金' -- 奖金,如果没有奖金默认为0
)ENGINE=INNODB DEFAULT CHARSET=utf8;-- 设置引擎和字符集
insert into emp(ename,joindate,salary,bonus) values('张三','1999-11-11',8500,5000);
insert into emp(ename,joindate,salary) values('李四','1999-11-11',8500);
添加约束:可以在建表语句中添加,可以在创建表之后添加。
alter table 表名 modify 字段名 数据类型 约束名;
删除约束:
alter table 表名 modify 字段名 数据类型;
外键约束(foreign key)
作用:用来让两个表的数据之间建立链接,保证数据的一致性和完整性;
使用:在子表添加外键约束,然后引用主表中某字段。
【示例 】在创建表的时候,增加约束
- 创建年级表(id,年级名称)
CREATE TABLE `grade`(
`gradeid` INT(10) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
PRIMARY KEY(`gradeid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 创建学生信息表(学号,姓名,密码,性别,出生日期,学生年级,家庭地址,邮箱)
CREATE TABLE IF NOT EXISTS `student` (
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`gradeid` INT(10) NOT NULL COMMENT '学生年级',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭地址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY(`id`), -- 将id设置主键
KEY `FK_gradeid` (`gradeid`), -- 定义外键
CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`) -- 给这个外键添加约束(CONSTRAINT),然后执行引用(REFERENCES)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
注:如果要删除有外键关系的表,必须先删除引用的别人的表(从表),再删除被引用的表(主表)。
【示例 】创建表成功后,添加外键约束
-- 创建年级表(id,年级名称)
CREATE TABLE `grade`(
`gradeid` INT(10) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
PRIMARY KEY(`gradeid`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 创建学生信息表(学号,姓名,密码,性别,出生日期,学生年级,家庭地址,邮箱)
CREATE TABLE IF NOT EXISTS `student` (
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`gradeid` INT(10) NOT NULL COMMENT '学生年级',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭地址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY(`id`) -- 将id设置主键
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 给学生表添加外键约束
ALTER TABLE `student`
ADD
CONSTRAINT `FK_gradeid` FOREIGN KEY(`gradeid`) REFERENCES `grade`(`gradeid`);
阿里手册相关约束:
7、数据库设计
软件的研发步骤
数据库设计的概念
- 根据业务系统的具体需求,结合我们所选的DBMS,为这个业务构造出最优的数据存储模型;
- 然后考虑有哪些表组成,表里有哪些字段,表与表之间存在着什么关系;
数据库设计的步骤
1、需求分析(数据是什么,数据具有哪些属性,数据与属性的特点是什么)
2、逻辑分析(通过ER图对数据库进行逻辑建模)
3、物理设计(根据数据库自身的特点把逻辑设计转换成物理设计)
4、维护设计(对新的需求进行建表、表优化)
表关系
-
表关系有 一对一 ,一对多(多对一),多对多 ;
-
一对一的实现方式:用于表拆分,将实体常用的字段放一张表,不经常用的字段放另一张表,然后在任意一方建立外键,关联对方主键,并设置外键唯一(unique);
-
一对多的实现方式:在多的一个方建立
外键(foreign key)
,指向一的那方主键; -
多对多的实现方式:建立第三张
中间表
, 中间表至少包含两个外键
,分别关联双方主键
;
示例:多对多关系
-- 商品表
CREATE TABLE `goods`(
`id` int PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
`title` VARCHAR(100) COMMENT '标题',
`price` DOUBLE(10,2) COMMENT '价格'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 订单表
CREATE TABLE `order`(
`id` int PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
`payment` DOUBLE(10,2) COMMENT '付款金额',
`payment_type` TINYINT COMMENT '付款类型',
`status` TINYINT COMMENT '状态'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 订单商品中间表
CREATE TABLE `order_goods`(
`id` int PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
`order_id` int COMMENT '外键order_id',
`goods_id` int COMMENT '外键goods_id',
`count` int COMMENT '数量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 添加外键
ALTER TABLE `order_goods` ADD CONSTRAINT `fk_order_id` FOREIGN KEY(`order_id`) REFERENCES `order`(`id`);
ALTER TABLE `order_goods` ADD CONSTRAINT `fk_goods_id` FOREIGN KEY(`goods_id`) REFERENCES `goods`(`id`);
8、多表查询(联表查询)
多表查询顾名思义,从多张表中查询数据
插入表数据:
# 创建部门表
CREATE TABLE dept(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门编号',
NAME VARCHAR(20) COMMENT '部门名称'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO dept (NAME) VALUES ('开发部'),('市场部'),('财务部'),('销售部');
# 创建员工表
CREATE TABLE emp (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工编号',
NAME VARCHAR(10) COMMENT '员工名称',
gender CHAR(1) COMMENT '性别',
salary DOUBLE COMMENT '工资',
join_date DATE COMMENT '入职日期',
dept_id INT COMMENT '部门编号',
FOREIGN KEY (dept_id) REFERENCES dept(id) -- 外键,关联部门表(部门表的主键)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('张三','男',7200,'2013-02-24',1);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('李四','男',3600,'2010-12-02',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('王五','男',9000,'2008-08-08',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('赵六','女',5000,'2015-10-07',3);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('陈七','女',4500,'2011-03-14',1);
INSERT INTO emp(NAME,gender,salary,join_date) VALUES('张宝','女',3500,'2016-03-14');
笛卡尔积:
- 有两个集合A,B ;取这两个集合的所有组成情况。
- 要完成多表查询,需要消除无用的数据。
8.1、连接查询
内连接(相当于查询两个集合交集的数据)
语法如下:
-- 隐式内连接
SELECT 字段列表 FROM 表1,表2,.. WHERE 条件;
-- 显示内连接
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件;
示例:查询员工的姓名、性别、薪资以及所在部门的名称(隐式内连接)
-- 给数据表起别名 emp e1,dept d1
select
e1.name,e1.gender,e1.salary,d1.name
from
emp e1,dept d1
where
e1.dept_id=d1.id;
示例:使用显示内连接方式查询表中的数据
select * from emp inner join dept on emp.dept_id = dept.id;
或
select * from emp join dept on emp.dept_id = dept.id;
外连接
1、左外连接(相当于查询A表所有数据和交集部分数据)
语法如下:
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;
示例:查询emp表所有数据和对应的部门信息
select * from emp left join dept on emp.dept_id = dept.id;
2、右外连接(相当于查询B表所有数据和交集部分数据)
语法如下:
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;
示例:查询dept表所有数据和对应的员工信息
select * from emp right join dept on emp.dept_id = dept.id;
或
select * from dept left join emp on emp.dept_id = dept.id;
8.2、子查询
子查询:查询中的嵌套查询,程嵌套查询为子查询
子查询根据查询结果不同,作用也是不同的:
- 单行单列
- 多行单列
- 多行多列
单行单列(作为条件值,使用=、!=、>、<等进行条件判断)
语法格式如下:
SELECT 字段列表 FROM 表 WHERE 字段名 = (子查询);
示例:查询工资高于赵六的员工信息(单行单列)
select * from emp where salary > (select salary from emp where name = '赵六');
示例:查询 【财务部】 所有的员工信息
select * from emp where dept_id=(select id from dept where name='财务部')
多行单列(作为条件值,使用 in 等关键字进行条件判断)
语法格式如下:
SELECT 字段列表 FROM 表 WHERE 字段名 in (子查询);
示例:查询 【财务部或者开发部】 所有的员工信息
select * from emp where dept_id in (select id from dept where name='财务部' or name='开发部')
多行多列(作为虚拟表)
语法格式如下:
SELECT 字段列表 FROM (子查询) WHERE 条件;
示例:查询入职日期是2011-3-1之后的员工信息和部门信息
select * from (select * from emp where join_date > '2011-3-1') e1,dept where e1.dept_id = dept.id;
课后练习
插入表数据:
DROP TABLE IF EXISTS emp;
DROP TABLE IF EXISTS dept;
DROP TABLE IF EXISTS job;
DROP TABLE IF EXISTS salarygrade;
-- 部门表
CREATE TABLE dept (
id INT PRIMARY KEY PRIMARY KEY COMMENT '部门编号',
dname VARCHAR(50) COMMENT '部门名称',
loc VARCHAR(50) COMMENT '部门所在地'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 添加4个部门
INSERT INTO dept(id,dname,loc) VALUES
(10,'教研部','北京'),
(20,'学工部','上海'),
(30,'销售部','广州'),
(40,'财务部','深圳');
-- 职务表
CREATE TABLE job (
id INT PRIMARY KEY COMMENT '职务编号',
jname VARCHAR(20) COMMENT '职务名称',
description VARCHAR(50) COMMENT '职务描述'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 添加4个职务
INSERT INTO job (id, jname, description) VALUES
(1, '董事长', '管理整个公司,接单'),
(2, '经理', '管理部门员工'),
(3, '销售员', '向客人推销产品'),
(4, '文员', '使用办公软件');
-- 员工表
CREATE TABLE emp (
id INT PRIMARY KEY COMMENT '员工编号',
ename VARCHAR(50) COMMENT '员工姓名' ,
job_id INT COMMENT '职务id',
mgr INT COMMENT '上级领导',
joindate DATE COMMENT '入职日期',
salary DECIMAL(7,2) COMMENT '工资',
bonus DECIMAL(7,2) COMMENT '奖金',
dept_id INT COMMENT '所在部门编号',
CONSTRAINT emp_jobid_ref_job_id_fk FOREIGN KEY (job_id) REFERENCES job (id), -- 外键
CONSTRAINT emp_deptid_ref_dept_id_fk FOREIGN KEY (dept_id) REFERENCES dept (id) -- 外键
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 添加员工
INSERT INTO emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id) VALUES
(1001,'张三',4,1004,'2000-12-17','8000.00',NULL,20),
(1002,'李四',3,1006,'2001-02-20','16000.00','3000.00',30),
(1003,'王五',3,1006,'2001-02-22','12500.00','5000.00',30),
(1004,'赵六',2,1009,'2001-04-02','29750.00',NULL,20),
(1005,'陈七',4,1006,'2001-09-28','12500.00','14000.00',30),
(1006,'兜兜飞',2,1009,'2001-05-01','28500.00',NULL,30),
(1007,'张宝',2,1009,'2001-09-01','24500.00',NULL,10),
(1008,'马飞',4,1004,'2007-04-19','30000.00',NULL,20),
(1009,'张平',1,NULL,'2001-11-17','50000.00',NULL,10),
(1010,'陈皮',3,1006,'2001-09-08','15000.00','0.00',30),
(1011,'沙海',4,1004,'2007-05-23','11000.00',NULL,20),
(1012,'旺财',4,1006,'2001-12-03','9500.00',NULL,30),
(1013,'岑凤强',4,1004,'2001-12-03','30000.00',NULL,20),
(1014,'杜昂',4,1007,'2002-01-23','13000.00',NULL,10);
-- 工资等级表
CREATE TABLE salarygrade (
grade INT PRIMARY KEY COMMENT '工资级别',
losalary INT COMMENT '最低工资',
hisalary INT COMMENT '最高工资'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 添加5个工资等级
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES
(1,7000,12000),
(2,12010,14000),
(3,14010,20000),
(4,20010,30000),
(5,30010,99990);
查看表模型:
1、查询所有员工信息。查询员工编号,员工姓名,工资,职务名称,职务描述
分析:emp(员工编号、员工姓名、工资),job(职务名称、职务描述)
-- 隐式内连接
select emp.id,emp.ename,emp.salary,job.jname,job.description from emp,job where emp.job_id=job.id;
-- 显式内连接
select emp.id,emp.ename,emp.salary,job.jname,job.description from emp inner join job on emp.job_id=job.id;
2、查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
分析: 分析:emp(员工编号、员工姓名、工资),job(职务名称、职务描述),dept(部门名称、部门位置)
select emp.id,emp.ename,emp.salary,job.jname,job.description,dept.dname,dept.loc
from emp,job,dept
where emp.job_id=job.id and emp.dept_id=dept.id;
3、查询员工姓名,工资,工资等级
分析:emp(员工姓名、工资),salarygrade(工资等级), losalary<=salary<=hisalary
-- 第一种写法
select emp.ename,emp.salary,t1.*
from emp t1,salarygrade t1
where emp.salary>=t1.losalary and emp.salary <= t1.hisalary;
-- 第二种写法
select emp.ename,emp t1.salary,t1.*
from emp,salarygrade t1
where emp.salary BETWEEN t1.losalary and t1.hisalary;
4、查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
分析:emp(员工姓名、工资),job(职务名称、职务描述),dept(部门名称、部门位置),salarygrade(工资等级)
select
emp.ename,emp.salary,job.jname,job.description,dept.dname,dept.loc,t1.grade
from
emp,job,dept,salarygrade t1
where
emp.job_id = job.id
and dept.id=emp.dept_id
and emp.salary between t1.losalary and t1.hisalary;
5、查询部门编号、部门名称、部门位置、部门人数
分析:dept(部门编号、部门名称、部门位置 ) ,emp(部门人数dept_id)
-- 查看部门的人数
select dept_id,count(*) from emp group by dept_id;
-- 子查询
select dept.id,dept.dname,dept.loc, t1.count
from dept,(select dept_id,count(*) count from emp group by dept_id) t1
where t1.dept_id=dept.id;
6、查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询
select t1.ename as '员工姓名',t2.ename as '上级' from emp t1,emp t2 where t1.mgr=t2.id
-- 左外连接
select t1.ename as '员工姓名',t2.ename as '上级' from emp t1 left join emp t2 on t1.mgr=t2.id