【牛客网】数据库SQL实战(题解)
阅读原文时间:2023年07月08日阅读:3

【题解】

hire_date可能存在重复值,所以需要找到hire_date的最大值,然后再筛选,才能hire_date最晚的记录都筛选出来。

【代码】

1 SELECT * FROM employees
2 WHERE hire_date = (SELECT MAX(hire_date) FROM employees)

【题解】

还是hire_date可能存在重复值问题,所以需要先找到第三晚的hire_date(此处排序记得用distinct去重),然后再进行筛选。

【代码】

1 SELECT * FROM employees
2 WHERE hire_date = (SELECT DISTINCT hire_date FROM employees
3 ORDER BY hire_date DESC LIMIT 2, 1)

【题解】

这题好坑哦,因为题目中说的是“薪水详情以及其对应部门编号dept_no”,所以salaries表是主表,要写在前面?(我对这个思路并不是很赞同)

但其实我觉得是后台没有排序问题,只要把emp_no排个序也能过。

【代码】

SELECT s.*, d.dept_no
FROM salaries s, dept_manager d
WHERE d.to_date = '9999-01-01' AND s.to_date = '9999-01-01' AND d.emp_no = s.emp_no

1 SELECT s.*, d.dept_no
2 FROM dept_manager d, salaries s
3 WHERE d.to_date = '9999-01-01' AND s.to_date = '9999-01-01' AND d.emp_no = s.emp_no
4 ORDER BY s.emp_no

【题解】

也就是说有的员工不一定被分配了部门,那么只要将部门表左连接到员工表,即部门表上的信息都会有,但部门表上没有员工表上有的信息就不会被筛出,符合题目所求。

【代码】

1 SELECT e.last_name, e.first_name, d.dept_no
2 FROM dept_emp d LEFT JOIN employees e ON e.emp_no = d.emp_no

【题解】

跟上一题刚好反一下,这里是要把员工表左连接到部门表,这样不管员工是否有对应的部门,都能被显示出来。

【代码】

1 SELECT e.last_name, e.first_name, d.dept_no
2 FROM employees e LEFT JOIN dept_emp d ON e.emp_no = d.emp_no

【题解】

这里需要注意一下,因为是给出每个员工入职时的薪资,所以还需要将加上这个条件e.hire_date = s.from_date。

【代码】

1 SELECT e.emp_no, s.salary
2 FROM employees e, salaries s
3 WHERE e.emp_no = s.emp_no AND e.hire_date = s.from_date
4 ORDER BY e.emp_no DESC

【题解】

GROUP BY配合聚合函数使用,按照emp_no分类后,COUNT记录每个emp_no的薪水涨幅次数,最后选出大于15的即可。

COUNT语句后面要跟HAVING哦。

如果对GROUP BY和聚合函数的使用不是很了解的话可以戳这里,我感觉讲的蛮好的。

【代码】

1 SELECT emp_no, COUNT(to_date) AS t
2 FROM salaries GROUP BY emp_no HAVING t > 15

【题解】

DISTINCT:如果作用于某一列,同一列的相同值只会出现一次,如果作用于所有列,那么所有列的相同值都相同才相同(可用于整张表去重)。

ORDER BY:ORDER BY col DESC 按照col列降序排,ASC为升序。

【代码】

1 SELECT DISTINCT salary
2 FROM salaries WHERE to_date = '9999-01-01'
3 ORDER BY salary DESC

【题解】

类似第3题

【代码】

1 SELECT d.dept_no, d.emp_no, s.salary
2 FROM dept_manager d, salaries s
3 WHERE d.emp_no = s.emp_no AND d.to_date = '9999-01-01' AND s.to_date = '9999-01-01'

【题解】

使用NOT IN选出在employees但不在dept_manager中的emp_no记录,NOT IN的话就是顾名思义啦,“不在”的意思。

【代码】

1 SELECT e.emp_no FROM employees e
2 WHERE emp_no NOT IN(SELECT d.emp_no FROM dept_manager d)

【题解】

题意中明确说明如果是manager自己的话不用显示,所以需要加上这一句e.emp_no != m.emp_no,然后按要求连接两张表查询就可以啦。

【代码】

1 SELECT e.emp_no, m.emp_no AS manager_no
2 FROM dept_emp e, dept_manager m
3 WHERE e.dept_no = m.dept_no AND e.to_date = '9999-01-01'
4 AND m.to_date = '9999-01-01' AND e.emp_no != m.emp_no

【题解】

可以通过两步来理解。

