牛客网数据库SQL编程笔记

第一题:查找最晚入职员工的所有信息

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

解题思路:利用子查询,找出入职员工里最晚的入职时间,在查询入职最晚的员工的信息。

其实这道题第一时间想到使用 order by 进行排序查找第一条,但这种方法仅限于当最晚入职数据只有一条时才正确,不能保证有多条同一最晚时间时的准确性。

select * from employees
order by hire_date desc
limit 1;

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

select * from employees
where hire_date = (
  select  distinct  hire_date 
  from employees 
  order by hire_date desc
  limit 2,1); 

解题思路:利用子查询方法,关键点是要用distinct去重,去重后会按入职日期进行排序分组,多个相同入职日期会分为一组,在此基础上再查找员工信息。

在系统里可能用例中没有相同日期入职的员工,所以下面的方法也可以通过,但是这样并不严谨。

select *
from employees
where emp_no in (
    select emp_no from employees order by hire_date desc limit 2,1);

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

select s.*,d.dept_no
from salaries as s,dept_manager as d
where s.to_date='9999-01-01'
and d.to_date='9999-01-01'
and s.emp_no = d.emp_no;

解题思路:将两张表通过emp_no进行连接,并限定查找条件为是当前时间(to_date='9999-01-01')的情况。

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

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

解题思路:查找所有已经分配部门的员工,则将dept_emp作为主表,使用左联结,也可使用自然连接,但是要明确dept_emp.dept_no不能为空。

select employees.last_name,employees.first_name,dept_emp.dept_no
from dept_emp inner join employees
on dept_emp.emp_no = employees.emp_no
where dept_emp.dept_no is not null;

第五题:查找所有员工的last_name和first_name以及对应部门编号dept_no,也包括展示没有分配具体部门的员工。

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

解题思路:employees作为主表,使用左联结即可。

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

select salaries.emp_no,salaries.salary
from employees,salaries
where employees.emp_no=salaries.emp_no
and employees.hire_date=salaries.from_date
order by salaries.emp_no DESC

解题思路:使用内连接或者左连接,条件是salaries表的from_date 和 employees表的hire_date 的值要相等,因此有限制条件employees.hire_date=salaries.from_date,两个date都是刚入职的时间。

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

select emp_no,count(*) as t
from salaries
group by emp_no having count(emp_no)>15;

解题思路:在系统中,该方法输入可以通过,但并不严谨,个人认为涨幅可包括涨薪和降薪两种,主要问题是刚入职薪水不应该算做涨幅薪水,所以count(emp_no)>16才应该正确。
看到一解法思路:count(*) 为计算全部数据的行数地意思,比较关键的一个点就是联结条件a.to_date = b.from_date,这个条件限定了两个工资之比必须是相邻的,如果没有这个条件,那同一个emp_no下的任意两个salary都可以做对比,可以把这个条件去掉,对比两个查询结果,就明白了。

select a.emp_no,count(*) t
from salaries a inner join salaries b on a.emp_no=b.emp_no and a.to_date = b.from_date
where a.salary < b.salary
group by a.emp_no
having t > 15;

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

select distinct salary
from salaries
where to_date='9999-01-01'
order by salary desc;

解题思路:对于相同的薪水只显示一次,使用distinct去重的方法,但是数据较多时使用distinct效率相比group by要慢。

select salary
from salaries
where to_date='9999-01-01'
group by salary
order by salary desc;

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

select dept_manager.dept_no,dept_manager.emp_no,salaries.salary
from dept_manager inner join salaries
on dept_manager.emp_no = salaries.emp_no
where salaries.to_date='9999-01-01'
and  dept_manager.to_date='9999-01-01';

解题思路:关键点是dept_manager表的to_date和salaries表的to_date都要等于'9999-01-01',s.to_date = '9999-01-01'限定当前时间当前工资,d.to_date = '9999-01-01'则限定在职经理,有两个“当前”。

第十题:获取所有非manager的员工emp_no

select employees.emp_no
from employees left join dept_manager
on employees.emp_no=dept_manager.emp_no
where dept_manager.dept_no is null;

解题思路:employees作为主表,使用左联结,限定条件为d.dept_no为空,选出在employees但不在dept_manager中的emp_no记录。

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

select dept_emp.emp_no,dept_manager.emp_no as manager_no
from dept_emp left join dept_manager
on dept_emp.dept_no = dept_manager.dept_no 
where dept_emp.to_date='9999-01-01' 
and dept_manager.to_date='9999-01-01'
and dept_emp.emp_no<>dept_manager.emp_no

解题思路:由于每个部门都有manager,每个员工都有对应的部门,所以用部门号dept_no进行左连接,根据题目要求,如果当前的manager是自己的话结果不显示,所以限定条件dept_emp.emp_no与dept_manager.emp_no不相等,且题目要求当前,所以限定员工时间和经理任职时间都是to_date='9999-01-01'。

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

