课堂任务
上课用的是scott用户。
多表查询
上周的课主要讲了单表查询和一些常用函数,今天讲多表查询。
等值连接
- 查询员工信息:员工号 姓名 月薪 部门名称
select empno,ename,sal,dname
from emp,dept
where emp.deptno=dept.deptno;
- 查询员工信息:员工号 姓名 月薪 部门名称 部门编号
错误写法,原因是表emp和dept中都有名为deptno的属性(列),没有指定是显示哪个表中的deptno
select empno,ename,sal,dname,deptno
from emp,dept
where emp.deptno=dept.deptno;
select empno,ename,sal,dname,deptno
*
第 1 行出现错误:
ORA-00918: 未明确定义列
正确写法
select empno,ename,sal,dname,emp.deptno
from emp,dept
where emp.deptno=dept.deptno;
或者
select e.empno,e.ename,e.sal,d.dname,e.deptno
from emp e,dept d
where e.deptno=d.deptno;
不等值连接
- 查询员工信息:员工号 姓名 月薪 工资级别
select e.empno,e.ename,e.sal,s.grade
from emp e,salgrade s
where e.sal between s.losal and s.hisal;
左、右外连接
- 按部门统计员工信息:部门号 部门名称 人数
select d.deptno,d.dname,count(*)
from emp e,dept d
where e.deptno=d.deptno
group by d.deptno,d.dname;
这样写好像对了,实际上不是。在查询结果中没有显示40号部门的员工,而实际上是有40号这个部门的,只不过员工数为0。我们希望把某些不成立的记录(40号部门)仍然包含在最后的结果中。
左外连接:当where e.deptno=d.deptno不成立的时候,等号左边的表任然被包含在最后的结果中。
写法:where e.deptno=d.deptno(+)
右外连接:当where e.deptno=d.deptno不成立的时候,等号右边的表任然被包含在最后的结果中。
写法:where e.deptnb(+)=d.deptno
这个加号可以这样来理解:+ 表示补充,即哪个表有加号,这个表就是匹配表。如果加号写在左表,右表就是全部显示,故是右外连接。
select d.deptno,d.dname,count(*)
from emp e join all dept d
where e.deptno=d.deptno
group by d.deptno,d.dname;
运行的结果中,40号部门的结果为1,这里的1并不代表它的员工人数为1,代表的是查询40号部门人数时,结果为空这一条数据。写一个全连接的例子,看查询结果的最后一行,可以很好的理解。
select * from emp e full join dept d on e.deptno=d.deptno;
结果如下
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO DEPTNO DNAME LOC
------ --------- --------- ---------- ----------- ---------- ---------- ------- ------- ------------- -------
7369 SMITH CLERK 7902 17-12月-80 800 20 20 RESEARCH DALLAS
7499 ALLEN SALESMAN 7698 20-2月 -81 1600 300 30 30 SALES CHICAGO
7521 WARD SALESMAN 7698 22-2月 -81 1250 500 30 30 SALES CHICAGO
7566 JONES MANAGER 7839 02-4月 -81 2975 20 20 RESEARCH DALLAS
7654 MARTIN SALESMAN 7698 28-9月 -81 1250 1400 30 30 SALES CHICAGO
7698 BLAKE MANAGER 7839 01-5月 -81 2850 30 30 SALES CHICAGO
7782 CLARK MANAGER 7839 09-6月 -81 2450 10 10 ACCOUNTING NEW YORK
7788 SCOTT ANALYST 7566 19-4月 -87 3000 20 20 RESEARCH DALLAS
7839 KING PRESIDENT 17-11月-81 5000 10 10 ACCOUNTING NEW YORK
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO DEPTNO DNAME LOC
------ --------- --------- ---------- ----------- ---------- ---------- ------- ------- ------------- -------
7844 TURNER SALESMAN 7698 08-9月 -81 1500 0 30 30 SALES CHICAGO
7876 ADAMS CLERK 7788 23-5月 -87 1100 20 20 RESEARCH DALLAS
7900 JAMES CLERK 7698 03-12月-81 950 30 30 SALES CHICAGO
7902 FORD ANALYST 7566 03-12月-81 3000 20 20 RESEARCH DALLAS
7934 MILLER CLERK 7782 23-1月 -82 1300 10 10 ACCOUNTING NEW YORK
40 OPERATIONS BOSTON
自连接
- 查询员工信息:员工姓名 老板姓名
select e.ename,b.ename
from emp e,emp b
where e.mgr=b.empno;
层次查询
- 查询员工之间的上下级(MGR)
select level,empno,ename,mgr
from emp
connect by prior empno=mgr
start with mgr is null
order by 1;
子查询
子查询属于嵌套查询,在主查询之前一次执行完成,子查询的结果被主查询使用。
单行子查询:
- 返回一条记录
- 要使用单行操作符:=,>,>=,<,<=
多行子查询: - 返回多条记录
- 要使用多行操作符:in在集合中,any和任意一个值比较,all和所有值比较
子查询需要注意的问题:
- 括号
- 合理的书写风格
- 可以在主查询的where、select、having、from后使用子查询
- 不可以在group by后使用子查询
- 主查询和子查询可以不是同一张表,主查询可以使用子查询的结果
- 一般不在子查询中排序,但在top-n分析问题中,必须对子查询排序
- 单行子查询只能使用单行操作符,多行子查询只能使用多行操作符
- 子查询中null值问题
- 查询工资比scott高的员工信息
select *
from emp
where sal>(select sal from emp where ename='SCOTT');
- 查询部门最低的工资和比10号部门最低工资还要低的部门号
select deptno,min(sal)
from emp
group by deptno
having min(sal)<(select min(sal) from emp where deptno=10);
- 使用from后的子查询,查询员工信息:员工号 姓名 月薪
select * from (select empno,ename,sal from emp);
- 使用where后的子查询,查询部门名称是SALES的员工
select *
from emp
where deptno=(select deptno from dept where dname='SALES');
- in在集合中
查询部门名称是SALES和ACCOUNTING的员工
select *
from emp
where deptno in (select deptno from dept where dname='SAKES' or dname='ACCOUNTING');
- any和任意一个值比较
查询工资比30号部门任意一个员工高的员工信息
select *
from emp
where sal>any(select sal from emp where deptno=30);
查询结果与下列语句相同,但结果顺序不同
select *
from emp
where sal>(select min(sal) from emp where deptno=30);
- all和集合中的所有值比较
查询工资比30号部门所有员工高的员工信息
select *
from emp
where sal>all(select sal from emp where deptno=30);
查询结果与下列语句相同,但结果顺序不同
select *
from emp
where sal>(select max(sal) from emp where deptno=30);
- 查询不是老板的员工信息
select *
from emp
where empno not in(select mgr from emp where mgr is not null)
相关子查询
一般子查询是先执行子查询再执行主查询,但是相关子查询比较特殊。相关子查询将主查询中的值,作为参数传递给子查询
select e.empno,e.ename,e.sal,(select avg(sal) from emp where deptno=e.deptno) avgsal
from emp e
where sal >(select avg(sal) from emp where deptno=e.deptno);
行转列
wm_concat(varchar2)多行函数
select deptno,wm_concat(ename)
from emp
group by deptno;
集合运算
集合运算注意的问题:
- 参与运算的各个集合必须列数相同,且类型一致
- 采用第一个集合作为最后的表头
- order by用在最后
- 括号
- 查看执行时间
set timing on;
- 查询10号和20号部门的员工
select * from emp where deptno=10
union
select * from emp where deptno=20;
等同于
select * from emp where deptno=10 or deptno=20;
select * from emp where deptno in(10,20);
- 参与运算的各个集合必须列数相同,且类型一致
select deptno,job,sum(sal) from emp group by deptno,job
union
select deptno,to_char(null),sum(sal) from emp group by deptno
union
select to_number(null),to_char(null),sum(sal) from emp;
- 交集
显示薪水同时位于(700 ~ 1300)和(1201 ~ 1400)的员工信息
select ename,sal from emp
where sal between 700 and 1300
intersect
select ename,sal from emp
where sal between 1201 and 1400;
- 差集
显示薪水同时位于(700 ~ 1300)不属于(1201 ~ 1400)的员工信息
select ename,sal from emp
where sal between 700 and 1300
minus
select ename,sal from emp
where sal between 1201 and 1400;
rownum
- 跟level和dual一样,是伪列
- rownum永远按照默认的顺序生成
- rownum只能使用<,<=不能使用>,>=
- rownum 永远从1开始
select rownum,empno,ename,sal from emp;
Select语句的执行顺序
- from
- where
- group by
- having
- select
- order by
课堂练习
- 找到员工表中工资最高的前三名
格式如下
select rownum,empno,ename,sal
from (select * from emp order by sal desc)
where rownum<=3;
- 找到员工表中薪水大于本部门平均薪水的员工
格式如下
select e.empno,e.ename,e.sal,d.avgsal
from emp e,(select deptno,avg(sal) avgsal from emp group by deptno) d
where e.deptno=d.deptno and e.sal>d.avgsal;
- 统计每年入职的员工个数
格式如下
select count(*) total,
sum(decode(to_char(hiredate,'yyyy'),'1980',1,0)) "1980",
sum(decode(to_char(hiredate,'yyyy'),'1981',1,0)) "1981",
sum(decode(to_char(hiredate,'yyyy'),'1982',1,0)) "1982",
sum(decode(to_char(hiredate,'yyyy'),'1987',1,0)) "1987"
from emp;
Oracle分页
select *
from (select rownum r,empno,ename,sal from
(select * from emp order by sal desc) e1
where rownum <=8) e2
where r>=5;
SQL语句分类
- DQL(Data Query Language数据查询语言标准语法):select
- DML(Data Manipulation Language 数据操作语言):insert update delete
- DDL(Data Definition Language 数据定义语言):create table,alter table,truncate table,drop table,create/drop view,sequence,index,synonym(同义词)
- DCL(Data Control Language 数据控制语言) :grant (授权) revoke(撤销权限)
插入数据
insert语句的语法:insert into table [(column[,column...])] values (value,[,valuie]);
使用这种语法一次只能向表中插入一条数据。
insert into emp(empno,ename,sal,deptno) values (1001,'tom',3000,10);
insert into emp(empno,ename,sal,deptno) values (&empno,&ename,&sal,&deptno);
更新数据
update更新语法:update table set column=value where columm=value;
更新7934号员工的工资和工资与其7902员工相同
update emp10
set sal=(select sal from emp where empno=7902)
where empno=7934;
约束
update emp
set deptno=55
where deptno=20;
update emp
*
第 1 行出现错误:
ORA-02291: 违反完整约束条件 (SCOTT.FK_DEPTNO) - 未找到父项关键字
更新的时候数据出现了完整性的错误,数据的完整性指的就是我们创建在这张表的约束(主外键的约束)。
在数据库开发中,约束是必不可少的,使用约束可以更好的保证数据的完整性,在oracle数据库中,约束的类型包括:
主键约束(Primary key)
一般来说,关系型数据库所有的表都应该有主键,不过可以没有。
主键特点:
- 键列必须具有唯一性,且不能为空,其实主键约束相当于
unique + not null
- 一个表值只允许一个主键
- 主键所在的列必须具有索引,如果不存在,会自动创建一个
往现有表中添加主键约束:
alter table emp add constraint emp_id_pk primary key(id);
创建新表时添加主键约束:
create table person(
pid number(10) primary key,
name varchar2(10),
gender number(1) default 1,
birthday date
);
非空约束(Not null)
也叫强制列,强制键中必须有值。
往现有表中添加非空约束:
alter table emp modify ename not null;
创建新表时添加非空约束:
create table person(
Pid number(10) primary key,
name varchar2(10) not null,
gender number(1) default 1,
birthday date
);
唯一约束(Unique)
唯一约束包每一行的唯一性
- 对于unique约束来讲,索引是必须的,如果不存在,会自动创建一个
- unique约束的列可存在多个null
往现有表中添加唯一约束:
alter table emp add constraint emp_code_uq unique(code);
创建新表时添加唯一约束:
create table person(
pid number(10) primary key,
name varchar2(10) unique,
gender number(1) default 1,
birthday date
);
外键约束(Foreign Key)
是不是父表中的每一列都可以作为子表的外键?不是,子表的外键它必须是父表的主键或者唯一键。
父表中的某条记录被子表中引用,我想直接删除父表的记录,能删除掉吗?不一定能删除掉。可以先把子表的记录删除完,再删除父表。或者创建级联删除。
外键特点:
- 外键约束的子表中的列和对应父表中的列数据必须相同,列名可以不同
- 对应的父表的列存在主键约束(primary key)或唯一约束
- 外键元素允许nul值,对应的行就为孤行
往现有表中添加外键约束:
alter table emp10 add constraint emp_deptno_fk foreign key(deptno) references dept(deptno) ;
删除现有表中的外键约束
alter table emp10 drop constraint emp10_dept10_fk;
检查性约束(Check):
检查约束可以用来实施一些简单的规则,比如列值在摸个范围内。
往现有表中添加检查性约束:
alter table emp10 add constraint emp_sex_ck check(sex in('M','F'));
创建新表时添加检查性约束:
create table person(
Pid number(10) primary key,
name varchar2(10) unique,
gender number(1) check (Gender in(0,1)),
birthday date
);
删除
delete 和 truncate的区别:
- delete是DML,truncate是DDL。前者可以回滚(rollback),后者不可以回滚
- delte可以闪回(flashback),truncate不可以
- delete不会释放空间,truncate会
- delte会产生碎片,truncate不会
- 回滚适用于DML,闪回操作一般对应于DDL
oracle的事务
在数据库中事务是工作的逻辑单元,一个事务由一个或多个完成一组的相关行为的sql语句组成,通过事务机制确保这一组sql语句的操作要么都成功执行,要么都不执行。
比如说转账应该执行下面两条语句。如果刚刚执行完第一条就断电了,那么第一条的操作会回滚。
update set money=monye-100 where id=1;
update set money=monye+100 where id=2;
事务的四大特性(ACID)
- 原子性(Atomictiy):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
- 一致性(Consisteny):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
- 隔离性(isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
- 持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
- 脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下:
update account set money=money+100 where name='B';(此时A通知B)
update account set money=money-100 where name='A';
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。
-
不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了…… -
虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
隔离级别 | 描述 |
---|---|
Serializable(串行化) | 可避免脏读、不可重复读、幻读的发生。 |
Repeatable read(可重复读) | 可避免脏读、不可重复读的发生。 |
Read committed(读已提交) | 可避免脏读的发生。 |
Read uncommitted(读未提交) | 最低级别,任何情况都无法保证。 |
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)·使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。
MySQL数据库默认的隔离级别为Repeatable read(可重复读)。Oracle的默认隔离级别是Read committed(读已提交);
commit事务提交
语法:
commit;
它执行的时候,你不会有什么感觉。commit在数据库编程的时候很常用,当你执行DML操作时,数据库并不会立刻修改表中数据,这时你需要commit,数据库中的数据就立刻修改了,如果在commit之前,就算你把整个表中数据都删了,如果rollback的话,数据依然能够还原。说到这里,你或许感觉commit没什么用,其实不然。当你同时执行两条或两条以上的sql语句时,问题就出现了。举一个例子,你去银行转账,你转的时候银行的数据库会update你银行账户里面的数据,同时对另一个人得账户也进行update操作。这两个程序都必须全部正确执行,才能commit,否则rollback。如果只是完成一条,要么你郁闷,要么银行郁闷,第一种情况是,你的账户的钱没少,转账人得账户上的钱多了,银行郁闷了。第二种情况你的银行账户的钱少了,他的却没多,你就郁闷了。
简单来说就是,insert delete update的时候记得commit;
。
表空间
表空间是oracle数据库的逻辑划分,一个表空间只能属于一个数据库。所有的数据库对象都存放在指定的表空间中。但主要存放的是表,所以称作表空间。
- 一个表空间可以与多个数据文件关联
- 一个数据库可以建立多个表空间
- 一个表空间可以建立多个用户
- 一个用户可以建立多个表
行地址
oracle数据库的表中的每一行数据都有一个唯一的标识符,或者称为rowid,在oracle内部通常就是使用它来访问数据的。rowid需要10个字节的存储空间,并用18个字符来显示。该值表明了该行在oracle数据库中的物理具体位置。可以在一个查询中使用rowid来表明查询结果中包含该值。
select rowid,deptno,ename,sal from emp;
select * from emp where rowid='AAAR3sAAEAAAACXAAJ';
创建表
创建表语法:CREATE TABLE[schema] table (column datatype [DEFAULT expr] [,....]);
数据类型 | 描述 |
---|---|
Varchar2 | 可变字符串数据 |
Char(size) | 定长字符串数据 |
Number(p,s) | 可变长数据,p表示有效数字的总位数,s表示小数位数。 |
Date | 日期型数据 |
Long | 可变长字符数据 |
Blob | 二进制数据 |
- 复制表
使用子查询创建表:
create table t
as
子查询语句;
下面的1=1代表复制表的结构于数据。1=2代表只复制表的结构,不复制数据。因为1=1是true,所以根据拼接后的查询语句能查出所有的数据,而1=2是false,查询之后查不出数据,因此只复制了表的结构。
create table emp10 as select * from emp where 1=1;
create table emp20 as select * from emp where 1=2;
- 保存30号部门员工
create table emp30
as
select * from emp where deptno=30;
- 创建表,属性tid类型为number,属性tname,类型为varchar2(20)
create table test1(tid number,tname varchar2(20));
desc test1;
增加/修改/重命名/删除列
增加列
alter table test1 add photo blob;
desc test1;
修改列
alter table test1 modify tname varchar2(40);
desc test1;
重命名列
alter table test1 rename column tname to username;
desc test1;
删除列
alter table test1 drop column photo;
desc test1;
重命名/删除表
重命名
rename test1 to test2;
desc test2;
删除表
drop table test2;
drop table emp20;
查看/清空回收站
查看回收站
show recyclebin;
清空回收站
purge recyclebin;
闪回删除(从回收站还原)
flashback table emp20 to before drop;
SQL优化原则
- 尽量使用多表查询
例子:
set timing on;
select e.* from emp e,dept d where e.deptno=d.deptno and d.dname='SALES';
select * from emp where deptno=(select deptno from dept where dname='SALES');
- 尽量不要使用集合运算
例子:
set timing on;
select deptno,job,sum(sal) from emp group by rollup(deptno,job);
select deptno,job,sum(sal) from emp group by deptno,job
union
select deptno,to_char(null),sum(sal) from emp group by deptno
union
select to_number(null),to_char(null),sum(sal) from emp;
课后任务
使用scott用户完成以下练习
- 显示部门号为10的雇员姓名,工资及所在部门的名字
select ename,sal,dname
from emp e,dept d
where e.deptno=d.deptno and d.deptno=10;
- 显示雇员姓名,工资和工资级别
select ename,sal,grade
from emp,salgrade
where sal between losal and hisal;
- 显示员工姓名,工资,所在部门名并按部门名排序
select ename,sal,dname
from emp e,dept d
where e.deptno=d.deptno order by dname;
- 显示FORD的上级
select a.ename,b.ename as "上级"
from emp a,emp b
where a.mgr=b.empno and a.ename='FORD';
- 显示和SMITH同一部门的所有员工
select *
from emp
where deptno=(select deptno from emp where ename='SMITH');
- 查询和10号部门工作相同的员工
select *
from emp
where job in(select job from emp where deptno=10);
- 查询比30号部门所有员工工资都高的员工信息
select *
from emp
where sal>all(select sal from emp where deptno=30);
- 查询比30号部门任意一个员工工资高的员工信息
select *
from emp
where sal>any(select sal from emp where deptno=30);
- 查询和SMITH部门和岗位都相同的员工
select *
from emp
where (deptno,job) in (select deptno,job from emp where ename='SMITH') and ename!='SMITH';
- 显示高于自己部门平均工资的员工信息
select a.ename,a.sal,b.avgsal,a.deptno
from emp a,(select deptno,avg(sal) as avgsal from emp group by deptno) b
where a.deptno=b.deptno and a.sal>b.avgsal
order by a.deptno desc;
使用hr用户完成以下练习
- 查询和Zlotkey相同部门的员工姓名和雇用日期
select first_name,last_name,hire_date
from employees
where department_id=(select department_id from employees where last_name='Zlotkey');
- 查询工资比公司平均工资高的员工的员工号,姓名和工资
select employee_id,first_name,last_name,hire_date
from employees
where salary>(select avg(salary) from employees);
- 查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资
select employee_id,first_name,last_name,salary
from employees a,(select department_id,avg(salary) avgsal from employees group by department_id) b
where a.department_id=b.department_id and a.salary>b.avgsal;
- 查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
select employee_id,first_name,last_name
from employees
where department_id in (select distinct department_id from employees where first_name||last_name like '%u%');
- 查询在部门的location_id为1700的部门工作的员工的员工号
select employee_id
from employees
where department_id in (select distinct department_id from departments where location_id=1700);
- 查询管理者是King的员工姓名和工资
select first_name,last_name,salary
from employees
where manager_id in (select employee_id from employees where last_name='King');