评论

收藏

[MySQL] #导入MD文档图片#SQL练习:通过练习写出有意思、有用的sql

数据库 数据库 发布于:2021-08-04 15:15 | 阅读数:543 | 评论:0

题目来源:牛客网
解答:老表
51CTO博客地址:https://blog.51cto.com/u_13334898
个人微信公众号:简说Python
大家好,我是老表,在这里会记录我的SQL刷题笔记,目前已经更新到了SQL21,欢迎大家进行学习交流、指正,一起加油。
SQL1 查找最晚入职员工的所有信息
DSC0000.png
我的思路: 根据hire_date(员工入职日期)进行降序排序,然后排第一位的就是最晚入职的员工。

我的题解:
select  
from employees
order by hire_date desc
limit 1;
涉及知识点: <br>

  • 排序:order by 字段名称 desc/asc (desc 降序,asc 升序)
  • 取出指定列:limit n
提交结果:<br>
DSC0001.png
其他题解学习:<br>
这个比最开始解法好,如果有多个员工都是最晚入职的,这样也可以筛选出来。
思路:先取出employees表中hire_date的最大值,然后再从employees表中选取出所有hire_date等于最大值的行。
select 
from employees
where hire_date = (
select max(hire_date)
from employees
);
另外还有几个新增知识点:

  • max函数,取出最大值,参数为列名(字段名字)
  • 使用limit与offset关键字,返回n行数据,从第m+1行开始返回:limit n offset m
  • 使用limit关键字 从第m+1条记录开始,返回n行数据,也就是第一条记录 limit m,n,如果m=0,就等于limit n
首先说下上一题中留的讨论问题:为什么where中不能使用聚合函数?
理由很简单,聚合函数是对列进行操作的,而where是对行数据进行筛选的,所以不能在where中不能使用聚合函数,但是在having中是可以使用的。
为什么呢?你可以评论区留言说说你的看法。
SQL2 查找入职员工时间排名倒数第三的员工所有信息
DSC0002.png

我的思路: 先取出入倒数第三的hire_date(员工入职日期),然后取出对应hire_date(员工入职日期)的数据行即可。
我的题解:
select *
from employees
where hire_date = (
select hire_date
from employees
group by hire_date
order by hire_date desc
limit 2,1
);
涉及知识点: <br>

  • 子查询(嵌套查询)
  • 子查询sql执行顺序:from-group by-select-order by-limit,补充完整sql执行顺序:
    from -> where -> group by -> having -> select -> order by -> limit
  • 排序:order by 字段名称 desc/asc (desc 降序,asc 升序)
  • 从指定位置开始取出指定行:limit m,n
提交结果:<br>
DSC0003.png
其他题解学习:<br>
思路: 首先按hire_date进行降序排序,然后直接利用row_number()函数给数据进行标号,取出标号为3的那条数据即可。
select emp_no, birth_date, first_name, last_name, gender, hire_date
from (
select , row_number() over (order by hire_date desc) as rownum
from employees
) a1
where rownum = 3;
另外还有几个新增知识点:

  • row_number() over (partition by col1 order by col2)  表示按col1进行分区,然后每个区块内按col2进行升序排序,并给对应的区块内编号(也可以不写分区,就整个数据集为一个分区);
理论上来说如果有相同入职时间的人,这种方法就不科学了。
SQL3 查找当前薪水详情以及部门编号dept_no
DSC0004.png
DSC0005.png

我的思路: 直接通过emp_no连接两个表就可以啦,这里的技巧就是左表优先选数据量小的表,比如题目中dept_manager就是相对小的表。
我的题解:
select a.emp_no, b.salary, b.from_date, b.to_date, a.dept_no
from (select 
from dept_manager
) as a
join (select *
from salaries
) as b
on a.emp_no = b.emp_no
order by a.emp_no;
涉及知识点: <br>

  • join 连接,还有左连接(left join)和右连接(right join)
  • sql 执行顺序(一天发一遍,记住)
    from -> where -> group by -> having -> select -> order by -> limit
  • 排序:order by 字段名称 desc/asc (desc 降序,asc 升序)
提交结果:<br>
DSC0006.png
其他题解学习:<br>
思路: 没有什么特别的,主要是将join改为了where实现,另外提出了应该限制时间的想法(应该只统计目前还在职的员工相关信息)。
select b.,a.dept_no
from dept_manager as a, salaries as b
where a.to_date='9999-01-01'
and b.to_date='9999-01-01'
and a.emp_no = b.emp_no
order by b.emp_no;
SQL4 查找所有已经分配部门的员工的last_name和first_name以及dept_no
DSC0007.png
DSC0008.png