第一步:通过emp_no将两个表连接,并挑选出所有部门当前员工工薪,当前是9999-01-01,题里没说,是个小bug。

第二步:因为需要给出所有部门中工薪最高的信息,所以我们按照部门分组,将最高的工薪选出来。

【代码】

1 SELECT d.dept_no, d.emp_no, s.salary
2 FROM dept_emp d, salaries s
3 WHERE d.emp_no = s.emp_no AND d.to_date = '9999-01-01' AND s.to_date = '9999-01-01'
4 GROUP BY d.dept_no HAVING MAX(s.salary)

【题解】

第一步:根据title将表进行分组

第二步:分组后将具有相同title的记录计数,返回>=2的即可。

【代码】

1 SELECT title, COUNT(title) AS t
2 FROM titles
3 GROUP BY title HAVING t >= 2

【题解】

这题首先要理解清楚题目的意思,题意是想我们找到按照title分组后,每组个数大于等于2(并且其中不能包含重复的emp_no)

比如title emp_no

1      1

1       1

这样的COUNT只能算1个哦。所以还是分2步走。

第一步:按照title分组

第二步:利用DISTINCT去掉重复emp_no,然后计数。

【代码】

1 SELECT title, COUNT(DISTINCT emp_no) AS t
2 FROM titles
3 GROUP BY title HAVING t >= 2

【题解】

这题比较简单,直接按题意做就可以啦。

【代码】

1 SELECT * FROM employees
2 WHERE emp_no % 2 = 1 AND last_name != 'Mary'
3 ORDER BY hire_date DESC

【题解】

还是分2步来理解。

第一步:先通过emp_no将两个表连接

第二步:按照title分组,并对同属一个title的salay进行avg运算。

【代码】

1 SELECT t.title, AVG(s.salary) AS avg
2 FROM titles t, salaries s
3 WHERE t.emp_no = s.emp_no AND t.to_date = '9999-01-01' AND s.to_date = '9999-01-01'
4 GROUP BY t.title

【题解】

类似第二题

【代码】

1 SELECT emp_no, salary FROM salaries
2 WHERE to_date = '9999-01-01' AND salary = (
3 SELECT DISTINCT salary FROM salaries
4 ORDER BY salary DESC LIMIT 1, 1
5 )

【题解】

仍然2步走。

第一步:先把两个表通过emp_no连接起来

第二步:因为这里不能用ORDER BY,所以不能再用上面的方法了,我们可以这么想,我先找出一个最大值salary,然后再找比这个最大值小的最大值,那不就是次大值了吗?

然后这里要注意题意是说当前薪水第二多的员工,所以只需s.to_date = '9999-01-01'即可。

【代码】

1 SELECT e.emp_no, MAX(s.salary), e.last_name, e.first_name
2 FROM employees e, salaries s
3 WHERE s.salary < (SELECT MAX(salary) FROM salaries
4 WHERE to_date = '9999-01-01')
5 AND e.emp_no = s.emp_no AND s.to_date = '9999-01-01'

【题解】

第一步:将employees表和dept_emp表通过emp_no进行左外连接,这样不管有没有分配部门的员工号对应的部门号就找出来了。

第二步:用同样的方法就讲dept_emp和departments左外连接,就能得到答案啦。

【代码】

1 SELECT e.last_name, e.first_name, dp.dept_name
2 FROM (employees e LEFT JOIN dept_emp d ON e.emp_no = d.emp_no)
3 LEFT JOIN departments dp ON d.dept_no = dp.dept_no

【题解】

涨幅值即薪水的最大值 - 最小值,知道这个就可以做啦。

【代码】

1 SELECT (MAX(salary) - MIN(salary)) AS growth
2 FROM salaries WHERE emp_no = '10001'

【题解】

做到这题的时候会发现,这次要求的时候所有盐工自入职以来的薪水涨幅情况,所以我们可以新建两张表。

一张是当前员工的薪水,一张是员工入职时的薪水,再将两张表连接一下排序就可以了。

【代码】

1 SELECT now.emp_no, (now.salary - pre.salary) AS growth
2 FROM (SELECT e.emp_no, s.salary FROM employees e, salaries s
3 WHERE e.emp_no = s.emp_no AND s.to_date = '9999-01-01') AS now,
4 (SELECT e.emp_no, s.salary FROM employees e, salaries s
5 WHERE e.emp_no = s.emp_no AND e.hire_date = s.from_date) AS pre
6 WHERE now.emp_no = pre.emp_no
7 ORDER BY growth ASC

【题解】

