牛客网:数据库SQL实战题库笔记

牛客网原题地址


牛客网的测试系统是SQLite,所以有一些用法与MySQL不一致

1. 查找最晚入职员工的所有信息

select * from employees order by hire_date desc limit 0,1

方法1:若最晚入职员工只有1人,则按入职时间倒序排列,排在第一的就是目标人员。

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

方法2:若最晚入职员工不止1人,则先查出最晚的入职日期,再用此日期作为筛选条件。


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

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

此题考查order by和limit用法:

  • “入职时间排名倒数”,使用order by hire_date desc
  • “第三名”,位置排在3,且为1人,使用limit 2,1。排序从0开始,若此题改为“第一名”,则用limit 0,1(此处0可省略)。

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

感觉题目有点问题:增加一句“输出结果按emp_no递增排序”就没异议了。
题干中的“输出描述”已经暗含了此排序,输出结果必须与之完全一致才能通过(这确实有些不合理)。

解法1:salaries作为主表,salaries的主键是emp_no,结果按emp_no递增排序

select s.* ,d.dept_no from salaries as s 
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'

解法2:dept_manager作为主表,dept_manager的主键是dept_no,结果按dept_no递增排序

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'
order by s.emp_no  #比解法1多了一步按emp_no递增排序
  • 表salaries里,1个人可能会发多次工资,对应多个to_date,因此用s.to_date = '9999-01-01’筛选“当前薪水”。
  • 表dept_manager里,1个部门可能有过多个领导,对应多个to_date,因此用d.to_date = '9999-01-01’筛选“部门当前领导”。

4. 查找所有已经分配部门的员工的last_name和first_name以及dept_no

select e.last_name,e.first_name,d.dept_no 
from dept_emp as d
join employees as e on d.emp_no=e.emp_no
where dept_no is not null;
  • “已经分配部门的员工”,所以dept_no不能为空

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

select e.last_name,e.first_name,d.dept_no 
from employees as e
left join dept_emp as d on e.emp_no=d.emp_no
  • 与题目4的差别:“包括展示没有分配具体部门的员工”,不用排除dept_no为空的情况。

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

select e.emp_no,s.salary
from employees as e
left join salaries as s
on e.emp_no=s.emp_no 
and e.hire_date=s.from_date
order by e.emp_no desc
  • “入职时候的薪水”,即发薪的from_date等于入职日期,employees.hire_date=salaries.from_date

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

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

虽然本题通过了,但严格来讲不够严谨。本题每出现一次工资就认为涨幅1次,但可能不变,也可能降薪。


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

select distinct salary from salaries 
where to_date='9999-01-01' 
group by emp_no 
order by salary desc
  • “当前”,to_date=‘9999-01-01’;
  • “相同的薪水只展现一次”,用distinct salary去重;
  • 同1个员工有多次发薪资的记录,因此需要去重group by emp_no,不过- 此题不用此步结果也正确,说明数据没有此情况;
  • “逆序”,order by salary desc。

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

select d.dept_no,s.emp_no,s.salary
from salaries as s
left join dept_manager as d
on s.emp_no=d.emp_no
where d.to_date='9999-01-01'
and s.to_date='9999-01-01'

同题目3


10. 获取所有非manager的员工emp_no

select emp_no from employees
where emp_no not in 
(
    select emp_no from dept_manager
)
  • dept_manager中的emp_no都是manager
  • 从employees过滤掉dept_manager中的emp_no

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

select de.emp_no,dm.emp_no as manager_no
from dept_emp as de
left join dept_manager as dm
on de.dept_no=dm.dept_no 
and dm.to_date='9999-01-01'
where de.emp_no <> dm.emp_no
  • “当前manager”,dm.to_date=‘9999-01-01’
  • “manager是自己的话结果不显示”,de.emp_no <> dm.emp_no

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

select d.dept_no,d.emp_no,max(salary)
from dept_emp as d
left join salaries as s
on d.emp_no=s.emp_no
where d.to_date='9999-01-01'
group by d.dept_no
  • “当前员工”,d.to_date=‘9999-01-01’
  • “部门最高薪水”,先用group by d.dept_no按部门分组,再用max(salary)找出各部门最高薪水

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