select dept_emp.dept_no,salaries.emp_no,max(salaries.salary) AS salary
from salaries left join dept_emp
on salaries.emp_no = dept_emp.emp_no
where dept_emp.to_date = '9999-01-01' AND salaries.to_date = '9999-01-01'
group by dept_emp.dept_no

解题思路:
①用group by dept_emp.dept_no将每个部门分为一组,用MAX()函数选取每组中工资最高者。
②为获得当前薪水,工资表和部门表的to_date都是'9999-01-01'

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

select title,count(title)as t
from titles
group by title
having count(title)>=2

解题思路:首先使用group by对tittle进行分组,然后用having计算个数大于等于2的分组即可。

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

select title,count(distinct emp_no)as t
from titles
group by title
having count(title)>=2

解题思路:同上题,主要是使用distinct对emp_no 去重。

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

select *
from employees
where last_name !='Mary' and emp_no%2=1
order by hire_date DESC

解题思路:last_name不为Mary,使用 last_name !='Mary' 表示,emp_no为奇数,可使用emp_no取余为1,即emp_no%2=1表示,最后进行排序。

第十六题:统计出当前各个title类型对应的员工当前(to_date='9999-01-01')薪水对应的平均工资。结果给出title以及平均工资avg。

select title,avg(salary) as avg
from titles inner join salaries
on titles.emp_no = salaries.emp_no
where titles.to_date='9999-01-01' and salaries.to_date='9999-01-01'
group by titles.title

解题思路:使用group by对title进行分组,然后限定条件to_date='9999-01-01',使用平均函数求出平均工资。

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

select emp_no,salary
from salaries
where to_date='9999-01-01'
and salary =(select distinct salary
              from salaries
              where to_date='9999-01-01'
              order by salary desc limit 1,1)

解题思路:首先要找出薪水第二多的员工,通过将薪水从高到底排序,剔除重复的薪水,找出排名第二的薪水额度,在查找该员工的信息。

第十八题:查找当前薪水(to_date='9999-01-01')排名第二多的员工编号emp_no、薪水

salary、last_name以及first_name,不准使用order by

select employees.emp_no,max(salary) as salary,last_name,first_name
from employees inner join salaries
on employees.emp_no = salaries.emp_no
where salaries.to_date='9999-01-01'
and salary not in (select max(salary)
              from salaries
              where to_date='9999-01-01')

解题思路:将最大的薪水剔除,再求最大的薪水,就是薪水第二多。

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

select last_name,first_name,dept_name
from (employees left join dept_emp on employees.emp_no=dept_emp.emp_no) 
left join departments on dept_emp.dept_no=departments.dept_no

解题思路:解题思路:本题关键在于理清三表之间关联关系。需要使用两次左联结。红色部分可以省略。

①题目要求包括暂时没有分配部门的员工,所以将employees作为主表,使用左联结将其和dept_emp表联结,联结条件为员工编号emp_no,得出所有员工所在部门,并将结果命名为a。

②将查询结果a与departments左联结,联结条件为部门编号dept_no,求出部门名字dept_name。

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

select max(salary)-min(salary) as growth
from salaries
where emp_no='10001'
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 asc limit 1))
as growth

解题思路:对“入职以来的薪水salary涨幅值growth”有两种理解,第一种是员工入职以来工资最大值减去工资最小值,参考方法一。第二种理解是最新一次工资减去入职时的工资,即为涨幅值growth,参考方法二,方法二更严谨。

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

select a.emp_no,(b.salary-a.salary) as growth
from 
(
    select salaries.emp_no,salaries.salary
    from employees,salaries
    where employees.emp_no=salaries.emp_no
    and employees.hire_date=salaries.from_date)
as a,
(
    select emp_no,salary
    from salaries
    where to_date ='9999-01-01') as b
where a.emp_no=b.emp_no
order by growth

解题思路:1.首先将两表通过emp_no进行连接,筛选雇佣时间与薪资时间相等的条件,查找出员工入职时候的薪资
2.通过salaries表查找出当前薪资筛选条件to_date ='9999-01-01'
3.将查找出的a表和b表通过emp_no进行连接,最后对growth进行排序

第二十二题:统计各个部门的工资记录数,给出部门编码dept_no、部门名称dept_name以及次数sum

select dept_emp.dept_no,departments.dept_name,count(salary) as sum
from (dept_emp left join salaries
on dept_emp.emp_no=salaries.emp_no)
left join departments
on dept_emp.dept_no=departments.dept_no
group by dept_emp.dept_no

解题思路:两次连接左连接和内连接都可以,先将dept_emp表和 salaries表进行连接,再与departments进行连接,由于是查找各个部门的工资记录数,所以要记得进行group by分组

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

