sql练习题

employees表

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

题目1:查找最晚入职员工的所有信息:

select * from employees
where hire_date = (select max(hire_date) from employees);

考点:子查询、函数。

题目2:查找入职员工时间排名倒数第三的员工所有信息

select * from employees
order by hire_date desc
limit 2,1;

考点:分页查询,limit offset,size;offset要显示条目的起始索引(起始索引从0开始),size要显示的条目个数。故查询倒数第三员工信息即为先将入职时间进行降序desc然后从第3个索引开始取一条数据。

题目3:查找各个部门当前(to_date=‘9999-01-01’)领导当前薪水详情以及其对应部门编号dept_no

//原始表
CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select s.*,d.dept_no
from salaries as s
inner join dept_manager as d
on s.emp_no = d.emp_no
where s.to_date = '9999-01-01' and d.to_date = '9999-01-01';

考点:连接查询,sql99语法。题目要求是薪水情况以及部门编号,再结合输出情况dept_no 被放到了最后一列,可见是主表是“salaries”

题目4:查找所有已经分配部门的员工的last_name和first_name

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select e.last_name,e.first_name,d.dept_no
from employees e
inner join dept_emp d
on e.emp_no = d.emp_no;

考点:连接查询

题目5:查找所有员工的last_name和first_name以及对应部门编号dept_no,也包括展示没有分配具体部门的员工(表和上题一致)

select e.last_name, e.first_name, d.dept_no 
from employees e 
left join dept_emp d
on e.emp_no = d.emp_no

考点:主表员工表,字段emp_no,birth_date,first_name,last_name,gender,hire_date,从表部门表,字段emp_no,dept_no,from_date,to_date

  • inner join即内连接。两边表同时有对应的数据,即任何一边缺失数据就不显示。
  • left join左外连接。会读取左边数据表的全部数据,即便右边表无对应数据。
  • right join右外连接。会读取右边数据表的全部数据,即便左边表无对应数据。

题目6:查找所有员工入职时候的薪水情况,给出emp_no以及salary, 并按照emp_no进行逆序

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
//方法1
select e.emp_no,s.salary
from employees e
inner join salaries s
on s.emp_no = e.emp_no and s.from_date = e.hire_date
order by e.emp_no desc;

//方法2
SELECT e.emp_no, s.salary FROM employees AS e, salaries AS s
WHERE e.emp_no = s.emp_no AND e.hire_date = s.from_date
ORDER BY e.emp_no DESC

//方法3
select emp_no,salary from salaries
group by emp_no having min(from_date)
order by emp_no DESC

解析:

  • salaries.emp_no 不唯一(因为号码为 emp_no 的员工会有多次涨薪的可能,所以在 salaries 中对应的记录不止一条),employees.emp_no 唯一,即 salaries 的数据会多于 employees,因此需先找到 employees.emp_no 在 salaries 表中对应的记录salaries.emp_no,则有限制条件 e.emp_no = s.emp_no
  • 根据题意注意到 salaries.from_date 和 employees.hire_date 的值应该要相等,因此有限制条件 e.hire_date = s.from_date
  • 根据题意要按照 emp_no 值逆序排列,因此最后要加上 ORDER BY e.emp_no DESC

题目7:查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t

//原始表
CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select emp_no,count(emp_no) t
from salaries
group by emp_no
having t >15;

考点:

  • 用COUNT()函数和GROUP BY语句可以统计同一emp_no值的记录条数

  • 根据题意,输出的涨幅次数为t,故用AS语句将COUNT(emp_no)的值转换为t

  • 由于COUNT()函数不可用于WHERE语句中,故使用HAVING语句来限定t>15的条件。添加复杂的筛选条件(分组函数)一般使用having在group by后面,即分组后筛选。where放在group by前,即简单筛选,分组前筛选。

  • 最后存在一个理解误区,涨幅超过15次,salaries中相应的记录数应该超过16(从第2条记录开始算作第1次涨幅),不过题目为了简单起见,将第1条记录当作第1次涨幅,所以令t>15即可

题目8:找出所有员工当前(to_date=‘9999-01-01’)具体的薪水salary情况,对于相同的薪水只显示一次,并按照逆序显示

//原始表
CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select distinct(salary)
from salaries
where to_date = '9999-01-01'
order by salary desc;

考点:DISTINCT函数去重

题目9:获取所有部门当前manager的当前薪水情况,给出dept_no, emp_no以及salary,当前表示to_date=‘9999-01-01’

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select d.dept_no,d.emp_no,s.salary
from dept_manager d
inner join salaries s
on d.emp_no = s.emp_no and d.to_date = s.to_date
where d.to_date = '9999-01-01';

考点:因为同一emp_no在salaries表中对应多条涨薪记录,而当s.to_date = '9999-01-01’时是该员工当前的薪水记录

题目10:获取所有非manager的员工emp_no

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
//方法1:not in
select emp_no
from employees
where emp_no not in (select emp_no from dept_manager);

//方法2
select e.emp_no
from employees e left join dept_manager d
on e.emp_no = d.emp_no
where d.emp_no is null;