将三个表连接后,按照dept_no分组,计算有几条salary记录即可。

【代码】

1 SELECT dp.dept_no, dp.dept_name, COUNT(s.salary) AS sum
2 FROM departments dp, dept_emp d, salaries s
3 WHERE dp.dept_no = d.dept_no AND d.emp_no = s.emp_no
4 GROUP BY dp.dept_no

【题解】

这道题我觉得还是挺巧妙的,有必要收藏一下嘻嘻嘻。

首先要将两张salaries表连接,先挑选出to_date = '9999-01-01'的记录,然后注入灵魂的一句话就是s1.salary <= s2.salary,这句话是什么意思呢?比如我在s1表中找到一个salary是100,在s2中就能找到对应的大于等于100的(100,100,300)这样3条数据,然后用DISTINCT去重之后也就是2条,就是该salary的排名啦。

【代码】

1 SELECT s1.emp_no, s1.salary, COUNT(DISTINCT s2.salary) AS rank
2 FROM salaries s1, salaries s2
3 WHERE s1.to_date = '9999-01-01' AND s2.to_date = '9999-01-01' AND s1.salary <= s2.salary
4 GROUP BY s1.emp_no
5 ORDER BY s1.salary DESC, s1.emp_no ASC

【题解】

好像斌不需要用employees表?

因为我只需从salaries表中找出不属于dept_manager表的emp_no,然后让salaries表和dept_emp连接就可以得到非manager员工当前的薪水情况啦。

【代码】

1 SELECT d.dept_no, s.emp_no, s.salary
2 FROM dept_emp d, salaries s
3 WHERE s.emp_no NOT IN (SELECT emp_no FROM dept_manager) AND
4 s.emp_no = d.emp_no AND s.to_date = '9999-01-01'

【题解】

首先将manager表和salaries表连接成一个新表ms,emp表和salaries表也连接成一个新表es。

然后就可以根据题意操作啦,只要es.salary > ms.salary就可以了。

【代码】

1 SELECT es.emp_no, ms.emp_no AS manager_no, es.salary AS emp_salary, ms.salary AS manager_salary
2 FROM (dept_emp AS d INNER JOIN salaries AS s ON d.emp_no = s.emp_no AND s.to_date = '9999-01-01')AS es,
3 (dept_manager AS dm INNER JOIN salaries AS s ON dm.emp_no = s.emp_no AND s.to_date = '9999-01-01')AS ms
4 WHERE es.emp_no NOT IN (SELECT emp_no FROM dept_manager) AND
5 es.dept_no = ms.dept_no AND es.salary > ms.salary

【题解】

这里有几个注意点。

(1)这里统计的是当前员工的当前头衔,所以t.to_date = '9999-01-01',d.to_date = '9999-01-01'

(2)分组也有两个参数,因为首先需要根据部门分组,再在部门里面根据不同的title进行分组统计。

【代码】

1 SELECT d.dept_no, dp.dept_name, t.title, COUNT(t.title) AS count
2 FROM departments dp, dept_emp d, titles t
3 WHERE dp.dept_no = d.dept_no AND d.emp_no = t.emp_no AND t.to_date = '9999-01-01'
4 AND d.to_date = '9999-01-01'
5 GROUP BY d.dept_no, t.title

【题解】

设s1表为员工涨薪之前的表,s2表为员工涨薪之后的表。那么通过emp_no就可以把他们连接起来。然后这道题其实题意不是很明确,他说是每年,但事实上只要年份差为1即可。

因此只需要两个表的to_date相差1,且满足薪水涨幅超过5000就要把它选出来,另外不能忘记如果两个表的from_date相差1,也是需要被选出来的。比如有两个字段(2000-5-05,2001-6-13)和(2001-7-30,2001-8-12)也可以算是相差1年。

【代码】

1 SELECT s2.emp_no, s2.from_date, (s2.salary - s1.salary) AS salary_growth
2 FROM salaries s1, salaries s2
3 WHERE s1.emp_no = s2.emp_no
4 AND salary_growth > 5000
5 AND (strftime("%Y", s2.to_date) - strftime("%Y", s1.to_date) = 1
6 OR strftime("%Y", s2.from_date) - strftime("%Y", s1.from_date) = 1 )
7 ORDER BY salary_growth DESC

【题解】

这题我们可以把每个类别电影数目>=5的类别先找出来建立个虚表cc。

然后经过这几个表的连接就可以得到答案啦。

【代码】