select title,count(*) as t from titles
group by title
having t>=2
  • “按照title进行分组”,group by title
  • “每组个数大于等于2”,having t>=2

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

解法1:

  • 先创建一个包含title和不重复emp_no的临时表,select distinct emp_no,title from titles
  • 再对临时表进行分组和筛选,group by title, having t>=2
select title,count(1) as t from
(select distinct emp_no,title from titles) 
group by title
having t>=2

解法2:

  • 基于title分组,group by title
  • “每组个数大于等于2”,having t>=2
  • “忽略重复的emp_no”,count(distinct emp_no)
select title,count(distinct emp_no) as t from titles
group by title 
having t>=2

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

select * from employees
where emp_no % 2 = 1
and last_name <> "Mary"
order by  hire_date desc
  • “奇数”,余数=1,emp_no %2 = 1
  • “last_name不为Mary”,last_name <> “Mary”
  • 逆序,order by desc

延展思考:判断奇数偶数常用办法

1.位运算:位运算符是在二进制数上进行计算的运算符。位运算会先将操作数变成二进制数,进行位运算。然后再将计算结果从二进制数变回十进制数。

  • 按位与
    位运算是对数的二进制进行的运算,利用按位与操作,就是 & 。奇数的二进制数的最后一位永远是 1,与 1 按位且只会得到 1,偶数相反。
select * from table where id&1 #筛选奇数
  • 右移1和左移1
    右移1(>>1)相当于十进制除以2,左移1(<<1)相当于十进制乘以2,先乘2再除2,若与原数相等则是偶数,不等是奇数。
select * from table where id<>(id>>1)<<1 #筛选奇数
select * from table where id=(id>>1)<<1 #筛选偶数

2. id计算

  • 求余数:%2或MOD(id,2),除以2余数是1则为奇数,余数是0则为偶数
select * from table where id%2=1 #筛选奇数
select * from table where id%2=0 #筛选偶数
select * from table where mod(id,2)=1 #筛选奇数
select * from table where mod(id,2)=0 #筛选偶数
  • -1的奇数次方和偶数次方
select * from table where power(-1,id)=-1 #筛选奇数
select * from table where power(-1,id)=1 #筛选偶数

16. 统计出当前各个title类型对应的员工当前(to_date=‘9999-01-01’)薪水对应的平均工资。结果给出title以及平均工资avg。

select title,avg(salary) from salaries
left join titles on salaries.emp_no=titles.emp_no
where salaries.to_date='9999-01-01'
and titles.to_date='9999-01-01'
group by title
  • “当前各个title”,titles.to_date=‘9999-01-01’,group by title
  • “员工当前”,salaries.to_date=‘9999-01-01’
  • “平均工资”,avg(salary)

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

select emp_no,salary from salaries
where to_date='9999-01-01'
order by salary desc
limit 1,1
  • “当前”,to_date=‘9999-01-01’
  • “薪水第二多”,
    • 按薪水降序排,order by salary desc
    • 过滤掉第一名,且只取1名,limit 1,1

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

select e.emp_no,s.salary,e.last_name,e.first_name
from employees as e
left join salaries as s
on e.emp_no=s.emp_no
where to_date='9999-01-01'
and s.emp_no =
(
    select emp_no from salaries
    where salary=
    (
        select max(salary) from salaries where salary<
         (
             select max(salary) from salaries
         )
    )
)
  • 当前薪水,to_date=‘9999-01-01’
  • 薪水排名第二多的员工,
    • 最多薪水,select max(salary) from salaries
    • 第二多就是“除了最多的以外,剩余的人中最多的”,salary=select max(salary) from salaries where salary<(select max(salary) from salaries)

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

select e.last_name,e.first_name,d.dept_name 
from employees as e
left join dept_emp as de
on e.emp_no=de.emp_no
left join departments as d
on de.dept_no=d.dept_no
  • 3个字段所在的两个表,没有直接联系,需要借助中间表进行连接,因此需要2次左联
  • “所有员工,包括没有分配部门的员工”,说明要用employees作为主表,再左联其他表

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