解析:

  • 方法1是使用NOT IN选出在employees但不在dept_manager中的emp_no记录
  • 方法2是先使用LEFT JOIN连接两张表,再从此表中选出dept_no值为NULL对应的emp_no记录

题目11:获取所有员工当前的manager,如果当前的manager是自己的话结果不显示,当前表示to_date=‘9999-01-01’。结果第一列给出当前员工的emp_no,第二列给出其manager对应的manager_no。

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));
select e.emp_no,m.emp_no as manager_no
from dept_emp e
inner join dept_manager m
on e.dept_no = m.dept_no
where e.to_date='9999-01-01'
and m.to_date='9999-01-01'
and e.emp_no <> m.emp_no;

解析:

  • 用 INNER JOIN 连接两张表,因为要输出自己的经理,得知自己与经理的部门要相同,故有限制条件 de.dept_no = dm.dept_no
  • 再用 WHERE 限制当前员工与当前经理的条件,即 dm.to_date 等于 ‘9999-01-01’ 、de.to_date 等于 ‘9999-01-01’ 、 de.emp_no 不等于 dm.emp_no

题目12:获取所有部门中当前员工薪水最高的相关信息,给出dept_no, emp_no以及其对应的salary

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select d.dept_no,d.emp_no,max(s.salary) salary
from dept_emp d
inner join salaries s
on d.emp_no = s.emp_no
where d.to_date = '9999-01-01'
and s.to_date = '9999-01-01'
group by d.dept_no;

考点:

  • 先用INNER JOIN连接两张表,限制条件是两张表的emp_no相同,即d.emp_no = s.emp_no;
  • 选取每个员工当前的工资水平,用d.to_date = ‘9999-01-01’ AND s.to_date = '9999-01-01’作条件限制,因为此表中每条最新记录的 to_date 都用 9999-01-01 表示;
  • 用GROUP BY d.dept_no将每个部门分为一组,用MAX()函数选取每组中工资最高者记住这种情况

题目13:从titles表获取按照title进行分组,每组个数大于等于2,给出title以及对应的数目t。

CREATE TABLE IF NOT EXISTS "titles" (
`emp_no` int(11) NOT NULL,
`title` varchar(50) NOT NULL,
`from_date` date NOT NULL,
`to_date` date DEFAULT NULL);
select title,count(title) t
from titles
group by title
having count(title) > 1;

考点:having复杂分组后筛选

题目14:从titles表获取按照title进行分组,每组个数大于等于2,给出title以及对应的数目t。注意对于重复的emp_no进行忽略。

CREATE TABLE IF NOT EXISTS "titles" (
`emp_no` int(11) NOT NULL,
`title` varchar(50) NOT NULL,
`from_date` date NOT NULL,
`to_date` date DEFAULT NULL);
select title,count(distinct(emp_no)) t
from titles
group by title
having count(title) > 1;

考点:理解题意,分组后再次去重,计算每组去重后的个数

题目15:查找employees表所有emp_no为奇数,且last_name不为Mary的员工信息,并按照hire_date逆序排列

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select emp_no,birth_date,first_name,last_name,gender,hire_date
from employees
where last_name <> "Mary"
and emp_no % 2 == 1
order by hire_date desc;

考点:添加多个筛选条件and连接

题目16:统计出当前各个title类型对应的员工当前薪水对应的平均工资。结果给出title以及平均工资avg。

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));

CREATE TABLE IF NOT EXISTS "titles" (
`emp_no` int(11) NOT NULL,
`title` varchar(50) NOT NULL,
`from_date` date NOT NULL,
`to_date` date DEFAULT NULL);
select title,avg(s.salary) avg
from titles t
inner join salaries s
on t.emp_no = s.emp_no
where t.to_date = '9999-01-01'
and s.to_date = '9999-01-01'
group by title;

考点:

  • “Where” 是一个约束声明,使用Where来约束来之数据库的数据,Where是在结果返回之前起作用的,且Where中不能使用聚合函数。
  • “Having”是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作,在Having中可以使用聚合函数。

题目17:获取当前(to_date=‘9999-01-01’)薪水第二多的员工的emp_no以及其对应的薪水salary

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select emp_no,salary
from salaries
where to_date = '9999-01-01'
order by salary desc
limit 1,1

//更好的答案
select emp_no, salary from salaries
where to_date = '9999-01-01' 
and salary = (select distinct salary from salaries order by salary desc limit 1,1)

考点:钱最多的人可能存在多个

题目18:查找当前薪水(to_date=‘9999-01-01’)排名第二多的员工编号emp_no、薪水salary、last_name以及first_name,不准使用order by

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select s.emp_no,max(s.salary),e.last_name,e.first_name
from salaries s
inner join employees e
on s.emp_no = e.emp_no
where to_date = '9999-01-01'
and s.salary not in (select max(salary) from salaries where to_date = '9999-01-01');