1 SELECT c.name, COUNT(fc.film_id) AS num
2 FROM (SELECT category_id FROM film_category
3 GROUP BY category_id HAVING COUNT(film_id) >=5) AS cc,
4 film f, category c, film_category fc
5 WHERE f.description LIKE '%robot%' AND f.film_id = fc.film_id
6 AND fc.category_id = cc.category_id AND fc.category_id = c.category_id

【题解】

这题还是蛮简单的,只需要通过左连接找到有分类的电影id,然后加个NOT IN就可以了。

【代码】

1 SELECT film_id, title
2 FROM film
3 WHERE film_id NOT IN(
4 SELECT f.film_id FROM film_category AS fc LEFT JOIN film AS f ON fc.film_id = f.film_id)

【题解】

简单题,通过WHERE…IN…将3张表连接即可。

【代码】

1 SELECT title, description FROM film
2 WHERE film_id IN (SELECT film_id FROM film_category WHERE category_id IN
3 (SELECT category_id FROM category WHERE name = 'Action'))

【题解】

explain模拟优化器执行SQL语句,在5.6以及以后的版本中,除过select,其他比如insert,update和delete均可以使用explain查看执行计划,从而知道mysql是如何处理sql语句,分析查询语句或者表结构的性能瓶颈。

【代码】

1 EXPLAIN SELECT * FROM employees

【题解】

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

【代码】

mysql写法:

1 SELECT CONCAT(last_name, " ", first_name) AS Name FROM employees

SQLite写法:

1 SELECT last_name ||" "|| first_name AS Name FROM employees

【题解】

获取系统默认时间是datetime('now','localtime')

【代码】

1 CREATE TABLE actor
2 (
3 actor_id smallint(5) NOT NULL PRIMARY KEY,
4 first_name varchar(45) NOT NULL,
5 last_name varchar(45) NOT NULL,
6 last_update timestamp NOT NULL DEFAULT (datetime('now','localtime'))
7 )

【题解】

只需要知道INSERT INTO 表名 VALUES的用法就可以啦

【代码】

1 INSERT INTO actor VALUES (1, 'PENELOPE', 'GUINESS', '2006-02-15 12:34:33') ,
2 (2, 'NICK', 'WAHLBERG', '2006-02-15 12:34:33')

【题解】

需要知道INSERT OR IGNORE INTO 表名 VALUES…

即如果数据存在就忽略不插入

【代码】

SQLite写法:

1 INSERT OR IGNORE INTO actor VALUES
2 (3, 'ED', 'CHASE', '2006-02-15 12:34:33')

Mysql写法:

1 INSERT IGNORE INTO actor VALUES
2 (3, 'ED', 'CHASE', '2006-02-15 12:34:33')

【题解】

把查询到的内容建表!get√

【代码】

SQLite写法:

1 CREATE TABLE actor_name AS
2 SELECT first_name, last_name FROM actor

MySQL写法:

1 CREATE TABLE actor_name
2 SELECT first_name, last_name FROM actor

【题解】

添加索引的两种方式:(不知道为啥这题第二种过不了)

①修改表:ALTER TABLE 表名 ADD (UNIQUE) INDEX ON 索引名(属性名)

②创建索引:CREATE (UNIQUE) INDEX 索引名 ON 表名(属性名)

【代码】

1 CREATE UNIQUE INDEX uniq_idx_firstname ON actor(first_name);
2 CREATE INDEX idx_lastname ON actor(last_name);
3
4 //
5 ALTER TABLE actor ADD UNIQUE INDEX uniq_idx_firstname(first_name);
6 ALTER TABLE actor ADD INDEX idx_lastname(last_name);

【题解】

创建视图:CREATE VIEW 视图名 AS …

【代码】

1 CREATE VIEW actor_name_view AS
2 SELECT first_name AS first_name_v, last_name AS last_name_v
3 FROM actor

【题解】

SQLite中,使用INDEXED语句进行强制索引查询:

SELECT * FROM salaries INDEXED BY idx_emp_no WHERE emp_no = 10005

MySQL中,使用 FORCE INDEX 语句进行强制索引查询:

SELECT * FROM salaries FORCE INDEX idx_emp_no WHERE emp_no = 10005

【代码】

1 SELECT * FROM salaries
2 INDEXED BY idx_emp_no WHERE emp_no = '10005'

【题解】

修改表结构:ARTER TABLE 表名

添加列:ADD COLUMN 列名 …

删除列:DROP COLUMN 列名

修改列:CHANGE COLUMN 列名 新列名…

【代码】

1 ALTER TABLE actor
2 ADD COLUMN create_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00'