select max(salary)-min(salary) as growth from salaries where emp_no=10001
  • 解法1:默认员工是一直涨薪从不降薪的,所以入职时是最低薪资,最近一次发薪水是最高薪资,故将“最近一次薪资”与“入职时的薪资”做比较,转化为“最高薪资”与“最低薪资”做比较,即 :max(salary)-min(salary)
  • 若员期间出现了先降薪后涨薪,则上述结果会不准确
select a.top - b.down as growth
from (select emp_no,max(to_date),salary as top from salaries where emp_no=10001) as a
join (select emp_no,min(to_date),salary as down from salaries where emp_no=10001) as b
on a.emp_no=b.emp_no
  • 解法2:解决期间降过薪的情况。
    • 入职之初min(to_date)的工资,最近一次max(to_date)的工资,二者相减得出涨幅值
    • 两表用emp_no作为连接条件
select 
s1.salary-s2.salary as growth 
from
(select emp_no,salary from salaries where emp_no=10001 order by salary desc limit 1) as s1
join 
(select emp_no,salary from salaries where emp_no=10001 order by salary asc limit 1) as s2
on 
s1.emp_no=s2.emp_no
  • 解法3:按工资降序和升序排列,取第一个值即为最高工资和最低工资

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

select e.emp_no,s1.salary-s2.salary as growth
from employees as e
inner join salaries as s1
on s1.emp_no=e.emp_no and s1.to_date ='9999-01-01'
inner join salaries as s2
on s2.emp_no=e.emp_no and s2.from_date=e.hire_date
order by growth asc
  • 受惯性思维影响,用20题同样的方式,发现无法通过,原因还在思考中
  • 难点1:最高工资为当前工资。当前时间是’9999-01-01‘,是之前的题目给出的已知条件,本题未给出,是隐含条件。
  • 难点2:最低工资为入职时工资。from_date=hire_date

22. 统计各个部门的工资记录数,给出部门编码dept_no、部门名称dept_name以及次数sum

select d.dept_no,d.dept_name,count(1) as sum
from departments as d
left join dept_emp as de
on d.dept_no=de.dept_no
left join salaries as s
on de.emp_no=s.emp_no
group by d.dept_no
  • “各个部门的工资记录数”,按部门分组,然后count
  • 表与表相连,注意用索引:
    • departments的主键是dept_no
    • dept_emp的主键是emp_no,dept_no
    • salaries的主键是emp_no,from_date
      因此想要连接departments和salaries,需要连上中间表dept_emp

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

select s1.emp_no,s1.salary,count(distinct s2.salary) as rank
from salaries as s1,salaries as 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 rank asc,s1.emp_no asc
  • 只有1个表,引用2次,自己和自己比大小并排名
  • 重点是s1.salary<=s2.salary,count(s2.salary),若s1.salary=10000,满足条件的s2.salary有(10000,10001,10002,10002),但由于10002重复,利用count(distinct s2.salary)去重后为3,即s1.salary=10000,排名3。
  • 不要忘了GROUP BY s1.emp_no,否则输出的记录只有一条(可能是第一条或者最后一条,根据不同的数据库而定),因为用了聚合函数COUNT()

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

select de.dept_no,de.emp_no,salary
from dept_emp as de,employees as e,salaries as s
where de.emp_no not in(select emp_no from dept_manager)
and de.emp_no=e.emp_no
and de.emp_no=s.emp_no
and s.to_date='9999-01-01'
  • 非manager员工,de.emp_no not in(select emp_no from dept_manager)

25. 获取员工其当前的薪水比其manager当前薪水还高的相关信息,当前表示to_date=‘9999-01-01’,结果第一列给出员工的emp_no,第二列给出其manager的manager_no,第三列给出该员工当前的薪水emp_salary,第四列给该员工对应的manager当前的薪水manager_salary

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

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