解析:

  • 本题做法很多,主要思想为多层SELECT嵌套与MAX()函数结合
  • 先利用MAX()函数找出salaries中当前薪水最高者,即SELECT MAX(salary) FROM salaries WHERE to_date = ‘9999-01-01’
  • 再利用INNER JOIN连接employees与salaries表,限定条件为【同一员工】e.emp_no = s.emp_no、【当前】s.to_date = '9999-01-01’与【非薪水最高】s.salary NOT IN (SELECT MAX(salary) FROM salaries WHERE to_date = ‘9999-01-01’)
  • 在以上限制条件下找薪水最高者,即为所有员工薪水的次高者

题目19:查找所有员工的last_name和first_name以及对应的dept_name,也包括暂时没有分配部门的员工

CREATE TABLE `departments` (
`dept_no` char(4) NOT NULL,
`dept_name` varchar(40) NOT NULL,
PRIMARY KEY (`dept_no`));
CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select e.last_name,e.first_name,dp.dept_name
from employees e
left join dept_emp de on e.emp_no = de.emp_no
left join departments dp on de.dept_no = dp.dept_no;

解析:本题思路为运用两次LEFT JOIN连接嵌套

  • 第一次LEFT JOIN连接employees表与dept_emp表,得到所有员工的last_name和first_name以及对应的dept_no,也包括暂时没有分配部门的员工
  • 第二次LEFT JOIN连接上表与departments表,即连接dept_no与dept_name,得到所有员工的last_name和first_name以及对应的dept_name,也包括暂时没有分配部门的员工

题目20:查找员工编号emp_no为10001其自入职以来的薪水salary涨幅值growth

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select (max(salary)-min(salary)) growth
from salaries
where emp_no = 10001;

//方法2
SELECT (
(SELECT salary FROM salaries WHERE emp_no = 10001 ORDER BY to_date DESC LIMIT 1)
- (SELECT salary FROM salaries WHERE emp_no = 10001 ORDER BY to_date LIMIT 1)
) AS growth;

考点:本题的另一种解法也能通过测试,但实际上不严谨,只有在员工最后一条工资记录为最大值时成立,如果最后一次的工资调整为降薪,则此思路通不过。

题目21:查找所有员工自入职以来的薪水涨幅情况,给出员工编号emp_no以及其对应的薪水涨幅growth,并按照growth进行升序

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select sCurrent.emp_no, (sCurrent.salary-sStart.salary) as growth
from (select s.emp_no, s.salary 
      from employees e 
      left join salaries s 
      on e.emp_no = s.emp_no 
      where s.to_date = '9999-01-01') 
as sCurrent
inner join (select s.emp_no, s.salary 
            from employees e 
            left join salaries s 
            on e.emp_no = s.emp_no 
            where s.from_date = e.hire_date) 
as sStart
on sCurrent.emp_no = sStart.emp_no
order by growth;

解析:

  • 先得到一个当前所有员工的薪水表(employees join salaries on employees.emp_no = salaries.emp_no and salaries.to_date=‘9999-01-01’)和一个入职以来员工的薪水表(employees join salaries on employees.emp_no = salaires.emp_no and employees.hire_date= salaries.from_date)
  • 然后连接这两个表

题目22:统计各个部门对应员工涨幅的次数总和,给出部门编码dept_no、部门名称dept_name以及次数sum

CREATE TABLE `departments` (
`dept_no` char(4) NOT NULL,
`dept_name` varchar(40) NOT NULL,
PRIMARY KEY (`dept_no`));

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select dp.dept_no,dp.dept_name,count(sl.salary) sum
from departments dp
inner join dept_emp de
on dp.dept_no = de.dept_no
inner join salaries sl
on de.emp_no = sl.emp_no
group by de.dept_no;

解析:

  • 用INNER JOIN连接dept_emp表和salaries表,并以dept_emp.no分组,统计每个部门所有员工工资的记录总数
  • 再将上表用INNER JOIN连接departments表,限制条件为两表的dept_no相等,找到dept_no与dept_name的对应关系,最后依次输出dept_no、dept_name、sum

题目23:对所有员工的当前(to_date=‘9999-01-01’)薪水按照salary进行按照1-N的排名,相同salary并列且按照emp_no升序排列

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select s1.emp_no,s1.salary,count(distinct s2.salary) rank
from salaries s1, salaries s2
where s1.salary <= s2.salary
and s1.to_date = '9999-01-01'
and s2.to_date = '9999-01-01'
group by s1.emp_no
order by rank;

解析:

  • 从两张相同的salaries表(分别为s1与s2)进行对比分析,先将两表限定条件设为to_date = ‘9999-01-01’,挑选出当前所有员工的薪水情况。
  • 本题的精髓在于 s1.salary <= s2.salary,意思是在输出s1.salary的情况下,有多少个s2.salary大于等于s1.salary,比如当s1.salary=94409时,有3个s2.salary(分别为94692,94409,94409)大于等于它,但由于94409重复,利用COUNT(DISTINCT s2.salary)去重可得工资为94409的rank等于2。其余排名以此类推。
  • 千万不要忘了GROUP BY s1.emp_no,否则输出的记录只有一条(可能是第一条或者最后一条,根据不同的数据库而定),因为用了合计函数COUNT()
  • 最后先以 s1.salary 逆序排列,再以 s1.emp_no 顺序排列输出结果
  • 其实就是emp_no=001号员工下,符合筛选条件的s2.salary有很多种可能;emp_no=002号员工下,符合筛选条件的s2.salary也有很多种可能,以此类推,故必须将s1.emp_no分组,以确保对每个s1.emp_no都会有一个rank输出值

