网上流传较广的50道SQL训练,前18道题的难度依次递增,从19题开始的后半部分算是循环练习和额外function的附加练习,难度恢复到普通状态。
以下答案都是我自己实践后写出来的,当前参考了网上其他的答案,目的是为了练习SQL,让自己写SQL语句变得熟练。
以下SQL语句用的全是MySQL ,版本 5.7.16
CREATE TABLE `Student` (
`SId` varchar(10) DEFAULT NULL,
`Sname` varchar(10) DEFAULT NULL,
`Sage` datetime DEFAULT NULL,
`Ssex` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO Student (SId,Sname,Sage,Ssex) VALUES
('01','赵雷','1990-01-01 00:00:00.000','男')
,('02','钱电','1990-12-21 00:00:00.000','男')
,('03','孙风','1990-12-20 00:00:00.000','男')
,('04','李雷','1990-12-06 00:00:00.000','男')
,('05','周梅','1991-12-01 00:00:00.000','女')
,('06','吴兰','1992-01-01 00:00:00.000','女')
,('07','郑竹','1989-01-01 00:00:00.000','女')
,('09','张三','2017-12-20 00:00:00.000','女')
,('10','李四','2017-12-25 00:00:00.000','女')
,('11','韩梅梅','2012-06-06 00:00:00.000','女')
,('12','赵六','2013-06-13 00:00:00.000','女')
,('13','孙七','2014-06-01 00:00:00.000','女')
,('08','聂小倩','2019-12-12 12:12:12.000','女')
;
CREATE TABLE `Course` (
`CId` varchar(10) DEFAULT NULL,
`Cname` varchar(10) DEFAULT NULL,
`TId` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO Course (CId,Cname,TId) VALUES
('01','语文','02')
,('02','数学','01')
,('03','英语','03')
;
CREATE TABLE `Teacher` (
`TId` varchar(10) DEFAULT NULL,
`Tname` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO Teacher (TId,Tname) VALUES
('01','张三')
,('02','李四')
,('03','王五')
;
CREATE TABLE `SC` (
`SId` varchar(10) DEFAULT NULL,
`CId` varchar(10) DEFAULT NULL,
`score` decimal(18,1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO SC (SId,CId,score) VALUES
('01','01',80.0)
,('01','02',90.0)
,('01','03',99.0)
,('02','01',70.0)
,('02','02',60.0)
,('02','03',80.0)
,('03','01',80.0)
,('03','02',80.0)
,('03','03',80.0)
,('04','01',50.0)
,('04','02',30.0)
,('04','03',20.0)
,('05','01',76.0)
,('05','02',87.0)
,('06','01',31.0)
,('06','03',34.0)
,('07','02',89.0)
,('07','03',98.0)
,('08','01',77.0)
,('08','02',44.0)
,('08','03',59.0)
,('09','01',25.0)
,('09','03',89.0)
;
答案不一定对,如有错误,或有更优解法,欢迎留言/邮件交流。
-- 1题
SELECT st.*,t3.t1score as 课程1分数,t3.t2score as 课程2分数
FROM Student st RIGHT JOIN (
SELECT t1.SId,t1.score t1score,t2.score t2score FROM
(SELECT SId,score FROM SC WHERE CId="01") t1
JOIN
(SELECT SId,score FROM SC WHERE CId="02") t2
ON t1.SId=t2.SId
WHERE t1.score>t2.score
)t3 ON st.SId=t3.SId;
-- 1.1题
-- 查询出SId,如果要学生的其他信息,再关联Student表即可
SELECT t1.SId FROM
(SELECT SId FROM SC WHERE CId="01") t1
JOIN
(SELECT SId FROM SC WHERE CId="02") t2
ON t1.SId=t2.SId;
-- 1.2题
-- 是可能不存在,即可存在,可不存在
SELECT * FROM
(SELECT * FROM SC WHERE CId="01") t1
LEFT JOIN
(SELECT * FROM SC WHERE CId = "02") t2
ON t1.SId=t2.SId;
-- 1.3
SELECT * FROM SC WHERE SId NOT IN (
SELECT SId FROM SC WHERE CId="01"
) AND CId="02";
-- 2题
SELECT st.SId,st.Sname,AVG(s.score) as 平均分 FROM SC s JOIN Student st ON s.SId=st.SId
GROUP BY s.SId HAVING AVG(s.score)>=60;
-- 3题
SELECT * FROM Student WHERE SId IN (SELECT SId FROM SC);
-- 4题
-- 注意是所有学生,所以要student的left join
-- 总分一列做了判断,如果总分是null,替换成0
SELECT st.SId,st.Sname,COUNT(CId) as 选课总数,SUM(case when s.score is null then 0 else s.score end) as 课程总分
FROM Student st
LEFT JOIN SC s ON s.SId=st.SId
GROUP BY SId;
-- 4.1题
-- 有成绩,没有说明是多少门课的成绩,只要有一门课有成绩即可,即是只要出现在sc表上的sid都是
SELECT * FROM Student WHERE SId IN (SELECT SId FROM SC);
-- 5题
SELECT COUNT(*) FROM Teacher WHERE Tname LIKE "李%";
-- 6题
SELECT st.* FROM Student st JOIN SC s ON st.SId=s.SId
JOIN Course c ON c.CId = s.CId
JOIN Teacher t ON t.TId = c.TId
WHERE t.Tname = "张三";
-- 7题
SELECT SId FROM SC s
GROUP BY SId
HAVING COUNT(CId) < (SELECT COUNT(CId) FROM Course);
-- 8题
SELECT DISTINCT st.SId,st.Sname,st.Sage,st.Ssex
FROM SC s JOIN Student st ON s.SId=st.SId
WHERE s.CId IN ( SELECT CId FROM SC WHERE SId="01")
AND s.SId != "01";
-- 9题
-- group_concat是在group by语句中求某个字段的集合
-- t2表查询出学号是01的同学的课程号的集合ccid2,t1表是所有学生的课程号集合ccid1,两表关联,取ccid1=ccid2,即得到所有课程相同的学生学号
-- 最后查询student表中等于这些学号的学生信息
SELECT st.SId,st.Sname,st.Sage,st.Ssex FROM Student st
WHERE SId IN (
SELECT t1.SId FROM
(SELECT SId,group_concat(CId) as ccid1 FROM SC GROUP BY SId) t1
JOIN
(SELECT group_concat(CId) ccid2 FROM SC WHERE SId="01" GROUP BY SId) t2
ON t1.ccid1=t2.ccid2
WHERE t1.SId != "01"
);
-- 10题
SELECT Sname FROM Student WHERE SId NOT IN(
SELECT DISTINCT s.SId from Course c JOIN SC s ON c.CId=s.CId
JOIN Teacher t ON c.TId=t.TId
WHERE t.Tname = "张三"
);
-- 11题
-- 这个是错的
select st.SId,st.Sname,AVG(score) as 平均分 from SC JOIN Student st ON SC.SId=st.SId
WHERE score<60
GROUP BY SId HAVING COUNT(*)>=2;
-- 这个才对
SELECT s.SId,st.Sname,AVG(score) as 平均分 FROM SC s JOIN
(SELECT SId FROM SC WHERE score<60 GROUP BY SId HAVING COUNT(score)>1) t
ON s.SId=t.SId
JOIN Student st ON s.SId=st.SId
GROUP BY t.SId;
-- 12题
select * from SC s join Student st on s.Sid=st.Sid
WHERE s.score<60 AND s.CId="01"
ORDER BY score DESC;
-- 13题
SELECT s1.SId,s1.CId,s1.score,s2.avgScore from SC s1 JOIN (
SELECT SId,AVG(score) as avgScore from SC GROUP BY SId ) s2
ON s1.SId=s2.SId
ORDER BY avgScore DESC;
-- 14题
select CId as 课程号,COUNT(CId) as 选修人数,MAX(score) as 最高分,MIN(score) as 最低分,AVG(score) as 平均分,
SUM(CASE WHEN score>=60 THEN 1 ELSE 0 END)/COUNT(CId) as 及格率,
SUM(CASE WHEN score>=70 and score<80 THEN 1 ELSE 0 END)/COUNT(CId) as 中等率,
SUM(CASE WHEN score>=80 AND score<90 THEN 1 ELSE 0 END)/COUNT(CId) as 优良率,
SUM(CASE WHEN score>=90 THEN 1 ELSE 0 END)/COUNT(CId) as 优秀率
from SC
GROUP BY CId
ORDER BY CId ASC;
-- 15题
-- 分数相同,名次相同,保留名次空缺,即相同分数的后一个名次会隔一个名次
SELECT a.*,COUNT(b.score)+1 as rank FROM SC a left JOIN SC b
ON a.CId=b.CId AND a.score<b.score
GROUP BY a.CId,a.SId,a.score
ORDER BY a.CId,rank ASC;
-- 分数相同,名次相同,但是不保留名次空缺,即相同分数后的名次后一个名次是连续的
SELECT a.*,COUNT(a.score) AS rank FROM SC a LEFT JOIN SC b
ON a.CId=b.CId AND a.score<b.score
GROUP BY a.SId,a.CId,a.score
ORDER BY a.CId,a.score DESC
-- 16题
-- 思路是先按照总分排序,然后加上序号,和15题的按照课程排名不同
-- 分数相同时,名次不同,按照学号大小排序
set @i=0;
SELECT t.SId,t.sumScore,@i:=@i+1 as rank FROM(
SELECT SId,SUM(score) sumScore FROM SC
GROUP BY SId ORDER by sumScore DESC
) t;
-- 分数重复时,名次相同,但不保留名次空缺,即名次会连续
set @i=0;
set @r=1;
SELECT t.SId,t.sumScore,
(CASE WHEN @r=t.sumScore THEN @i
WHEN @r:=t.sumScore THEN @i:=@i+1 END) as rank
FROM(
SELECT SId,SUM(score) sumScore FROM SC
GROUP BY SId ORDER by sumScore DESC
) t;
-- 分数重复时,名次相同,并且保留名次空缺,即会跳过一个名次
set @i=0;
set @r=1;
set @a=0;
SELECT t.SId,t.sumScore,@a:=@a+1,
(CASE WHEN @r=t.sumScore THEN @i
WHEN @r:=t.sumScore THEN @i:=@a END) as rank
FROM(
SELECT SId,SUM(score) sumScore FROM SC
GROUP BY SId ORDER by sumScore DESC
) t;
-- 17题
SELECT s.CId,c.Cname,COUNT(s.CId),
SUM(CASE WHEN s.score>=85 THEN 1 END)/COUNT(s.CId) as "[100-85]百分比",
SUM(CASE WHEN s.score>=70 AND s.score<85 THEN 1 END)/COUNT(s.CId) as "[85-70]百分比",
SUM(CASE WHEN s.score>=60 AND s.score<70 THEN 1 END)/COUNT(s.CId) as "[70-60]百分比",
SUM(CASE WHEN s.score<60 THEN 1 END)/COUNT(s.CId) as "[60-0]百分比"
FROM SC s JOIN Course c ON s.CId=c.CId
GROUP BY s.CId;
-- 18题
-- 难度较大,需要一步步拆解sql,先输出前两行的结果,再输出前三行的结果以此类推,就知道为什么了
SELECT a.* FROM SC a LEFT JOIN SC b
ON a.CId=b.CId AND a.score<b.score
GROUP BY a.CId,a.SId
HAVING COUNT(b.CId)<3
ORDER BY a.CId,a.score DESC;
-- 19 题
SELECT CId,COUNT(SId) FROM SC GROUP BY CId;
-- 20题
SELECT s.SId,st.Sname FROM SC s JOIN Student st on s.SId=st.SId
GROUP BY SId HAVING COUNT(CId)=2;
-- 21题
SELECT Ssex,COUNT(Ssex) FROM Student GROUP BY Ssex;
-- 22题
SELECT * FROM Student WHERE Sname LIKE "%风%";
-- 23题
SELECT * FROM Student s1 LEFT JOIN Student s2 ON s1.Sname=s2.Sname;
-- 24题
SELECT * FROM Student WHERE YEAR(Sage)=1990;
-- 25题
SELECT CId,AVG(score) avscore FROM SC
GROUP BY CId
ORDER BY avscore DESC, CId ASC;
-- 26题
SELECT s.SId,st.Sname,AVG(score) as avgScore
FROM SC s JOIN Student st ON s.SId=st.SId
GROUP BY SId HAVING AVG(score)>=85;
-- 27题
SELECT st.Sname,s.score FROM SC s
JOIN Course c ON s.CId=c.CId
JOIN Student st ON s.SId=st.SId
WHERE c.Cname="数学" AND s.score<60;
-- 28题
SELECT s.SId,s.CId,c.Cname,s.score FROM SC s
LEFT JOIN Course c ON s.CId = c.CId;
-- 29 题
SELECT st.Sname,c.Cname,s.score FROM SC s JOIN Student st ON s.SId=st.SId
JOIN Course c ON s.CId=c.CId
GROUP BY s.SId HAVING MAX(s.score)>70;
-- 30题
-- 即对于一门课程,有一个学生不及格就符合条件
SELECT DISTINCT CId FROM SC WHERE score<60;
-- 31题
SELECT st.Sname,st.SId,s.score FROM SC s JOIN Student st on s.SId=st.SId
WHERE CId="01" AND score>=80;
-- 32题
SELECT CId,COUNT(CId) FROM SC GROUP BY CId;
-- 33题
SELECT st.*,c.Cname,MAX(score) FROM SC s JOIN Course c ON s.CId=c.CId
JOIN Teacher t ON c.TId=t.TId
JOIN Student st ON s.SId=st.SId
WHERE t.Tname="张三"
GROUP BY s.CId LIMIT 1;
-- 34题
-- 和上一题不同,当最高分有多名同学时,会全部查找出来
SELECT st.*,s.score FROM SC s JOIN (
SELECT MAX(score) maxScore,c.CId ccid FROM SC s JOIN Course c ON s.CId=c.CId
JOIN Teacher t ON t.TId=c.TId
WHERE t.Tname="张三"
) t ON s.CId=t.ccid and s.score=t.maxScore
JOIN Student st ON s.SId=st.SId;
-- 35题
-- group by sid,score之后,把相同的课程号拼接起来
-- 拼接起来之后是字符类型的,如果有两个结果,就是有相同分数的课程了,把包含逗号的查找啊出来即可
SELECT * FROM
(SELECT a.SId, group_concat(a.CId) AS ccid,a.score FROM SC a
GROUP BY a.SId,a.score) t
WHERE t.ccid LIKE "%,%";
-- 36题
-- 同18题,当前两名分数相同时,只显示这两名
SELECT a.* FROM SC a LEFT JOIN SC b
ON a.CId=b.CId AND a.score<b.score
GROUP BY a.CId,a.SId
HAVING COUNT(b.CId)<2
ORDER BY a.CId,a.score DESC;
-- 37题
SELECT CId,COUNT(CId) AS 选课人数 FROM SC GROUP BY CId HAVING COUNT(SId)>5;
-- 38题
SELECT SId FROM SC GROUP BY SId HAVING COUNT(SId)>=2;
-- 39题
-- 有一个前提,一个同学不能选修同一门课两次
SELECT st.* FROM SC s JOIN Student st ON s.SId=st.SId
GROUP BY SId
HAVING COUNT(s.CId)=(SELECT COUNT(*) FROM Course);
-- 40题
-- NOW() yyyy-MM-dd HH:mm:ss
-- CURDATE() yyyy-MM-dd
-- CURTIME() HH:mm:ss
SELECT Sname,YEAR(NOW())-YEAR(Sage)as 年龄 FROM Student;
-- 41题
-- 注意,datediff是比较年月日,所以只能通过判断来比较月和日的大小
-- TIMESTAMPDIFF
SELECT Sname,(CASE WHEN
MONTH(Sage)>MONTH(CURDATE()) OR (MONTH(CURDATE())=MONTH(Sage) AND DAY(Sage)>DAY(CURDATE()))
THEN YEAR(NOW())-YEAR(Sage)-1
ELSE YEAR(NOW())-YEAR(Sage) END) as 年龄
FROM Student;
-- 比较简洁
SELECT Sname,TIMESTAMPDIFF(YEAR,Sage,CURDATE())as 年龄 FROM Student;
-- 42题
-- week() 函数,返回日期在一年中的周数
-- 每年的同一天周数可能不相同,即1890年的12月12日和今年的12月12日的周数可能不同,所以不能用简单的week函数
-- 可能会不对 SELECT SId,Sname FROM Student WHERE WEEK(Sage)=WEEK(NOW());
SELECT SId,Sname FROM Student
WHERE WEEK(CONCAT(YEAR(NOW()),'-',MONTH(Sage),'-',DAY(Sage)))=WEEK(NOW());
-- 43题
-- 不能是简单的week()+1,要考虑一年最后一周的情况
-- 过去的今天可能和现在不是同一周,原因同42题
SELECT SId,Sname FROM Student
WHERE WEEK(CONCAT(YEAR(NOW()),'-',MONTH(Sage),'-',DAY(Sage)))=(CASE WHEN WEEK(NOW())=52 THEN 1 ELSE WEEK(NOW())+1 END);
-- 44题
SELECT SId,Sname FROM Student
WHERE MONTH(NOW())=MONTH(Sage);
-- 45题
-- 不能简单的month()+1,因为如果现在是12月的话,那+1结果就是13了
SELECT SId,Sname FROM Student
WHERE (case WHEN MONTH(NOW())=12 THEN 1 ELSE MONTH(NOW())+1 END) = MONTH(Sage);
-- 增加一题,查找7天内过生日的学生
-- 不用考虑周,今天开始7天内的都算
SELECT SId,Sname FROM Student WHERE datediff(Sage,NOW()) BETWEEN 0 AND 7;