[牛客数据库SQL实战] 1~10题及个人解答
1. 查找最晚入职员工的所有信息
-- 先进行逆序排序,然后只输出第一个
-- 对hire_date字段排序降序,此时最晚的时间排在第一个,再用LIMIT取出
SELECT * FROM employees ORDER BY hire_date DESC LIMIT 1;
运行时间:16ms
占用内存:3320k
-- 最晚入职员工可能不只一个,先选出最晚入职时间,再对应到编号,由编号识别出最晚入职员工
-- 先找出 hire_date 字段的最大值,再把该值当成 employees 表的 hire_date 查询条件
SELECT MAX(hire_date) FROM employees;
SELECT * FROM employees WHERE hire_date = (SELECT MAX(hire_date) FROM employees);
运行时间:17ms
占用内存:3320k
2. 查找入职员工时间排名倒数第三的员工所有信息
-- 入职时间hire_date 排名倒数order by ? desc 第三limit 2,1
SELECT * FROM employees ORDER BY hire_date DESC LIMIT 2,1;
运行时间:20ms
占用内存:3296k
-- 题目的关键信息是入职时间排名 排名的是入职时间
-- 先给入职时间排名(去重),再从排名时间选出第三个,最后由时间对应员工输出员工信息
SELECT DISTINCT hire_date FROM employees ORDER BY hire_date DESC;
SELECT DISTINCT hire_date FROM employees ORDER BY hire_date DESC LIMIT 2,1;
SELECT * FROM employees WHERE hire_date = (SELECT DISTINCT hire_date FROM employees ORDER BY hire_date DESC LIMIT 2,1);
运行时间:22ms
占用内存:5480k
3. 查找各个部门当前(to_date='9999-01-01')领导当前薪水详情以及其对应部门编号dept_no
-- 按条件查询,三个条件:两表员工编号相同,部门管理表to_date=当前,薪水表to_date=当前
SELECT s.*, dm.dept_no
FROM salaries s, dept_manager dm
WHERE s.emp_no = dm.emp_no AND s.to_date = '9999-01-01' AND dm.to_date = '9999-01-01';
运行时间:24ms
占用内存:3296k
- 注意:这里有个坑!
主要在于两个表的逻辑关系,题目要求是薪水情况以及部门编号,再结合输出情况 dept_no 被放到了最后一列,可见是主表是“salaries”。
这里顺序错了就会提示:您的代码无法通过所有用例。
4. 查找所有已经分配部门的员工的last_name和first_name
-- 按条件查询 部门员工编号=员工编号
SELECT e.last_name, e.first_name, de.dept_no FROM employees e, dept_emp de WHERE e.emp_no = de.emp_no;
运行时间:16ms
占用内存:3424k
5. 查找所有员工的last_name和first_name以及对应部门编号dept_no,也包括展示没有分配具体部门的员工
-- 员工表的emp_no关联dept_emp的emp_no 注意连接方式
SELECT e.last_name, e.first_name, de.dept_no
FROM employees e LEFT JOIN dept_emp de
ON e.emp_no = de.emp_no;
运行时间:16ms
占用内存:3428k
- 注意:三种JOIN连接的基本区别
INNER JOIN 两边表同时有对应的数据,即任何一边缺失数据就不显示;
LEFT JOIN 会读取左边数据表的全部数据,即便右边表无对应数据;
RIGHT JOIN 会读取右边数据表的全部数据,即便左边表无对应数据。
本题使用的LEFT JOIN:
LEFT JOIN(左连接):获取左表employees所有记录,即使右表dept_emp没有对应匹配的dept_no记录。
- 注意:on与where的区别
两个表连接时用on,在使用left jion时,on和where条件的区别如下:
1、on 条件是在生成临时表时使用的条件,它不管on中的条件是否为真,都会返回左边表中的记录;
2、where 条件是在临时表生成好后,再对临时表进行过滤的条件。这时已经没有left join的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉。
SQL中ON和WHERE的区别
【mySQL】left join、right join和join的区别
6. 查找所有员工入职时候的薪水情况,给出emp_no以及salary, 并按照emp_no进行逆序
-- 按条件查询 两个条件: 员工表职员编号=薪水表职员编号, 员工表hire_date=薪水表from_date
SELECT e.emp_no, s.salary
FROM employees e, salaries s
WHERE s.emp_no = e.emp_no AND s.from_date = e.hire_date
ORDER BY e.emp_no DESC;
运行时间:20ms
占用内存:3420k
- 本题有以下四点需要注意:
1、由于测试数据中,salaries.emp_no 不唯一(因为号码为 emp_no 的员工会有多次涨薪的可能,所以在 salaries 中对应的记录不止一条),employees.emp_no 唯一,即 salaries 的数据会多于 employees,因此需先找到 employees.emp_no 在 salaries 表中对应的记录salaries.emp_no,则有限制条件 e.emp_no = s.emp_no
2、根据题意注意到 salaries.from_date 和 employees.hire_date 的值应该要相等,因此有限制条件 e.hire_date = s.from_date
3、根据题意要按照 emp_no 值逆序排列,因此最后要加上 ORDER BY e.emp_no DESC
4、为了代码良好的可读性,运用了 alias 别名语句,将 employees 简化为 e,salaries 简化为s,即 employees AS e 与 salaries AS s,其中 AS 可以省略
-- 另一种解法: 通过在薪水表找出同一职员的from_date, 最早的必定是入职时间, 而对应的薪水就是入职薪水
SELECT emp_no, salary FROM salaries GROUP BY emp_no HAVING MIN(from_date) ORDER BY emp_no DESC;
运行时间:21ms
占用内存:3420k
- 如果你使用命令后, 抛出以下异常信息:
Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'employees.salaries.salary' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中,也就是说查出来的列必须在group by后面出现否则就会报错,或者这个字段出现在聚合函数里面。
mysql命令gruop by报错this is incompatible with sql_mode=only_full_group_by
https://www.cnblogs.com/jim2016/p/6322703.html
-- 第二种方法需要修改sql_mode(这样并不好) 可以通过添加临时表来解决问题
SELECT sf.emp_no, s.salary
FROM salaries AS s
INNER JOIN (
SELECT emp_no, MIN(from_date) AS hire_date FROM salaries GROUP BY emp_no) AS sf
ON s.emp_no=sf.emp_no AND s.from_date=sf.hire_date
ORDER By sf.emp_no DESC;
运行时间:20ms
占用内存:3424k
7. 查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t
-- 计数使用count 通过group by对每个员工编号分组 通过having进行条件筛选
SELECT emp_no, COUNT(emp_no) AS t FROM salaries GROUP BY emp_no HAVING t > 15;
运行时间:30ms
占用内存:3436k
- 本题有四个注意点:
1、用COUNT()函数和GROUP BY语句可以统计同一emp_no值的记录条数
2、根据题意,输出的涨幅次数为t,故用AS语句将COUNT(emp_no)的值转换为t
3、由于COUNT()函数不可用于WHERE语句中,故使用HAVING语句来限定t>15的条件
4、最后存在一个理解误区,涨幅超过15次,salaries中相应的记录数应该超过16(从第2条记录开始算作第1次涨幅),
不过题目为了简单起见,将第1条记录当作第1次涨幅,所以令t>15即可
- 注意:
严格来说,下一条salary高于本条才算涨幅,但本题只要出现了一条记录就算一次涨幅,salary相同可以理解为涨幅为0,salary变少理解为涨幅为负
8. 找出所有员工当前(to_date='9999-01-01')具体的薪水salary情况,对于相同的薪水只显示一次,并按照逆序显示
-- 当前to_date 相同薪水只显示一次-去重distinct 逆序order by desc
SELECT DISTINCT(salary) FROM salaries WHERE to_date="9999-01-01" ORDER BY salary DESC;
运行时间:21ms
占用内存:3432k
-- 大表一般用distinct效率不高,大数据量的时候都禁止用distinct,这时也可以group by解决重复问题
SELECT salary FROM salaries WHERE to_date="9999-01-01" GROUP BY salary ORDER BY salary DESC;
运行时间:20ms
占用内存:3424k
9. 获取所有部门当前manager的当前薪水情况,给出dept_no, emp_no以及salary
当前表示to_date='9999-01-01'
-- dept_manager表获得manager的emp_no和dept_no, salaries表获得emp_no对应的salary
SELECT d.dept_no, d.emp_no, s.salary
FROM dept_manager AS d , salaries AS s
WHERE s.emp_no = d.emp_no AND s.to_date="9999-01-01" AND d.to_date="9999-01-01";
运行时间:22ms
占用内存:3304k
-- 使用双表连接也可以
SELECT d.dept_no, d.emp_no, s.salary
FROM dept_manager AS d
INNER JOIN salaries AS s
ON s.emp_no = d.emp_no AND s.to_date="9999-01-01" AND d.to_date="9999-01-01";
运行时间:24ms
占用内存:4064k
- 本题有两个注意点:
1、先用INNER JOIN连接两张表,限制条件是两张表的emp_no相同,即d.emp_no = s.emp_no,并且将salaries用别名s代替,dept_manager用别名d代替
2、根据题意,要获取当前manager的当前salary情况,再加上限制条件d.to_date = '9999-01-01' AND s.to_date = '9999-01-01'即可(因为同一emp_no在salaries表中对应多条涨薪记录,而当s.to_date = '9999-01-01'时是该员工当前的薪水记录)
10. 获取所有非manager的员工emp_no
-- 从manager表中选出所有manager员工emp_no, 再从员工表反选
SELECT emp_no FROM employees WHERE emp_no NOT IN (SELECT emp_no FROM dept_manager);
运行时间:17ms
占用内存:3300k
-- 使用连接,选出交集为NULL的值即为不在manager表的emp_no
SELECT e.emp_no, d.dept_no FROM employees AS e LEFT JOIN dept_manager AS d ON e.emp_no=d.emp_no ORDER BY d.dept_no DESC;
SELECT t.emp_no
FROM (
SELECT e.emp_no, d.dept_no
FROM employees AS e
LEFT JOIN dept_manager AS d
ON e.emp_no=d.emp_no) AS t
WHERE t.dept_no IS NULL;
运行时间:17ms
占用内存:3408k
-- 可以直接使用单层SELECT查询
SELECT e.emp_no FROM employees AS e LEFT JOIN dept_manager AS d ON e.emp_no=d.emp_no WHERE d.dept_no IS NULL;
运行时间:17ms
占用内存:3408k
- 注意:
NOT IN在实际使用中,因为NOT IN会转化成多表连接,而且不使用索引,在这里,觉得还是用LEFT JOIN代替会好一点
[[笔记] SQL性能优化 - 避免使用 IN 和 NOT IN] (https://www.cnblogs.com/hydor/p/5391556.html)
补充
本文格式
题目 + SQL代码 + 代码在审核系统中的耗时与资源
(+题目注意点+需要掌握的知识点)
SQL实战注意事项
- 有些题目是有坑的,字面上的意思和审核的标准可能是不同的
比如:许多题目可能没写“当前”这个关键词,但审核结果是在“当前”的前提下的;
可能因为计算关系,“薪水涨幅情况”其实是“薪水记录数”等等。- 该实战的审核系统和运行环境(SQL 3.7.9)与当前我们的实际环境(比如MySQL 5.7+)是有比较大的区别的
运行环境方面,所以有些语法实际环境上可能是无法得到正确答案,甚至有误的,
审核系统方面,因为是在离线审核,所以数据量不太够或其他原因,无法正确识别正确代码
以至其他有误的代码可以通过审核系统,所以我们最好将可以通过题目的答案仅做参考,主要从中吸取解题思路和技巧。
完整SQL代码
我的练习SQL代码已经上传至Github:https://github.com/slowbirdoflsh/newcode-sql-practice