题目24:获取所有非manager员工当前的薪水情况,给出dept_no、emp_no以及salary ,当前表示to_date=‘9999-01-01’

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));//部门员工表

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));//部门经理表

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));//员工表

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));//薪资表
//法1:不使用employees表
select de.dept_no, s.emp_no, s.salary 
from dept_emp de
inner join salaries s 
on s.emp_no = de.emp_no 
and s.to_date = '9999-01-01'
where de.emp_no not in 
(select emp_no from dept_manager where to_date = '9999-01-01');

//法2
select de.dept_no, s.emp_no, s.salary 
from (employees e inner join salaries s on s.emp_no = e.emp_no 
      and s.to_date = '9999-01-01')
inner join dept_emp de on e.emp_no = de.emp_no
where de.emp_no not in
(select emp_no from dept_manager where to_date = '9999-01-01');

解析:

  • 法1思路:不使用employees表,将部门员工表与薪资表内连接,筛选部门员工号不在部门经理表员工号里
  • 法2思路:先用INNER JOIN连接employees和salaries,找出当前所有员工的工资情况;再用INNER JOIN连接dept_emp表,找到所有员工所在的部门;最后用限制条件de.emp_no NOT IN (SELECT emp_no FROM dept_manager WHERE to_date = ‘9999-01-01’)选出当前所有非manager员工,再依次输出dept_no、emp_no、salary

题目25:获取员工其当前的薪水比其manager当前薪水还高的相关信息,当前表示to_date=‘9999-01-01’,

结果第一列给出员工的emp_no,
第二列给出其manager的manager_no,
第三列给出该员工当前的薪水emp_salary,
第四列给该员工对应的manager当前的薪水manager_salary

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select sem.emp_no emp_no, sdm.emp_no manager_no, sem.salary emp_salary, sdm.salary manager_salary
from (select s.salary, s.emp_no, de.dept_no 
      from salaries s 
      inner join dept_emp de
      on s.emp_no = de.emp_no 
      and s.to_date = '9999-01-01' ) sem,
      (select s.salary, s.emp_no, dm.dept_no
      from salaries s
      inner join dept_manager dm
      on s.emp_no = dm.emp_no
      and s.to_date = '9999-01-01' ) sdm
where sem.dept_no = sdm.dept_no
and sem.salary > sdm.salary;

解析:本题主要思想是创建两张表(一张记录当前所有员工的工资,另一张只记录部门经理的工资)进行比较,具体思路如下:

  • 先用INNER JOIN连接salaries和demp_emp,建立当前所有员工的工资记录sem
  • 再用INNER JOIN连接salaries和demp_manager,建立当前所有员工的工资记录sdm
  • 最后用限制条件sem.dept_no = sdm.dept_no AND sem.salary > sdm.salary找出同一部门中工资比经理高的员工,并根据题意依次输出emp_no、manager_no、emp_salary、manager_salary

题目26:汇总各个部门当前员工的title类型的分配数目,结果给出部门编号dept_no、dept_name、其当前员工所有的title以及该类型title对应的数目count

CREATE TABLE `departments` (
`dept_no` char(4) NOT NULL,
`dept_name` varchar(40) NOT NULL,
PRIMARY KEY (`dept_no`));

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE IF NOT EXISTS `titles` (
`emp_no` int(11) NOT NULL,
`title` varchar(50) NOT NULL,
`from_date` date NOT NULL,
`to_date` date DEFAULT NULL);
select de.dept_no, dp.dept_name, t.title, count(t.title) count
from titles t 
inner join dept_emp de
on t.emp_no = de.emp_no 
and de.to_date = '9999-01-01' 
and t.to_date = '9999-01-01'
inner join departments dp
on de.dept_no = dp.dept_no
group by de.dept_no, t.title;

解析:本题的关键在于用 GROUP BY 同时对 de.dept_no 和 t.title 进行分组

  • 先用 INNER JOIN 连接 dept_emp 与 salaries,根据测试数据添加限定条件 de.to_date = ‘9999-01-01’ AND t.to_date = ‘9999-01-01’,即当前员工的当前头衔
  • 再用 INNER JOIN 连接departments,限定条件为 de.dept_no = dp.dept_no,即部门编号相同
  • 最后用 GROUP BY 同时对 de.dept_no 和 t.title 进行分组,用 COUNT(t.title) 统计相同部门下相同头衔的员工个数

题目27:给出每个员工每年薪水涨幅超过5000的员工编号emp_no、薪水变更开始日期from_date以及薪水涨幅值salary_growth,并按照salary_growth逆序排列。

提示:在sqlite中获取datetime时间对应的年份函数为strftime(’%Y’, to_date)

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select s2.emp_no, s2.from_date, (s2.salary - s1.salary) as salary_growth
from salaries as s1, salaries as s2
where s1.emp_no = s2.emp_no
and s2.salary - s1.salary > 5000
and strftime('%Y',s2.to_date) - strftime('%Y',s1.to_date) = 1
order by salary_growth desc;