select emp_no,salary,dense_rank() over (order by salary desc) as rank
from salary
where to_date='9999-01-01'
order by salary desc,emp_no asc

解题思路:使用窗口函数DENSE_RANK() OVER()函数即可将实现薪水按照salary进行按照1-N的排名,相同salary并列。
窗口函数:<窗口函数> OVER ([PARITITION BY <列清单>] ORDER BY <排序用列清单>) [PARITITION BY <列清单>]可以省略。
①能够作为窗口函数的聚合函数(sum,avg,count,max,min)
②专用窗口函数(RANK,DENSE_RANK,ROW_NUMBER)
RANK() OVER (ORDER BY <列名>):计算排序,如果存在相同位次的记录,则会跳过之后的位次。如1,1,1,4
DENSE_RANK () OVER (ORDER BY <列名>):计算排序,即使存在相同位次的记录,也不会跳过之后的位次。如1,1,1,2
ROW_NUMBER () OVER (ORDER BY <列名>):计算排序,赋予唯一的连续位次。如:1,2,3,4

select s1.emp_no,s1.salary,count(DISTINCT s2.salary) as rank
from salaries s1,salaries s2
where s1.to_date='9999-01-01' 
and s2.to_date='9999-01-01'
and s1.salary<=s2.salary
group by s1.emp_no
order by s1.salary DESC,s1.emp_no ASC

1、从两张相同的salaries表(分别为s1与s2)进行对比分析,先将两表限定条件设为to_date = '9999-01-01',挑选出当前所有员工的薪水情况。
2、本题的精髓在于 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,其余排名依次类推。
3、由于使用了count函数,所以需要GROUP BY s1.emp_no,否则输出的记录只有一条(可能是第一条或者最后一条,根据不同的数据库而定)
4、最后先以 s1.salary 逆序排列,再以 s1.emp_no 顺序排列输出结果

第二十四题:获取所有非manager员工当前的薪水情况,给出dept_no、emp_no以及salary,当前表示to_date='9999-01-01'

select dept_emp.dept_no,employees.emp_no,salaries.salary 
from employees inner join salaries
on employees.emp_no=salaries.emp_no
inner join dept_emp
on employees.emp_no=dept_emp.emp_no
where salaries.to_date='9999-01-01'
and employees.emp_no not in (select emp_no
                            from dept_manager
                            where to_date='9999-01-01')

解题思路:
1.将employees表和salaries表进行连接,查找出员工当前薪资
2.再与dept_manager表连接,查找出员工所在部门
3.通过employees表和dept_manager表查找不属于manager员工的员工
4.通过to_date='9999-01-01'对员工和当前领导进行限定

第二十五题:获取员工其当前的薪水比其manager当前薪水还高的相关信息,当前表示to_date='9999-01-01',

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

select a.emp_no,b.emp_no as manager_no,a.salary as emp_salary,b.salary as manager_salary
from (
    select dept_emp.emp_no,salaries.salary,dept_emp.dept_no
    from dept_emp inner join salaries
    on dept_emp.emp_no=salaries.emp_no
    where dept_emp.to_date='9999-01-01'
    and salaries.to_date='9999-01-01') as a,
(
    select dept_manager.emp_no,salaries.salary,dept_manager.dept_no
    from dept_manager inner join salaries
    on dept_manager.emp_no=salaries.emp_no
    where dept_manager.to_date='9999-01-01'
    and salaries.to_date='9999-01-01') as b
where a.dept_no=b.dept_no and a.salary > b.salary

解题思路:1.连接dept_emp和salaries表,创建部门员工工资表a,
2.连接dept_manager和salaries表,创建部门领导工资表b
3.将两个子查询表a和表b进行连接,找出在同一个部门的manager和员工,限定条件为员工工资大于manager工资。

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

select departments.dept_no,departments.dept_name,title,count(title) as count
from departments,dept_emp,titles
where departments.dept_no=dept_emp.dept_no
and dept_emp.emp_no=titles.emp_no
and dept_emp.to_date='9999-01-01'
and titles.to_date='9999-01-01'
group by departments.dept_no,title

解题思路:重点理解各个部门下各个title类型的汇总。
①先以员工编号为联结条件联结titles表和dept_emp表,得到员工部门编号和员工对应的title类型,再以部门编号为联结条件联结departments表,得到部门名称,最后记得限定titles表和dept_emp表的时间为当前to_date ='9999-01-01'。
②汇总的是各个部门下各个title类型,所以要先将部门编号进行一次汇总,再将tltle类型进行汇总,聚合函数count的参数为title。

第二十七题:给出每个员工每年薪水涨幅超过5000的员工编号emp_no、薪水变更开始日期from_date以及薪水涨幅值salary_growth,并按照salary_growth逆序排列。提示:在sqlite中获取datetime时间对应的年份函数为strftime('%Y', to_date)