select d.dept_no,d.dept_name,t.title,count(t.title)
from titles as t
inner join dept_emp as de on de.emp_no=t.emp_no
inner join departments as d on d.dept_no=de.dept_no
where t.to_date='9999-01-01' and de.to_date='9999-01-01'
group by d.dept_no,t.title
  • inner join 三个表,谁做主表都行,因为inner join取的是交集
  • ”当前部门当前员工“,t.to_date=‘9999-01-01’ and de.to_date=‘9999-01-01’
  • 本题难点:同时对dept_no和title进行分类,并且顺序是dept_no在前、title在后。
    • 若只对dept_no分类,则同一部门的不同title无法展示;若只对title分类,则不同部门的同一title无法展示。
    • 分组顺序是先dept_no后title,是因为先按部门分组,然后在dept_no相同的基础上,再按title分组
    • “部门的title”,说明title归属于部门。若分组时先title后dept_no,表示先按title分组,在同一title的基础上再对dept_no分组,最后需添加“order by d.dept_no,t.title”,排序才能符合题目。

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

select s1.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 salary_growth>5000
and (strftime('%Y',s2.to_date)-strftime('%Y',s1.to_date)=1
or strftime('%Y',s2.from_date)-strftime('%Y',s1.from_date)=1)
order by salary_growth desc
  • 本题解答来源:牛客网

假设s1是涨薪水前的表,s2是涨薪水后的表,因为每个员工涨薪水的时间不全固定,有可能一年涨两次,有可能两年涨一次,所以每年薪水的涨幅,应该理解为两条薪水记录的from_date相同或to_date相同。“每年”,strftime(’%Y’,s2.to_date)-strftime(’%Y’,s1.to_date)=1


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

select c.name,count(c.name)
from (select category_id
      from film_category
      group by category_id
      having count(film_id)>=5) as ca,
film as f,category as c,film_category as fc
where f.film_id=fc.film_id
and c.category_id=fc.category_id
and fc.category_id=ca.category_id
and f.description like "%robot%"
  • 建立虚拟表,将电影数量>=5的分类找出来,再连接其他表

  • 以下是典型错误的:因为f.description like "%robot%"过滤后,电影分类下所属的电影数量减少了,之后再分组并count>=5,得不出正确结果

select c.name,count(c.name)
from film as f,category as c,film_category as fc
where f.film_id=fc.film_id
and c.category_id=fc.category_id
and f.description like "%robot%"#过滤后,电影分类下所属的电影数量减少了
group by c.name
having count(c.name)>=5

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

select f.film_id,f.title from film as f
left join film_category as fc on f.film_id=fc.film_id
left join category as c on c.category_id=fc.category_id 
where c.category_id is null 
  • 使用“外连接+判断为空”的形式代替not in
  • 题外话:这种形式在执行时效率也更高,原因参考这里。因此平时也要养成不要使用not in的习惯
#一般形式:
select ID,name from Table_A where ID not in (select ID from Table_B)
#替代方案:
select ID,name from Table_A left join Table_B on Table_A.id=Table_B.id where Table_B.id is null
注意:不要在on时判断为空,会与预期不同,需在where时判断

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

select title,description from film where film_id in
(
    select film_id from film_category where category_id in 
    (
        select category_id from category where name = "Action"    
    )
)
  • 先从category里找到name=Action对应的category_id
  • 再从film_category里找到category_id对应的film_id
  • 最后从film里找到film_id对应的title和description

31.获取select * from employees对应的执行计划

explain select * from employees
  • 本题考查explain的用法,即在SQL最前面加上explain,可得出执行计划,以便对SQL进行进一步的优化,提升执行效率

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

MySQL、SQL Server、Oracle等数据库支持CONCAT方法,
而本题所用的SQLite数据库只支持用连接符号"||"来连接字符串
 
CONCAT方法:
select CONCAT(CONCAT(last_name," "),first_name) as name  from employees
或者
select CONCAT(last_name," ",first_name) as name  from employees
 
本题中使用:
select last_name||" "||first_name as name  from employees

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 最后更新时间,默认是系统的当前时间
create table if not exists 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_id smallint(5) not null primary key,也可在最后面写,即primary key(actor_id)
  • "默认值"用default()
  • 当前日期datetime(‘now’,‘localtime’)