解析:

  • 假设s1是涨薪水前的表,s2是涨薪水后的表,因为每个员工涨薪水的时间不全固定,有可能一年涨两次,有可能两年涨一次,所以每年薪水的涨幅,应该理解为两条薪水记录的from_date相同或to_date相同。
  • 找到s1与s2符合要求的记录后,用s2的薪水减去s1的薪水,用salary_growth表示,加上限定条件 s1.emp_no = s2.emp_no AND salary_growth > 5000,即同一员工每年涨幅超过5000的记录
  • 最后依次输出emp_no、from_date、salary_growth,并以salary_growth逆序排列

题目28:查找描述信息中包括robot的电影对应的分类名称以及电影数目,而且还需要该分类对应电影数量>=5部

CREATE TABLE IF NOT EXISTS film (
film_id smallint(5)  NOT NULL DEFAULT '0',
title varchar(255) NOT NULL,
description text,
PRIMARY KEY (film_id));

CREATE TABLE category  (
category_id  tinyint(3)  NOT NULL ,
name  varchar(25) NOT NULL, `last_update` timestamp,
PRIMARY KEY ( category_id ));

CREATE TABLE film_category  (
film_id  smallint(5)  NOT NULL,
category_id  tinyint(3)  NOT NULL, `last_update` timestamp);
select c.name,count(fcc.film_id)as num
from film_category fc
inner join category c 
on c.category_id = fc.category_id
inner join (select * 
            from film f 
            where f.description like '%robot%') as f //film表但字段description含有robot
on f.film_id = fc.film_id
inner join (select *,count (fc.film_id)as num 
            from film_category fc 
            group by category_id 
            having num >= 5)as fcc //film_category表,筛选后字段category_id电影数量大于等于5个
on fc.category_id = fcc.category_id;

解析:

  • 找到对应电影数量>=5的所有分类,建立成虚表cc:(select category_id, count(film_id) as category_num from film_category group by category_id having count(film_id)>=5) as cc
  • 设定限制条件 f.description like ‘%robot%’
  • 在表cc、f、fc、c中查找包括robot的电影对应的分类名称和对应的电影数目。

题目29:使用join查询方式找出没有分类的电影id以及名称

select f.film_id,f.title
from film f
left join film_category fc
on f.film_id = fc.film_id
left join category c
on c.category_id = fc.category_id
where fc.category_id is null;

解析:

  • 用 LEFT JOIN 连接 film 和 film_category,限定条件为 f.film_id = fc.film_id,即连接电影 id 和电影分类 id,如果电影没有分类,则电影分类 id 显示 null
  • 再用 WHERE 来限定条件 fc.category_id IS NULL 选出没分类的电影

题目30:使用子查询的方式找出属于Action分类的所有电影对应的title,description

select title, description
from film
where film_id in (select film_id 
                  from film_category 
                  where category_id = (select category_id 
                                       from category 
                                       where name = 'Action')
                 );

解析:
title和description都是在film表中,即查询film_id是Action类的film_id,而Action类的film_id需要筛选类film_category中category_id是Action情形。

题目31:获取select * from employees对应的执行计划

explain select * from employees;

解析:
在SQLite数据库中,可以用 “EXPLAIN” 关键字或 “EXPLAIN QUERY PLAN” 短语,用于描述表的细节,具体说明与用法可参考:
http://www.runoob.com/sqlite/sqlite-explain.html
http://www.sqlite.org/lang_explain.html

题目32:将employees表的所有员工的last_name和first_name拼接起来作为Name,中间以一个空格区分

CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
//我的做法,未经测试MySQL
select concat(upper(substr(last_name,1,1)),
              lower(substr(last_name,2)),
             ' ',
             upper(substr(first_name,1,1)),
              lower(substr(first_name,2))
             ) Name
from employees;

//SQLite
select last_name||" "||first_name as Name 
from employees

解析:
不同数据库连接字符串的方法不完全相同,MySQL、SQL Server、Oracle等数据库支持CONCAT方法,而本题所用的SQLite数据库只支持用连接符号"||"来连接字符串

题目33:创建一个actor表,包含如下列信息

列表 类型 是否为NULL 含义
actor_id smallint(5) not null 主键id
first_name varchar(45) not null 名字
last_name varchar(45) not null 姓氏
last_update timestamp not null 最后更新时间,默认是系统的当前时间
creat table actor(
    actor_id smallint(5) not null primary key,
    first_name varchar(45) not null,
    last_name varchar(45) not null,
    last_update timestamp not null default (datetime('now','localtime'))
);

解析:
OJ系统使用的是sqlite3数据库,不是mysql,所以对于默认时间的写法和mysql不同。大家不必过于纠结。

题目34:对于表actor批量插入如下数据

actor_id first_name last_name last_update
1 PENELOPE GUINESS 2006-02-15 12:34:33
2 NICK WAHLBERG 2006-02-15 12:34:33
insert into actor values
(1,'PENELOPE','GUINESS','2006-02-15 12:34:33'),
(2,'NICK','WAHLBERG','2006-02-15 12:34:33');