我的思路: 直接通过emp_no连接两个表就可以啦,和上一个题几乎一样。
我的题解:
select a.last_name, a.first_name, b.dept_no
from (select emp_no, dept_no
from dept_emp) as b
join (select emp_no, last_name, first_name
from employees) as a
on b.emp_no = a.emp_no;
涉及知识点: <br>

  • 子查询(嵌套查询)
  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
提交结果:<br>
DSC0009.png
SQL5 查找所有员工的last_name和first_name以及对应部门编号dept_no
DSC00010.png

我的思路: 直接通过emp_no连接两个表就可以啦,和上一个题不同的是题目有说明也包括暂时没有分配具体部门的员工,所以这里使用dept_emp表右链接employees表。
我的题解:
select a.last_name, a.first_name, b.dept_no
from (select emp_no, dept_no
from dept_emp) as b
right join (select emp_no, last_name, first_name
from employees) as a
on b.emp_no = a.emp_no;
涉及知识点: <br>

  • 子查询(嵌套查询)
  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
    提交结果:<br>
    DSC00011.png
SQL7 查找薪水记录超过15次的员工号emp_no以及其对应的记录次数t
DSC00012.png
DSC00013.png

我的思路: 在薪水表中根据员工编号分组,然后计算每个组内的数据条数,筛选出数据条数大于15的员工编号和对应的数据条数即可。
我的题解:
select emp_no, count(emp_no) as t
from salaries
group by emp_no
having count(emp_no) > 15;
涉及知识点: <br>

  • 聚合函数count()计数
  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • group by 分组,having配合分组后筛选
提交结果:<br>
DSC00014.png
其他题解学习:<br>
DSC00015.png
在这题的讨论里看到这样一条讨论,然后看了下这个讨论下的评论,大家讨论的点是这题想统计的员工薪资涨幅次数大于15次的数据,而不是薪资表中有15条以上记录的员工数据。
估计这题原本是统计salaries表中员工薪水涨幅次数大于15次的员工编号和涨幅次数,那我们顺着这个思维来思考下吧。
思路: 首先涨幅肯定是想统计正涨幅,即按时间相邻两次薪水差为正值,然后统计涨幅超过15次的员工编号
理论上感觉不存在,一般调薪少则一个季度一次,多则一年一次,能调薪15次以上,应该是元老级别了!所以这样一想,可能原本出题者就是想考下大家group by having,统计下薪水发放次数。
当然,既然遇到这种思维,我们就顺着去练一练。
本题其他题解来自讨论区的LeronTalks。
DSC00016.png
select tmp.emp_no, count(tmp.whether) as t 
from ( select emp_no,
ifnull(s1.salary > (select salary
              from salaries as s2
              where s1.from_date &gt; s2.from_date
              and s1.emp_no = s2.emp_no
              order by s2.from_date desc 
              limit 1),
       1) as whether
    from salaries as s1) as tmp
group by tmp.emp_no
having count(tmp.whether) > 15;另外还有几个新增知识点:

  • 控制流函数ifnull(a,b),如果a为null或者0,则ifnull的值就为b,否则就为a;
  • count(null)为0。
这个评论很到位,学习、工作、生活中我们都应该多思考,而不是把自己困在既有的条条框框下,加油!
DSC00017.png
SQL8 找出所有员工当前薪水salary情况
DSC00018.png
我的思路: 首先筛选出题目中两个关键信息:只要薪水这一列、需要去重降序排列;按要求,我们 select salary,利用group by进行去重,order by salary desc降序排列,同时我们用where to_date = '9999-01-01'筛选出了当前还在公司的员工。

我的题解:
select salary
from salaries
where to_date = '9999-01-01'
group by salary
order by salary desc;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • group by单列分组 == 去重
  • order by 列名 desc  降序排序
  • distinct也可以去重复,但是效率相比group by会低一些(数据量大时)
提交结果:<br>
DSC00019.png
SQL10 获取所有非manager的员工emp_no
DSC00020.png
DSC00021.png

我的思路: 在员工表employees中取出员工编号不在部门领导表dept_manager中的员工编号即可。
我的题解:
select emp_no
from employees
where emp_no not in (select emp_no
         from dept_manager
         )
;涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • where筛选+子查询
  • not in / in 可以判断一个值在不在一个集合中
提交结果:<br>
DSC00022.png
其他题解:
也是筛选思路,先将dept_manager和employees连接起来,然后筛选出部门编号为空的员工编号即可,利用group by去重。
select b.emp_no
from dept_manager as a
right join employees as b
on a.emp_no = b.emp_no
where a.dept_no is null
group by b.emp_no
;
注意: != 和=用来判断具体的值,而NULL需要用is或者is not判断
SQL11 获取所有员工当前的manager
DSC00023.png
DSC00024.png