34.对于表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’)))

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(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,……)
  • 若插入的值的个数=字段个数,字段名可省略,即:insert into 表名 values (值1,值2,……)
  • 逐条插入:
    insert into actor values(1,'PENELOPE','GUINESS','2006-02-15 12:34:33');
    insert into actor values(2,'NICK','WAHLBERG','2006-02-15 12:34:33');
    

35. 对于表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’)))

actor_id first_name last_name last_update
‘3’ ‘ED’ ‘CHASE’ ‘2006-02-15 12:34:33’
#若不存在,则插入;若存在,则忽略
insert or ignore into actor values('3','ED','CHASE','2006-02-15 12:34:33')
#若不存在,则插入;若存在,则代替
insert or replace into actor values('3','ED','CHASE','2006-02-15 12:34:33')

36.对于如下表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

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

列表 类型 是否为NULL 含义
first_name varchar(45) not null 名字
last_name varchar(45) not null 姓氏
create table if not exists actor_name as 
select first_name,last_name from actor;
  • 方法1:创建表+插入值,一句sql
    • create table 新表 as select 字段 from 旧表
create table if not exists 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;
  • 方法2:第一步,建表;第二步,插入值
    • create table 新表(新字段)
    • insert into 新表 select 字段 from 旧表

37.对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);
  • 方法1,创建索引:create unique index 索引名 on 表名(字段名)
alter table actor add unique index uniq_idx_first(first_name);
alter table actor add index idx_lastname(last_name);
  • 方法2,修改表结构:alter table 表名 add unique index 索引名(字段名)
    点击查看更多关于MYSQL索引的知识点

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

create view actor_name_view as
select first_name as first_name_v,last_name as last_name_v from actor
  • 创建视图:create view view_name as select column_name from table_name where condition
  • 更新视图: create or replace view view_name as select column_name from table_name where condition

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

select * from salaries indexed by idx_emp_no where emp_no=10005;
  • SQLite强制索引:indexed by index_name
select * from salaries force index(idx_emp_no) where emp_no=10005;
  • MYSQL强制索引:force index(index_name)

40.存在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’)));
现在在last_update后面新增加一列名字为create_date, 类型为datetime, NOT NULL,默认值为’0000-00-00 00:00:00’

alter table actor add create_date datetime not null default '0000-00-00 00:00:00'
  • 添加字段:alter table table_name add column_name
alter table actor add create_date after last_update datetime not null default '0000-00-00 00:00:00'
  • MYSQL中,可使用关键字first(在首位),after column_name(在某字段后面)

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 if not exists audit_log
after insert on employees_test for each row 
begin
    insert into audit values(new.id,new.name);
end;

MYSQL触发器详细用法参考:MySQL 触发器学习教程

  • 创建触发器格式:create triggier triggier_name before/after insert/update/delete on table_name for each row
    begin
    sql语句;
    end;

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 table_name where 条件

delete from titles_test where id in
(select max(id) from titles_test group by emp_no having count(1)>1);
  • 方法1:保留最小的ID=删除最大ID,用max(id);emp_no重复,则group by emp_no having count(1)>1
delete from titles_test where id not in
(select min(id) from titles_test group by emp_no);
  • 方法2:先用group by emp_no分组,再用min(id)找到最小ID,删除这些以外的数据,也就是delete from……where not in以上条件。

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

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);

update titles_test set to_date=NULL,from_date='2001-01-01' 
where to_date='9999-01-01';
  • update基本格式:update 表名 set 字段1=值1,字段2=值2 where 条件
  • 点击查看update使用方法

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

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);

update titles_test set emp_no=replace(emp_no,10001,10005)
where id=5 and emp_no=10001;

replace用法如下
mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数去替换,用起来非常的方便
Update table_name SET field_name = replace (field_name,’from_str’,'to_str’) Where field_name LIKE ‘%from_str%’

说明:
table_name —— 表的名字
field_name —— 字段名
from_str —— 需要替换的字符串
to_str —— 替换成的字符串


45. 将titles_test表名修改为titles_2017。

alter table titles_test rename to titles_2017;

修改表名:ALTER TABLE 旧表名 RENAME TO 新表名


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
);