题目35:对于表actor批量插入如下数据,如果数据已经存在,请忽略,不使用replace操作

actor_id first_name last_name last_update
3 ‘ED’ ‘CHASE’ ‘2006-02-15 12:34:33’
//mysql,那么把or去掉
insert or ignore into actor
values(3,'ED','CHASE','2006-02-15 12:34:33');

题目36:创建一个actor_name表,将actor表中的所有first_name以及last_name导入改表。 actor_name表结构如下:

列表 类型 是否为null值 含义
first_name varchar(45) not null 名字
last_name varchar(45) not null 姓氏’
create table actor_name(
    first_name varchar(45) not null,
    last_name varchar(45) not null 
);
insert into actor_name select first_name,last_name from actor;

题目37:针对如下表actor结构创建索引:对first_name创建唯一索引uniq_idx_firstname,对last_name创建普通索引idx_lastname

create unique index uniq_idx_firstname on actor(first_name);
create index idx_lastname on actor(last_name);

题目38:针对actor表创建视图actor_name_view,只包含first_name以及last_name两列,并对这两列重新命名,first_name为first_name_v,last_name修改为last_name_v

create view actor_name_view (first_name_v,last_name_v) as
select first_name,last_name from actor;

题目39:针对salaries表emp_no字段创建索引idx_emp_no,查询emp_no为10005, 使用强制索引。

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
create index idx_emp_no on salaries(emp_no);
//SQLite
select * from salaries
indexed by idx_emp_no
where emp_no = '10005';

//mysql
select * from salaries force index idx_emp_no where emp_no = 10005;

解析:

  • SQLite中,使用 INDEXED BY 语句进行强制索引查询,可参考:
    http://www.runoob.com/sqlite/sqlite-indexed-by.html
  • MySQL中,使用 FORCE INDEX 语句进行强制索引查询,可参考:
    http://www.jb51.net/article/49807.htm

题目40:actor表,现在在last_update后面新增加一列名字为create_date, 类型为datetime, NOT NULL,默认值为’0000 00:00:00’

alter table actor add column create_date datetime not null default '0000-00-00 00:00:00';

题目41:构造一个触发器audit_log,在employees_test表中插入一条数据的时候,触发插入相关的数据到audit中。

CREATE TABLE employees_test(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);

CREATE TABLE audit(
EMP_no INT NOT NULL,
NAME TEXT NOT NULL
);
create trigger audit_log after insert on employees_test
begin
     insert into audit values(new.id,new.name);
end;

解析:构造触发器时注意以下几点:

  • 用 CREATE TRIGGER 语句构造触发器,用 BEFORE或AFTER 来指定在执行后面的SQL语句之前或之后来触发TRIGGER
  • 触发器执行的内容写出 BEGIN与END 之间
  • 可以使用 NEW与OLD 关键字访问触发后或触发前的employees_test表单记录
  • https://www.runoob.com/sqlite/sqlite-trigger.html

题目42:删除emp_no重复的记录,只保留最小的id对应的记录。

CREATE TABLE IF NOT EXISTS titles_test (
id int(11) not null primary key,
emp_no int(11) NOT NULL,
title varchar(50) NOT NULL,
from_date date NOT NULL,
to_date date DEFAULT NULL);

insert into titles_test values ('1', '10001', 'Senior Engineer', '1986-06-26', '9999-01-01'),
('2', '10002', 'Staff', '1996-08-03', '9999-01-01'),
('3', '10003', 'Senior Engineer', '1995-12-03', '9999-01-01'),
('4', '10004', 'Senior Engineer', '1995-12-03', '9999-01-01'),
('5', '10001', 'Senior Engineer', '1986-06-26', '9999-01-01'),
('6', '10002', 'Staff', '1996-08-03', '9999-01-01'),
('7', '10003', 'Senior Engineer', '1995-12-03', '9999-01-01');
delete from titles_test
where id not in (select min(id) from titles_test);

题目43:将所有to_date为9999-01-01的全部更新为NULL,且 from_date更新为2001-01-01。

update titles_test 
set to_date=null,from_date='2001-01-01'
where to_date='9999-01-01';

题目44:将id=5以及emp_no=10001的行数据替换成id=5以及emp_no=10005,其他数据保持不变,使用replace实现。

//方法1
replace into titles_test values(5, 10005, 'Senior Engineer', '1986-06-26', '9999-01-01');

//方法2
replace into titles_test
select 5, 10005, title, from_date, to_date
from titles_test
where id = 5;

解析:本题运用 REPLACE 有两种解法

  • 方法一:全字段更新替换。由于 REPLACE 的新记录中 id=5,与表中的主键 id=5 冲突,故会替换掉表中 id=5 的记录,否则会插入一条新记录(例如新插入的记录 id = 10)。并且要将所有字段的值写出,否则将置为空。
  • 方法二:运用REPLACE(X,Y,Z)函数。其中X是要处理的字符串,Y是X中将要被替换的字符串,Z是用来替换Y的字符串,最终返回替换后的字符串。以下语句用 UPDATE和REPLACE 配合完成,用REPLACE函数替换后的新值复制给 id=5 的 emp_no。REPLACE的参数为整型时也可通过。UPDATE titles_test SET emp_no = REPLACE(emp_no,10001,10005) WHERE id = 5