select s1.emp_no,s1.from_date,(s1.salary-s2.salary) as salary_growth
from salaries s1 inner join salaries s2 
on s1.emp_no=s2.emp_no
where salary_growth>5000
and (strftime('%Y',s1.to_date)-strftime('%Y',s2.to_date)=1
or strftime('%Y',s1.from_date)-strftime('%Y',s2.from_date)=1)
order by salary_growth DESC

解题思路:
本题的难点在于如何理解 每年薪水涨幅,以及复用salaries表求出每年薪水涨幅超过5000的员工,具体思路如下:
1、假设s1是涨薪水前的表,s2是涨薪水后的表,因为每个员工涨薪水的时间不全固定,有可能一年涨两次,有可能两年涨一次,所以每年薪水的涨幅,应该理解为两条薪水记录的from_date相同或to_date相同。
/** 如果只限定to_date相同,则将第三条原始测试数据的52668改成62668时,就会少一条【62668-48584=14084】的记录
INSERT INTO salaries VALUES(10008,46671,'1998-03-11','1999-03-11');
INSERT INTO salaries VALUES(10008,48584,'1999-03-11','2000-03-10');
INSERT INTO salaries VALUES(10008, 62668 ,'2000-03-10','2000-07-31'); **/
2、找到s1与s2符合要求的记录后,用s2的薪水减去s1的薪水,用salary_growth表示,加上限定条件 s1.emp_no = s2.emp_no AND salary_growth > 5000,即同一员工每年涨幅超过5000的记录
3、最后依次输出emp_no、from_date、salary_growth,并以salary_growth逆序排列

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

第二十九题:使用join查询方式找出没有分类的电影id以及名称

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

第三十一题:获取select * from employees对应的执行计划

explain select * from employees

解题思路:explain模拟优化器执行SQL语句,在5.6以及以后的版本中,除过select,其他比如insert,update和delete均可以使用explain查看执行计划,从而知道mysql是如何处理sql语句,分析查询语句或者表结构的性能瓶颈。
作用:https://blog.csdn.net/UFO___/article/details/80951869

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

select last_name||' '||first_name as Name
from employees;
select CONCAT(last_name,' ',first_name) as Name
from employees;

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

第三十三题:创建一个actor表,包含如下列信息

image.png
create table actor(
actor_id smallint(5) not null,
first_name varchar(45) not null,
last_name varchar(45) not null,
last_update timestamp not null default (datetime('now','localtime')),
primary key(actor_id));

解题思路:最关键的两点是创建主键和默认系统当前时间的设置。

第三十四题:对于表actor批量插入如下数据

CREATE TABLE IF NOT EXISTS 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')))


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

解题思路:插入数据语法:insert into <表名> (<属性1>,<属性2>....) values (<数据1>,<数据2>....);

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

CREATE TABLE IF NOT EXISTS 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')))


image.png
insert  or ignore into actor (actor_id,first_name,last_name,last_update)
values(3,'ED','CHASE','2006-02-15 12:34:33')

解题思路:针对牛客网sqlite3环境,需要上方写法,insert into:插入数据,如果主键重复,则报错 ,insert repalce:插入替换数据,如果存在主键或unique数据则替换数据,insert ignore:如果存在数据,则忽略

insert  ignore into actor (actor_id,first_name,last_name,last_update)
values(3,'ED','CHASE','2006-02-15 12:34:33')

解题思路:针对mysql数据库,如果数据存在则忽略,在insert into之间加个ignore就可以

第三十六题:对于如下表actor,其对应的数据为:

image.png

创建一个actor_name表,将actor表中的所有first_name以及last_name导入改表。 actor_name表结构如下
image.png
create table actor_name as
select first_name,last_name 
from actor

解题思路:不同数据库语法不同,在Mysql中,可以加as,也可以不加as,但是针对牛客网sqlite3环境,需要加as 才能通过。

第三十七题:针对如下表actor结构创建索引:

CREATE TABLE IF NOT EXISTS 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')))
对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);

解题思路:创建唯一索引:CREATE UNIQUE INDEX <唯一索引名> ON <表名> (<列名>);
创建一个简单的索引:CREATE INDEX <普通索引名> ON <表名> (<列名>);

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

CREATE TABLE IF NOT EXISTS 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')))

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

解题思路一: CREATE VIEW ... AS ... 的 AS 是创建视图语法中的一部分,而后面的两个 AS 只是为字段创建别名

CREATE VIEW actor_name_view (fist_name_v, last_name_v) AS
SELECT first_name, last_name FROM actor 

解题思路二:创建视图语句:CREATE VIEW <视图名称> (<视图列名1>,<视图列名2>…)AS