#牛客网是SQLite,能通过的版本
DROP TABLE audit;
CREATE TABLE audit(
    EMP_no INT NOT NULL,
    create_date datetime NOT NULL,
    FOREIGN KEY(EMP_no) REFERENCES employees_test(ID));

#mysql版本
alter table audit add foreign key(emp_no) references employees_test(id);

  • 对新创建表添加外键: create table 表名(字段名, foreign key(字段名) references 父表(主键名))
  • 对已创建表添加外键: alter table 表名 add foreign key(外键字段) references 父表(主键字段)

47.存在如下的视图:create view emp_v as select * from employees where emp_no >10005;如何获取emp_v和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:join

select em.* from employees as em
join emp_v
using(emp_no)#using(emp_no)等价于on em.emp_no=emp_v.emp_no

解法2:from多表,where限定

select em.* from employees as em,emp_v
where em.emp_no=emp_v.emp_no

注:from多表和join多表效果和效率是一样的

解法3:emp_v本身就是employees的子集,所以直接输出emp_v即可

select * from emp_v

错误解法:没对*做限定,结果会将2表左右合并全部输出

select * from employees as em
join emp_v
using(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=1.1 *salary where emp_no in 
(select emp_no from emp_bonus)
  • 更新数据值用update:update 表名 set 字段1=值1,字段2=值2 where 条件

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

MySQL的方法,牛客网不能通过

select concat("select count(*) from "," ",table_name,";") as counts from (select table_name from information_schema.tables
where table_schema='mydata') as new;

MySQL语句中,‘mydata’是当前数据库的名称
information_schema.tables表示从表名中选择,information_schema.xxx中xxx可选的还有很多字段,如information_schema.columns表示从所有表的所有字段中选择。
MySQL字符串的连接使用concat函数,则按照要求可以有以下代码:
select concat(“select count(*) from “,table_name,”;”) as countts
from information_schema.tables where table_schema=‘mydata’;
参考文章:mysql数据库所有的表生成select count(*) 对应的sql语句


50. 将employees表中的所有员工的last_name和first_name通过(’)连接起来,输出为name。

MySQL的方法,牛客网不能通过

select concat(last_name,"'",first_name) as name from employees

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

select (length('10,A,B')-length(replace('10,A,B',',','')))/length(',') as cnt
  • 子串出现的次数 = 所有子串的总长度 / 单个子串的长度 = (原字符串长度 - 除去子串后的长度) / 单个子串的长度
  • 所有子串的总长度:length(‘10,A,B’)
  • 除去子串后的长度:length(replace(‘10,A,B’,’,’,’’)),即用空值替换子串后的长度
  • 单个子串的长度:length(’,’)

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

MySQL答案

select first_name from employees
order by right(first_name,2) asc
  • right(要截取的字符串,截取长度),从最右侧向左截取

SQLite牛客网答案

方法1select first_name from employees
order by substr(first_name,length(first_name)-1,2) asc

方法2
select first_name from employees
order by substr(first_name,-2)
  • substr(字符串,起始位置,[截取长度]),若起始位置是正数,则从左往右数,第一位是1不是0;若起始位置是负数,则从右往左数;截取长度可选,不写时截取到最后。

  • 此题的启示:order by 语句中也可使用函数

  • 其他的Mysql字符串截取总结:left()、right()、substring()、substring_index()


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));

牛客网SQLite解法

select dept_no,group_concat(emp_no) as employees
from dept_emp group by dept_no
  • group_concat(字段名,[分隔符]),此函数要与group by一起使用,分隔符默认为",",因此本题可省略。

MySQL解法

select dept_no,group_concat(emp_no) as employees
from dept_emp group by dept_no
  • group_concat( [DISTINCT] 要连接的字段 [Order BY 排序字段 ASC/DESC] [Separator ‘分隔符’] ),参考文章

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) as avg_salary from salaries 
where to_date = '9999-01-01'
and salary not in (select max(salary) from salaries)
and salary not in (select min(salary) from salaries)