题目45:将titles_test表名修改为titles_2017。

alter table titles_test rename to titles_2017;

题目46:在audit表上创建外键约束,其emp_no对应employees_test表的主键id。

CREATE TABLE employees_test(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);

CREATE TABLE audit(
EMP_no INT NOT NULL,
create_date datetime NOT NULL
);
//OJ 系统
drop table audit;
create table audit(
    EMP_no int not null,
    create_date datetime not null,
    foreign key(EMP_no) references employees_test(ID));

题目47:如何获取emp_v和employees有相同的数据?

create view emp_v as select * from employees where emp_no >10005;

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select em.* from employees as em, emp_v as ev where em.emp_no = ev.emp_no;

题目48:将所有获取奖金的员工当前的薪水增加10%。

create table emp_bonus(
emp_no int not null,
recevied datetime not null,
btype smallint not null);

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL, PRIMARY KEY (`emp_no`,`from_date`));
update salaries
set salary = salary*1.1
where to_date = '9999-01-01'
and emp_no in (select emp_no from emp_bonus);

题目49:针对库中的所有表生成select count(*)对应的SQL语句

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

create table emp_bonus(
emp_no int not null,
recevied datetime not null,
btype smallint not null);

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));

输出:

cnts
select count(*) from employees;
select count(*) from departments;
。。
。。
。。
select "select count(*) from " || name || ";" as cnts
from sqlite_master 
where type = 'table'

//MySQL
select concat('select count(*) from', ' ', TABLE_NAME, ';') as cnts
from (select table_name from information_schema.tables 
where table_schema='shop') as hi;

解析:

  • 在 SQLite 系统表 sqlite_master 中可以获得所有表的索引,其中字段 name 是所有表的名字,而且对于自己创建的表而言,字段 type 永远是 ‘table’
  • 在 SQLite 中用 “||” 符号连接字符串

题目50:将employees表中的所有员工的last_name和first_name通过(’)连接起来。

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select last_name||"'"||first_name as name 
from employees

解析:类似题目32,在本题所用的SQLite数据库中,只支持用连接符号"||"来连接字符串,不支持用函数连接

题目51:查找字符串’10,A,B’ 中逗号’,'出现的次数cnt。

select (length('10,A,B')-length(replace('10,A,B',',',''))) as cnt

解析:

  • replace(字符串,“需要替换的子串”,“用于替换子串的字符串”)
  • 如果考虑到中文的话,其实应该用char_length()函数,只是牛客这里不支持char_length()函数,不知道是不是版本问题
  • char_length()统计的是字符长度,而length()函数统计的是字符串的字节长度

题目52:获取Employees中的first_name,查询按照first_name最后两个字母,按照升序进行排列

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select first_name
from employees
order by substr(first_name,length(first_name)-1);

解析:本题考查 substr(X,Y,Z) 或 substr(X,Y) 函数的使用。其中X是要截取的字符串。Y是字符串的起始位置(注意第一个字符的位置为1,而不为0),取值范围是±(1~length(X)),当Y等于length(X)时,则截取最后一个字符;当Y等于负整数-n时,则从倒数第n个字符处截取。Z是要截取字符串的长度,取值范围是正整数,若Z省略,则从Y处一直截取到字符串末尾;若Z大于剩下的字符串长度,也是截取到字符串末尾为止。

题目53:按照dept_no进行汇总,属于同一个部门的emp_no按照逗号进行连接,结果给出dept_no以及连接出的结果employees

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));
select dept_no,group_concat(emp_no) employees
from dept_emp
group by dept_no;

//mysql
select dept_no,group_concat(emp_no SEPARATOR ',') 
from dept_emp 
group by dept_no;

解析:

  • 本题要用到SQLite的聚合函数group_concat(X,Y),其中X是要连接的字段,Y是连接时用的符号,可省略,默认为逗号。
  • 此函数必须与 GROUP BY 配合使用。此题以 dept_no 作为分组,将每个分组中不同的emp_no用逗号连接起来(即可省略Y)。
  • group_concat()函数返回X的非null值的连接后的字符串。如果给出了参数Y,将会在每个X之间用Y作为分隔符。如果省略了Y,“,”将作为默认的分隔符。每个元素连接的顺序是随机的。

题目54:查找排除当前最大、最小salary之后的员工的平均工资avg_salary。

