部分答案来自牛客网讨论分析
SELECT c.name, COUNT(fc.film_id) FROM
(select category_id, COUNT(film_id) AS category_num FROM
film_category GROUP BY category_id HAVING count(film_id)>=5) AS cc,
film AS f, film_category AS fc, category AS c
WHERE f.description LIKE '%robot%'
AND f.film_id = fc.film_id
AND c.category_id = fc.category_id
AND c.category_id=cc.category_id
答案来自牛客网,本题需用到虚表,先Mark,以后细读
1、找到对应电影数量>=5的所有分类,建立成虚表cc:(select category_id, count(film_id) as category_num from film_category group by category_id having count(film_id)>=5) as cc
2、设定限制条件 f.description like ‘%robot%’
3、在表cc、f、fc、c中查找包括robot的电影对应的分类名称和对应的电影数目。
SELECT f.film_id,f.title FROM film f LEFT JOIN film_category fc ON f.film_id = fc.film_id WHERE fc.category_id IS NULL;
解题思路是运用 LEFT JOIN 连接两表,用 IS NULL 语句限定条件:
1、用 LEFT JOIN 连接 film 和 film_category,限定条件为 f.film_id = fc.film_id,即连接电影 id 和电影分类 id,如果电影没有分类,则电影分类 id 显示 null
2、再用 WHERE 来限定条件 fc.category_id IS NULL 选出没分类的电影
SELECT f.title,f.description FROM category c INNER JOIN film_category fc ON c.category_id = fc.category_id INNER JOIN film f ON f.film_id = fc.film_id WHERE c.name = 'Action';
本题题目要求使用子查询,我上面写的是非子查询的语句,使用子查询如下:select f.title,f.description from film as f
where f.film_id in (select fc.film_id from film_category as fc
where fc.category_id in (select c.category_id from category as c
where c.name = ‘Action’));
EXPLAIN SELECT * FROM employees;
easy的题,考的是explain关键字,在SQLite数据库中,可以用 “EXPLAIN” 关键字或 “EXPLAIN QUERY PLAN” 短语,用于描述表的细节
SELECT last_name||" "||first_name Name FROM employees;
注意:1、牛客网后台的判题系统使用的是SQLite数据库,该数据库不支持CONCAT内置函数。不同数据库连接字符串的方法不完全相同,MySQL、SQL Server、Oracle等数据库支持CONCAT方法,而本题所用的SQLite数据库只支持用连接符号"||"来连接字符串
2、连接空格时注意要使用双引号“”而不是单引号‘’
CREATE TABLE 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'))
)
做这题的时候碰到一个问题,字段类型关键字smallint、varchar等使用大写字母时代码报错,使用SQLite定义字段类型时只能用小写。
根据题意,本题关键点是actor_id的主键设置与last_update的默认获取系统时间:
1、在actor_id字段末尾加上PRIMARY KEY是将该字段设置为主键,或者在表的最后一行加上PRIMARY KEY(actor_id)
2、在last_update末尾加上DEFAULT是为该字段设置默认值,且默认值为(datetime(‘now’,‘localtime’)),即获得系统时间,注意最外层的括号不可省略
INSERT INTO actor
VALUES (1, 'PENELOPE', 'GUINESS', '2006-02-15 12:34:33'),
(2, 'NICK', 'WAHLBERG', '2006-02-15 12:34:33')
INSERT OR IGNORE INTO actor VALUES(3,'ED','CHASE','2006-02-15 12:34:33');
如果不存在则插入,如果存在则忽略
INSERT OR IGNORE INTO tablename VALUES(…);
如果不存在则插入,如果存在则替换
INSERT OR REPLACE INTO tablename VALUES(…);
这里指的存在表示的是unique属性的列值存在的情况下,unique表示键值唯一
因为题目判定系统使用的是sqlite3,所以必须按sqlite3的写法来做,如果是mysql,那么把or去掉,像下面这样
CREATE TABLE actor_name
(
first_name varchar(45) NOT NULL,
last_name varchar(45) NOT NULL
);
INSERT INTO actor_name VALUES ('PENELOPE','GUINESS'),('NICK','WAHLBERG');
CREATE UNIQUE INDEX uniq_idx_firstname ON actor(first_name);
CREATE INDEX idx_lastname ON actor(last_name);
CREATE VIEW actor_name_view AS SELECT first_name first_name_v,last_name last_name_v FROM actor;
SELECT * FROM salaries INDEXED BY idx_emp_no WHERE emp_no = 10005;
SQLite中,使用 INDEXED BY 语句进行强制索引查询,可参考:
http://www.runoob.com/sqlite/sqlite-indexed-by.html
MySQL中,使用 FORCE INDEX 语句进行强制索引查询,可参考:
http://www.jb51.net/article/49807.htm
ALTER TABLE actor ADD COLUMN create_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00';
注意:本题题目中给出的datetime的默认值格式有误,应为’0000-00-00 00:00:00’
CREATE TRIGGER audit_log AFTER INSERT ON employees_test BEGIN INSERT INTO audit VALUES(NEW.ID,NEW.NAME); END;
DELETE FROM titles_test WHERE id NOT IN (SELECT MIN(id) FROM titles_test GROUP BY emp_no);
本题思路如下:先用 GROUP BY 和 MIN() 选出每个 emp_no 分组中最小的 id,然后用 DELETE FROM … WHERE … NOT IN … 语句删除 “非每个分组最小id对应的所有记录”
UPDATE titles_test SET to_date = NULL,from_date = '2001-01-01';
用 UPDATE 语句更新若干列的最基本用法,详细可参考:
http://www.w3school.com.cn/sql/sql_update.asp
https://www.w3schools.com/sql/sql_update.asp
另外要注意若干列 to_date = NULL 和 from_date = ‘2001-01-01’ 之间只能用逗号连接,切勿用 AND 连接。
REPLACE INTO titles_test VALUES (5, 10005, 'Senior Engineer', '1986-06-26', '9999-01-01')
ALTER TABLE titles_test RENAME TO titles_2017;
DROP TABLE audit;
CREATE TABLE audit(
EMP_no INT NOT NULL,
create_date datetime NOT NULL,
FOREIGN KEY(EMP_no) REFERENCES employees_test(ID));
由于SQLite中不能通过 ALTER TABLE … ADD FOREIGN KEY … REFERENCES … 语句来对已创建好的字段创建外键,因此只能先删除表,再重新建表的过程中创建外键。可参考:
http://www.sqlite.org/foreignkeys.html
https://zhidao.baidu.com/question/350164703.html
SELECT e.* FROM employees e,emp_v ev WHERE e.emp_no = ev.emp_no;
SELECT * FROM employees INTERSECT SELECT * FROM emp_v
注意:本题解法很多,最常用也最容易想到的两种如下:
1、用 WHERE 选取二者 emp_no 相等的记录
2、用 INTERSECT 关键字求 employees 和 emp_v 的交集
UPDATE salaries SET salary = salary * 1.1 WHERE emp_no IN
(SELECT s.emp_no FROM salaries AS s INNER JOIN emp_bonus AS eb
ON s.emp_no = eb.emp_no AND s.to_date = '9999-01-01')
最标准的写法
SELECT "select count(*) from " || name || ";" AS cnts
FROM sqlite_master WHERE type = 'table'
注意:本题没看懂什么意思,答案来自牛客网,先Mark
本题主要有以下两个关键点:
1、在 SQLite 系统表 sqlite_master 中可以获得所有表的索引,其中字段 name 是所有表的名字,而且对于自己创建的表而言,字段 type 永远是 ‘table’,详情可参考:
http://blog.csdn.net/xingfeng0501/article/details/7804378
SELECT last_name||"'"||first_name FROM employees;
这题没什么好说的,跟前面有一题类似,SQLite数据库中,只支持用连接符号"||"来连接字符串,不支持用函数连接
SELECT (length("10,A,B")-length(replace("10,A,B",",","")))/length(",") AS cnt
技巧题,答案来自牛客网。
由于 SQLite 中没有直接统计字符串中子串出现次数的函数,因此本题用length()函数与replace()函数的结合灵活地解决了统计子串出现次数的问题,属于技巧题,即先用replace函数将原串中出现的子串用空串替换,再用原串长度减去替换后字符串的长度,最后除以子串的长度(本题中此步可省略,若子串长度大于1则不可省)。详情请参考:
http://www.cnblogs.com/huangtailang/p/5cfbd242cae2bcc929c81c266d0c875b.html
http://sqlite.org/lang_corefunc.html#replace
SELECT first_name FROM employees ORDER BY SUBSTR(first_name,LENGTH(first_name)-1) ASC;
SELECT first_name FROM employees ORDER BY SUBSTR(first_name,-2);
以上两种写法均可,SUBSTR的参数不同而已,效果一样。
SELECT dept_no,GROUP_CONCAT(emp_no) employees FROM dept_emp GROUP BY dept_no;
常规题,没什么好说的。
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)
注意:牛客网讨论组反映此题有问题,有待观望。
SELECT * FROM employees LIMIT 5,5
注意:划重点,分页在数据库中的实现。经常用。
根据题意,每行5页,返回第2页的数据,即返回第6~10条记录,以下有两种方法可以解决:
方法一:利用 LIMIT 和 OFFSET 关键字。LIMIT 后的数字代表返回几条记录,OFFSET 后的数字代表从第几条记录开始返回(第一条记录序号为0),也可理解为跳过多少条记录后开始返回。
1
SELECT * FROM employees LIMIT 5 OFFSET 5
方法二:只利用 LIMIT 关键字。注意:在 LIMIT X,Y 中,Y代表返回几条记录,X代表从第几条记录开始返回(第一条记录序号为0),切勿记反。
1
SELECT * FROM employees LIMIT 5,5
SELECT em.emp_no, de.dept_no, eb.btype, eb.recevied
FROM employees AS em INNER JOIN dept_emp AS de
ON em.emp_no = de.emp_no
LEFT JOIN emp_bonus AS eb
ON de.emp_no = eb.emp_no
注意:以上是标准写法。
本题严谨的思路为,先将 employees与dept_emp 用 INNER JOIN 连接,挑选出分配了部门的员工,再用 LEFT JOIN 连接 emp_bonus(在前面的题中可看到此表),分配了奖金的员工显示奖金类型和授予时间,没分配奖金的员工则不显示。
SELECT * FROM employees WHERE NOT EXISTS
(SELECT emp_no FROM dept_emp WHERE emp_no = employees.emp_no)
还可以使用NOT IN
SELECT * FROM employees WHERE emp_no NOT IN (SELECT emp_no FROM dept_emp)
SELECT e.* FROM employees e,emp_v ev WHERE e.emp_no = ev.emp_no;
SELECT e.emp_no, e.first_name, e.last_name, b.btype, s.salary,
(CASE b.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 INNER JOIN emp_bonus AS b ON e.emp_no = b.emp_no
INNER JOIN salaries AS s ON e.emp_no = s.emp_no AND s.to_date = '9999-01-01'
又是一道比较麻烦的题。老规矩,先Mark。答案来自牛客网。
本题主要考查 SQLite 中 CASE 表达式的用法。即当 btype = 1 时,得到 salary * 0.1;当 btype = 2 时,得到 salary * 0.2;其他情况得到 salary * 0.3。详细用法请参考:
http://www.sqlite.org/lang_expr.html 中的【The CASE expression】
http://www.2cto.com/database/201202/120267.html 中的【条件表达式】
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
本题的思路为复用 salaries 表进行子查询,最后以 s1.emp_no 排序输出求和结果。
1、输出的第三个字段,是由一个 SELECT 子查询构成。将子查询内复用的 salaries 表记为 s2,主查询的 salaries 表记为 s1,当主查询的 s1.emp_no 确定时,对子查询中不大于 s1.emp_no 的 s2.emp_no 所对应的薪水求和
2、注意是对员工当前的薪水求和,所以在主查询和子查询内都要加限定条件 to_date = ‘9999-01-01’
又是一道比较典型的题目,需要理解记忆一下,答案来自牛客网,Mark。
SELECT e1.first_name FROM
(SELECT e2.first_name,
(SELECT COUNT(*) FROM employees AS e3
WHERE e3.first_name <= e2.first_name)
AS rowid FROM employees AS e2) AS e1
WHERE e1.rowid % 2 = 1
又一道送命题,题目看起来异常简单,实现过程较为复杂。参见牛客网分析解答:
首先题目的叙述有问题,导致理解有误,输出的数据与参考答案不同。先给出正确的题目叙述:【对于employees表,在对first_name进行排名后,选出奇数排名对应的first_name】。
1、本题用到了三层 SELECT 查询,为了便于理解,采用缩进方式分层,且最外层对应e1,最内层对应e3;
2、在e3层中,采用 COUNT() 函数对 e2.first_name 进行排名标号,即在给定 e2.first_name的情况下,不大于 e2.first_name 的 e3.first_name 的个数有多少,该个数刚好与 e2.first_name 的排名标号匹配,且将该值命名为 rowid;
/注意:排名标号后并未排序,即[Bob, Carter, Amy]的排名是[2,3,1],选取奇数排名后输出[Carter, Amy],所以可见参考答案中的first_name并未按字母大小排序/
3、在e1层中,直接在限定条件 e1.rowid % 2 = 1 下,代表奇数行的 rowid,选取对应的 e1.first_name;
4、e2层则相当于连接e1层(选取表示层)与e3层(标号层)的桥梁。
至此,牛客网数据库专题已经走完一遍,感受下来就是数据库博大精深(一脸苦楚状。。。),这些题目中还没涉及到存储过程等高阶数据库操作,都是些数据库的基本操作,虽然如今实际应用中不一定会用到部分题目中那么复杂的数据库操作(现在基本上都会屏蔽数据库的复杂操作,将其放到后台代码逻辑中),但是这些基本的数据库操作还是需要熟悉了解。部分题目具有一定技巧性,值得细细体会。
PS:在刷题过程中遇到坑,看到别人的思路豁然开朗,牛人太多啊,要学的还有很多啊。。。谨以此文Mark一下我自己的数据库复习过程(虽然以前很水的学过。。。)。