上述结果尽管能通过,但与原题目对不上,

  • 要么调整题目:
    查找排除最大、最小salary之后的当前员工的平均工资avg_salary

  • 要么应该这样写:

select avg(salary) as avg_salary from salaries 
where to_date = '9999-01-01'
and salary not in (select max(salary) from salaries where to_date = '9999-01-01')
and salary not in (select min(salary) from salaries where to_date = '9999-01-01')
  • 我最开始的错误写法:
select avg(salary) as avg_salary from salaries 
where salary not in(select min(salary),max(salary) from salaries)
and to_date = '9999-01-01';

错误原因:select min(salary),max(salary) from salaries是两列值,所以salary not in两列结果为错


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

select * from employees limit (2-1)*5,5

limit用法:

  • 放在最后:select…from…where…group by… having…order by…limit……
  • 公式:limit (page-1)*size,size,page-返回的页数,size表示每页行数。以本题为例,“每页5行,返回第2页数据”则为:limit(2-1)*5,5。

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

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);

select e.emp_no,de.dept_no,eb.btype,eb.recevied 
from employees as e
inner join dept_emp as de on e.emp_no=de.emp_no 
left join emp_bonus as eb on eb.emp_no=de.emp_no

ps.原题创建表时有个拼写错误,received错拼成recevied,所以必须错着写才能有数据


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)

使用in的方法

select * from employees
where emp_no not in (select emp_no from dept_emp)

什么时候用in,什么时候用exists?
通常情况下采用exists要比in效率高,因为IN不走索引,但要看实际情况具体使用:

  • 外表大内表小用in,外表小内表大用exists,内外表大小差不多,用谁都行
  • 与not连用时,只用not exists,要比not in效率高

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));

select * from emp_v

视图跟表一样使用。emp_v是employees的子集,所以直接输出emp_v的全部数据即满足题意


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 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,
(
    case 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 as e
join emp_bonus as b on e.emp_no=b.emp_no
join salaries as s on e.emp_no=s.emp_no and s.to_date='9999-01-01'

case when 用法:

case colomn 
	when condition1 then result1
	when condition2 then result2
	when condition3 then result3	
else result4
end
注意最后要有end

60.统计员工emp_no, 当前薪资salary, 以及salary的累计和running_total,其中running_total为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));

解法1:子查询,单表自引用

select s1.emp_no,s1.salary,
(select sum(s2.salary) from salaries as s2 
 where s2.emp_no<=s1.emp_no and s2.to_date='9999-01-01')as running_total
from salaries as s1
where s1.to_date='9999-01-01'
order by s1.emp_no asc
  • running_total是emp_no排在前面的salary之和,可利用子查询统计,条件是s2.emp_no<=s1.emp_no
  • 当前薪资:to_date=‘9999-01-01’

解法2: 定义函数自增量

select emp_no,salary,@total:= @total + salary as running_total
from salaries,(select @total:=0) a
where to_date='9999-01-01'
order by emp_no asc

解法3:窗口函数(mysql8.0及以上版本可用)

select emp_no,salary,sum(salary)over(order by emp_no) as running_total from salaries
where to_date='9999-01-01'

聚合函数sum作为窗口函数的使用方法:

sum(求和列)over([partition by 分区列]order by 排序列 asc/desc)

分区列和排序列可以不在select列表中,但必须在数据源中,order by只对所在分区中的数据进行排序,与select语句中的排序无关,[partition by 分区列]可省略,若未省略则表示分组累计求和。


61. 对于employees表中,给出奇数行的first_name

解法1:定义函数自增量

select a.first_name
from (select @row: = @row + 1 as rownum, e.first_name from employees as e,(select @row: = 0 ) b ) a
where a.rownum % 2 = 1
  • rownum 是行的序列号。
  • where rownum % 2 =1,序列除以2余1,就是奇数行。

解法2:子查询

select e1.first_name
from employees as e1
where (select count(*) from employees as e2 where e1.first_name<=e2.first_name)%2=1

你可能感兴趣的:(MySql)