CREATE TABLE `salaries` ( `emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
select avg(salary) avg_salary
from salaries
where salary != (select max(salary) from salaries)
and salary != (select min(salary) from salaries)//不用not in不是字符而是数字
and to_date = '9999-01-01';

解析:

  • min()和max()都是聚合函数,是对结果集中的列进行操作而不是对单个记录进行操作(故not in不对)

题目55:分页查询employees表,每5行一页,返回第2页的数据

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select * 
from employees
limit 5,5;//要显示的页数page,每页的条目数size,limit (page-1)*size,size;

题目56:获取所有员工的emp_no、部门编号dept_no以及对应的bonus类型btype和recevied,没有分配具体的员工不显示

CREATE TABLE `dept_emp` ( 
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `dept_manager` (
`dept_no` char(4) NOT NULL,
`emp_no` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));

create table emp_bonus(
emp_no int not null,
recevied datetime not null,
btype smallint not null);

输出:

e.emp_no dept_no btype recevied
10001 d001 1 2010-01-01
10002 d001 2 2010-10-01
10003 d004 3 2011-12-03
10004 d004 1 2010-01-01
10005 d003
select de.emp_no, de.dept_no, eb.btype, eb.recevied 
from dept_emp as de 
left join emp_bonus as eb
on de.emp_no = eb.emp_no;

解析:
以上是代码,该题目中的dept_manager 表和salaries表不会用到,并且缺了一个 emp_bonus 表,emp_bonus 表可以从题库里获取奖金的员工当前工资加10%的那道题找到。

题目57:使用含有关键字exists查找未分配具体部门的员工的所有信息。

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));
select * 
from employees 
where not exists (select emp_no from dept_emp where emp_no=employees.emp_no);

//另法1
select e.*
from employees e
left join dept_emp d
on e.emp_no = d.emp_no
where d.dept_no is null;

//另法2
select * 
from employees 
where emp_no not in (select emp_no from dept_emp)

解析:本题用 EXISTS 关键字的方法如下:
意为在 employees 中挑选出令(SELECT emp_no FROM dept_emp WHERE emp_no = employees.emp_no)不成立的记录,也就是当 employees.emp_no=10011的时候。反之,把NOT去掉,则输出 employees.emp_no=10001~10010时的记录。

题目58:获取employees中的行数据,且这些行也存在于emp_v中。注意不能使用intersect关键字。

create view emp_v as select * from employees where emp_no >10005;

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

输出格式:

emp_no birth_date first_name last_name gender hire_date
10006 1953-04-20 Anneke Preusig F 1989-06-02
select e.*
from employees e
inner join emp_v v
on e.emp_no = v.emp_no;

题目59:获取有奖金的员工相关信息。

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));

CREATE TABLE `dept_emp` (
`emp_no` int(11) NOT NULL,
`dept_no` char(4) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`dept_no`));

create table emp_bonus(
emp_no int not null,
recevied datetime not null,
btype smallint not null);

CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL, PRIMARY KEY (`emp_no`,`from_date`));

给出emp_no、first_name、last_name、奖金类型btype、对应的当前薪水情况salary以及奖金金额bonus。 bonus类型btype为1其奖金为薪水salary的10%,btype为2其奖金为薪水的20%,其他类型均为薪水的30%。 当前薪水表示to_date=‘9999-01-01’

//凑巧
select e.emp_no,e.first_name,e.last_name,b.btype,s.salary,(s.salary * b.btype * 0.1) bonus
from employees e
inner join emp_bonus b
on e.emp_no = b.emp_no 
inner join salaries s
on e.emp_no = s.emp_no 
where s.to_date='9999-01-01';

//真正的case when
select e.emp_no,e.first_name,e.last_name,b.btype,s.salary,
(case b.btype
when 1 then s.salary*0.1
when 2 then s.salary*0.2
else s.salary*0.3 
 end) as bonus
from employees e
inner join emp_bonus b
on e.emp_no = b.emp_no
inner join salaries s
on s.emp_no = e.emp_no 
and s.to_date = '9999-01-01';

题目60:按照salary的累计和running_total,其中running_total为前两个员工的salary累计和,其他以此类推。 具体结果如下Demo展示。。

CREATE TABLE `salaries` ( 
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`));
emp_no salary running_total
10001 88958 88958
select a.emp_no,a.salary,(select sum(s.salary) 
                          from  salaries as s 
                          where s.emp_no <=a.emp_no 
                          and s.to_date='9999-01-01')as running_total
from salaries as a 
where a.to_date='9999-01-01' 
order by a.emp_no;

解析:本题的思路为复用 salaries 表进行子查询,最后以 s1.emp_no 排序输出求和结果。

  • 输出的第三个字段,是由一个 SELECT 子查询构成。将子查询内复用的 salaries 表记为 s2,主查询的 salaries 表记为 s1,当主查询的 s1.emp_no 确定时,对子查询中不大于 s1.emp_no 的 s2.emp_no 所对应的薪水求和
  • 注意是对员工当前的薪水求和,所以在主查询和子查询内都要加限定条件 to_date = ‘9999-01-01’

题目61:对于employees表中,给出奇数行的first_name

CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` char(1) NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`));
select e1.first_name
from employees e1
where(select count(*) 
      from employees e2 
      where e1.first_name >= e2.first_name) % 2 = 1;

解析:首先题目的叙述有问题,导致理解有误,输出的数据与参考答案不同。先给出正确的题目叙述:【对于employees表,在对first_name进行排名后,选出奇数排名对应的first_name】。

你可能感兴趣的:(sql练习题)