我的思路: 筛选思路,先将dept_emp和dept_manager通过dept_no连接起来,并要求emp_no不相等(去除管理者),然后筛选出dept_manager中员工编号emp_no不为空对应的两表中的员工编号即可。
我的题解:
select a.emp_no, b.emp_no
from dept_emp as a
left join dept_manager as b
on a.dept_no = b.dept_no
and a.emp_no != b.emp_no
where b.emp_no is not null
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • join 连接表
提交结果:<br>
DSC00025.png
其他题解:
同样思路,可以直接join两表。
select a.emp_no, b.emp_no
from dept_emp as a
join dept_manager as b
on a.dept_no = b.dept_no
and a.emp_no != b.emp_no;
SQL12 获取每个部门中当前员工薪水最高的相关信息(困难)
DSC00026.png
DSC00027.png

我的思路: 这里稍微有些复杂,首先需要取出的是当前每个部门中薪水最高的员工部门编号、员工编号、员工薪水,所以我们需要将dept_emp、salaries两表连接,限制to_date='9999-01-01'表示当前员工,然后where进行筛选最大薪水,这里用了一个字查询去找每个部门的最高薪水(其实就是从连接表中分组筛选出每组的最大薪水)。
我的题解:
select dept_no, emp_no, salary
from (
select a.dept_no, a.emp_no, b.salary
from dept_emp as a
join salaries as b
on a.emp_no = b.emp_no
and a.to_date='9999-01-01'
and b.to_date='9999-01-01') as a2
where a2.salary = (
select max(b.salary)
from dept_emp as a
join salaries as b
on a.emp_no = b.emp_no
and a.to_date='9999-01-01'
and b.to_date='9999-01-01'
where a2.dept_no = a.dept_no
group by a.dept_no
)
order by dept_no
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • 子查询
  • max聚合函数
  • group by 分组,其中非group by 的列在select里出现就必须是在聚合函数里。
提交结果:<br>
DSC00028.png
SQL15 查找employees表emp_no与last_name的员工信息
DSC00029.png

我的思路: 这个从题目标题无法看出需求,需要具体看描述:查找employees表所有emp_no为奇数,且last_name不为Mary的员工信息,并按照hire_date逆序排列。直接按照需求写sql即可,这里判断员工编号是否为奇数,可以使用%对2取余数进行判断,余数为1即为奇数。
我的题解:
select 
from employees
where emp_no % 2 = 1
and last_name != 'Mary'
order by hire_date desc
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • sql 取余数可以直接使用 %
  • order by hire_date desc 按hire_date列降序排序
提交结果:<br>
DSC00030.png
其他题解:
select *
from employees
where emp_no&1 = 1
and last_name != 'Mary'
order by hire_date desc
;
这里判断是否为奇数,也可以使用位运算进行判断,任何数和1的位运算结果如果是1,则是奇数,否则运算结果为0,则为偶数。
这个是真的涨见识了,位运算很好的应用实践。
SQL16 统计出当前各个title类型对应的员工当前薪水对应的平均工资
DSC00031.png
DSC00032.png
我的思路: 比较常规,首先明确需求:结果给出title以及平均工资avg,并且以avg升序排序,所以先将titles和salaries进行join连接,然后根据title进行分组求salary的平均值即可,最后按照平均值进行升序排序。

我的题解:
select title, avg(salary) as avg_s
from (
select a.emp_no, a.title, b.salary
from titles as a
join salaries as b
on a.emp_no = b.emp_no
and a.to_date = b.to_date
and a.to_date = '9999-01-01') as c
group by title
order by avg_s
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • 窗口函数:avg使用
  • group by 分组
  • order by 排序,默认是升序
提交结果:<br>
DSC00033.png
SQL17 获取当前薪水第二多的员工的emp_no以及其对应的薪水salary
DSC00034.png

我的思路: 这个只有一个表,蛮简单的,直接按照salary降序排序,取出第二列数据即可。
我的题解:
select emp_no, salary
from salaries
where to_date = '9999-01-01'
order by salary desc
limit 1,1
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • order by salary desc 降序排序
  • 从指定位置开始取出指定行:limit m,n 从m+1行开始,取出n行数据
提交结果:<br>
DSC00035.png
其他题解:
上面解法有局限性,如果排名第一的有很多个,那么取出来的还是薪水最高的,所以好的方法应该是直接按第二的薪水进行筛选。
所以我们先找出第二薪水是多少,然后筛选出薪水等于第二薪水的行即可。
select emp_no, salary
from salaries
where to_date = '9999-01-01'
and salary = (
select salary
from salaries
group by salary
order by salary desc
limit 1,1
)
order by salary desc
;
SQL18 获取当前薪水第二多的员工的emp_no以及其对应的薪水salary
DSC00036.png
DSC00037.png

我的思路: 紧接上一题,和sql17的区别就是 将员工薪水另外存了一张表,另外要求不能使用order by 排序。我们可以先找出薪水表中第二高的薪水,然后从employees、salaries两表中筛选出薪水第二高的员工信息。
取第二高的薪水,这里采用的是去除当前最高薪水后,再查找剩余薪水中的最高薪水。
我的题解:
select a.emp_no, b.salary, a.last_name, a.first_name
from employees as a
join salaries as b
on a.emp_no = b.emp_no
and b.salary = (
select max(salary)
from salaries
where to_date = '9999-01-01'
and salary < (
    select max(salary)
    from salaries
    where to_date = '9999-01-01'
  )
);涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • 窗口函数max取最大值
提交结果:<br>
DSC00038.png
其他题解:
DSC00039.png
这里有个大神三玫提供了另外一种找第二高薪水的方法,通过salaries自连接,连接条件是s1.salary &amp;lt;= s2.salary,这样后,再根据s1.salary分组,然后count(distinct s2.salary) = 2就是第二高薪水。
select a.emp_no, b.salary, a.last_name, a.first_name
from employees as a
join salaries as b
on a.emp_no = b.emp_no
and b.salary = (
select s1.salary
from salaries as s1
join salaries as s2
on s1.salary <= s2.salary
and s1.to_date = s2.to_date
and s1.to_date = '9999-01-01'
group by s1.salary
having count(distinct s2.salary) = 2
 )
;
这个思路真的特别巧妙,刚开始你可能会比较懵,不知道为什么这样就可以筛选出第二高薪水了,但是你一画图,举例子操作下,瞬间就能明白。
这里的原理可以归纳为:大于等于自己的数值的个数一定等于自己的降序排序的位数。
例如:在集合{100,98,97,89}中,大于等于98的只有100,98,所以98在这个集合中必然是第二大的数。
DSC00040.png
评论区也有人做了效率对比,当数据量上升时,该方法效率会大大降低,其实原因也很简单,salaries变大,自连接自然会消耗大量时间。
DSC00041.png
SQL19 查找所有员工的last_name和first_name以及对应的dept_name
DSC00042.png
DSC00043.png

我的思路: 这个题目和sql5是类似的,现在多了一个departments表,输出里把dept_no改成了dept_name,所以在原来的解法中,dept_emp表先与departmentsjoin,然后再和employees连接即可。
DSC00044.png
我的题解:
select a.last_name, a.first_name, b.dept_name
from (
select emp_no, last_name, first_name
from employees) as a
left join (
select d1.emp_no, d2.dept_name
from dept_emp as d1
join departments as d2
on d1.dept_no = d2.dept_no
) as b
on b.emp_no = a.emp_no
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • left join
提交结果:<br>
DSC00045.png
SQL21 查找在职员工自入职以来的薪水涨幅情况
DSC00046.png
DSC00047.png

我的思路: 这里需要求每个在职员工从入职开始到现在的薪水涨幅情况,这里直接找出每个在职员工入职时薪水,和当前薪水,然后求差即可。
这题比较有意思的是需要理解清楚以下几个日期字段含义:

  • employees.hire_date: 表示雇佣日期,可以理解为入职日期;
  • salaries.from_date: 表示薪水开始时间;
  • salaries.to_date: 表示薪水结束时间,如果为9999-01-01,则表示为当前在职员工。
我的题解:
select s1.emp_no, (s2.salary-s1.salary) as growth
from (
-- 入职薪水
select a1.emp_no, a2.salary
from employees as a1
join salaries as a2
on a1.emp_no = a2.emp_no
and a1.hire_date = a2.from_date
) as s1
join
(
-- 当前薪水
select emp_no, salary
from salaries
where to_date = '9999-01-01'
) as s2
on s1.emp_no = s2.emp_no
order by growth
;
涉及知识点: <br>

  • 完整sql执行顺序(每天看一遍,不信记不住):
    from -> where -> group by -> having -> select -> order by -> limit
  • join 连接
  • order by 分组
提交结果:<br>
DSC00048.png
题目来源:牛客网
解答:老表
51CTO博客地址:https://blog.51cto.com/u_13334898
个人微信公众号:简说Python

关注下面的标签,发现更多相似文章