工作之后发现自己已经不太会写sql了,除了业务方面的学习,技术上也不能落下啊。于是打算做一遍这50题,通过实际操作来加深对sql的理解。
CREATE TABLE student (
sid varchar(10),
sname varchar(10),
sage datetime,
ssex varchar(10)
);
insert into student values('01' , '赵雷' , '1990-01-01' , '男');
insert into student values('02' , '钱电' , '1990-12-21' , '男');
insert into student values('03' , '孙风' , '1990-05-20' , '男');
insert into student values('04' , '李云' , '1990-08-06' , '男');
insert into student values('05' , '周梅' , '1991-12-01' , '女');
insert into student values('06' , '吴兰' , '1992-03-01' , '女');
insert into student values('07' , '郑竹' , '1989-07-01' , '女');
insert into student values('08' , '王菊' , '1990-01-20' , '女');
CREATE TABLE course (
cid varchar(10),
cname varchar(10),
tid varchar(10)
);
insert into course values('01' , '语文' , '02');
insert into course values('02' , '数学' , '01');
insert into course values('03' , '英语' , '03');
CREATE TABLE teacher (
tid varchar(10),
tname varchar(10)
);
insert into teacher values('01' , '张三');
insert into teacher values('02' , '李四');
insert into teacher values('03' , '王五');
CREATE TABLE sc (
sid varchar(10),
cid varchar(10),
score decimal(18, 1)
);
insert into sc values('01' , '01' , 80);
insert into sc values('01' , '02' , 90);
insert into sc values('01' , '03' , 99);
insert into sc values('02' , '01' , 70);
insert into sc values('02' , '02' , 60);
insert into sc values('02' , '03' , 80);
insert into sc values('03' , '01' , 80);
insert into sc values('03' , '02' , 80);
insert into sc values('03' , '03' , 80);
insert into sc values('04' , '01' , 50);
insert into sc values('04' , '02' , 30);
insert into sc values('04' , '03' , 20);
insert into sc values('05' , '01' , 76);
insert into sc values('05' , '02' , 87);
insert into sc values('06' , '01' , 31);
insert into sc values('06' , '03' , 34);
insert into sc values('07' , '02' , 89);
insert into sc values('07' , '03' , 98);
查询"01"课程比"02"课程成绩高的学生的信息及课程分数
keyword : 自连接(SELF JOIN)
SELECT student.*, sc1.score AS 'course-01', sc2.score AS 'course-02'
FROM student, sc sc1, sc sc2
WHERE student.sid = sc1.sid
AND sc1.sid = sc2.sid
AND sc1.cid = '01'
AND sc2.cid = '02'
AND sc1.score > sc2.score
1.1 查询存在" 01 "课程但可能不存在" 02 "课程的情况(不存在时显示为 null )
keyword : LEFT JOIN
SELECT *
FROM
( SELECT * FROM sc WHERE sc.cid = '01' ) sc1
LEFT JOIN ( SELECT * FROM sc WHERE sc.cid = '02' ) sc2 ON sc1.sid = sc2.sid
1.2 查询同时存在01和02课程的情况
keyword : 子查询
SELECT *
FROM
( SELECT * FROM sc WHERE sc.cid = '01' ) sc1,
( SELECT * FROM sc WHERE sc.cid = '02' ) sc2
WHERE sc1.sid = sc2.sid
1.3 查询选择了02课程但没有01课程的情况
keyword : NOT IN
SELECT *
FROM sc
WHERE cid = '02' AND sid NOT IN ( SELECT sid FROM sc WHERE cid = '01' )
2.查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩
keyword : GROUP BY、HAVING
--注意版本默认的model问题
SELECT sc.sid,s.sname,avg(score)
FROM student s INNER JOIN sc ON s.sid = sc.sid
GROUP BY sid
HAVING (avg(score) >= 60)
3.查询在 SC 表存在成绩的学生信息
keyword : DISTINCT
SELECT DISTINCT student.*
FROM sc,student
WHERE sc.sid = student.sid
4.查询所有同学的学生编号、学生姓名、选课总数、所有课程的成绩总和
keyword : INNER JOIN、COUNT()、SUM()
SELECT s.sid, s.sname,COUNT(sc.cid),SUM(sc.score)
FROM student s INNER JOIN sc ON s.sid = sc.sid
GROUP BY sc.sid
5.查询「李」姓老师的数量
keyword : COUNT()、LIKE
SELECT COUNT(1)
FROM teacher
WHERE tname LIKE ('李%');
6.查询学过「张三」老师授课的同学的信息
keyword : 多表连接
SELECT s.*
FROM student s INNER JOIN sc ON s.sid = sc.sid
INNER JOIN course c ON sc.cid = c.cid
INNER JOIN teacher t ON c.tid = t.tid
WHERE t.tname = '张三'
7.查询没有学全所有课程的同学的信息
keyword : LEFT JOIN、HAVING 、COUNT()
SELECT s.*,COUNT(sc.cid)
FROM student s LEFT JOIN sc ON s.sid = sc.sid
GROUP BY sc.sid
HAVING COUNT(sc.cid) < (
SELECT COUNT(1) FROM course
)
8.查询至少有一门课与学号为" 01 "的同学所学相同的同学的信息
keyword : INNER JOIN、GROUP BY
SELECT s.sid,s.sname
FROM student s INNER JOIN sc on s.sid = sc.sid
WHERE sc.cid IN (
SELECT cid FROM sc WHERE sid = '01'
) AND s.sid != '01'
GROUP BY sid
9.查询和" 01 "号的同学学习的课程完全相同的其他同学的信息
keyword : GROUP_CONCAT、ORDER BY
SELECT s.sid,sname,ssex,GROUP_CONCAT( cid ORDER BY cid )
FROM sc INNER JOIN student s ON sc.sid = s.sid
GROUP BY sc.sid
HAVING GROUP_CONCAT(cid ORDER BY cid) = (
SELECT GROUP_CONCAT(cid ORDER BY cid)
FROM sc
GROUP BY sid
HAVING sid = '01')
AND sid != '01'
-- 然而实际上去掉GROUP_CONCAT中的ORDER BY也一样能得出正确答案
-- 去掉后执行子查询会发现04号的学生得到的课程是"03,01,02"
-- 不太清楚GROUP_CONCAT的拼接规则
10.查询没学过"张三"老师讲授的任一门课程的学生姓名
keyword : NOT IN、LEFT JOIN
SELECT sid,sname
FROM student
WHERE sid NOT IN (
SELECT sid
FROM sc LEFT JOIN course ON sc.cid=course.cid
LEFT JOIN teacher ON course.tid=teacher.tid
WHERE tname='张三')
11.查询两门及其以上不及格课程的同学的学号,姓名及其平均成绩
keyword : INNER JOIN、GROUP BY、HAVING
SELECT s.sid,s.sname,COUNT(cid),AVG(score)
FROM student s INNER JOIN sc ON s.sid = sc.sid
WHERE sc.score < 60
GROUP BY sc.sid
HAVING COUNT(cid) >= 2
12.检索" 01 "课程分数小于 60,按分数降序排列的学生信息
keyword : ORDER BY
SELECT * FROM student s,sc
WHERE s.sid = sc.sid
AND sc.cid = '01'
AND sc.score < 60
ORDER BY sc.score DESC
13.按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
keyword : GROUP BY、ORDER BY
SELECT s.sid,s.sname,cid,score,avg_score
FROM student s INNER JOIN sc ON s.sid = sc.sid
LEFT JOIN (SELECT sid ,AVG(score) as avg_score FROM sc GROUP BY sid) t ON s.sid = t.sid
WHERE avg_score is NOT NULL
ORDER BY avg_score DESC
14.查询各科成绩最高分、最低分和平均分,以如下形式显示:
以如下形式显示:
课程 ID,课程 name,最高分,最低分,平均分,及格率,中等率,优良率,优秀率
及格为>=60,中等为:70-80,优良为:80-90,优秀为:>=90
要求输出课程号和选修人数,查询结果按人数降序排列,若人数相同,按课程号升序排列
keyword : GROUP BY、CASE WHEN
SELECT cid as '课程ID',cname as '课程name',COUNT(sid) as '选修人数',
MAX(score) as '最高分',MIN(score) as '最低分',AVG(score) as '平均分',
SUM(及格)/COUNT(sid) AS '及格率',
SUM(中等)/COUNT(sid) AS '中等率',
SUM(优良)/COUNT(sid) AS '优良率',
SUM(优秀)/COUNT(sid) AS '优秀率'
FROM (
SELECT sc.*,c.cname,
CASE WHEN sc.score >= 60 THEN 1 ELSE 0 END as '及格',
CASE WHEN sc.score >= 70 AND score < 80 THEN 1 ELSE 0 END as '中等',
CASE WHEN sc.score >= 80 AND score < 90 THEN 1 ELSE 0 END as '优良',
CASE WHEN sc.score >= 90 THEN 1 ELSE 0 END as '优秀'
FROM sc,course c
WHERE sc.cid = c.cid
) temp
GROUP BY temp.cid
15.1 按各科成绩进行排序,并显示排名,Score 重复时保留名次空缺
成绩排序题参考如下:
keyword : 子查询
-- mysql5.7及以下(下文有分析) --
SELECT *, (
SELECT COUNT(DISTINCT score) + 1
FROM sc b
WHERE b.cid = a.cid AND b.score > a.score) AS 名次
FROM sc a
ORDER BY cid, 名次
-----------------------------------------------
-- mysql8.0及以上可以使用窗口函数 --
SELECT
*, DENSE_RANK() OVER(PARTITION BY cid ORDER BY score DESC) AS 'rank'
FROM
sc
分析:这应该是最直观的排名方法,对a表中每条记录,都会去b表查询比改记录分数高的数量,并且对score排重,保留名次空缺
15.2 按各科成绩进行行排序,并显示排名, Score 重复时合并名次
keyword : 对LEFT JOIN以及 GROUP BY需要有一定的理解
先上最终答案
-- mysql5.7及以下(下文有分析) --
SELECT a.*,COUNT(b.score)+1 AS 'rank'
FROM sc AS a LEFT JOIN sc AS b ON a.cid = b.cid AND a.score < b.score
GROUP BY a.sid,a.cid
ORDER BY a.cid,COUNT(b.score)
-----------------------------------------------
-- mysql8.0及以上可以使用窗口函数 --
SELECT
*, RANK() OVER(PARTITION BY cid ORDER BY score DESC) AS 'rank'
FROM
sc
接下来是步骤分解,为了方便理解,我们建一张简易版的test表
简易版test表
test表自交,得到cid相等,并且a表中score小于b表score的结果集
SELECT *
FROM test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score
1
2.先将结果集排序一下,寻找排名的原理
SELECT *
FROM test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score
ORDER BY a.cid,a.sid
2
分析:
我们直观上看a的语文90分是最高,左边是null。b的语文85分是第二,左边有a-语文。c的语文60分是第三,左边有a-语文,b-语文。结合sql语句中的“test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score”。因此test表自交并且a.score < b.score的意思就是每条a记录对应的就是分数比它高的记录。
我们列出来看看
a 语文 90 (对应为null,说明没有比它高的)
b 语文 85 a 语文 90 (对应有a-语文,说明有a-语文比它高)
c 语文 60 a 语文 90 (对应有a-语文,b-语文,说明有a-语文,b-语文比它高)
c 语文 60 b 语文 85
3.从步骤二得到规律之后就应该分组了,只要group by就能获取到对应的COUNT(b.score)作为排名,注意group by的字段选取,我们要通过sid和cid才能获取每个人每个科目的成绩,最终答案如下
SELECT a.*,COUNT(b.score)+1 AS 'rank'
FROM test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score
GROUP BY a.sid,a.cid /*不懂的话可以拿步骤2的数据group by a.sid,a.cid一次*/
ORDER BY a.cid,COUNT(b.score)
3
16.1 查询学生的总成绩,并进行排名,总分重复时保留名次空缺
keyword : 用户变量
SELECT t1.*,
@curRank := IF (@perScore = sum_score,@curRank, @curRank+1) as 'rank',
@perScore := sum_score
FROM
(SELECT sid,SUM(score) AS sum_score FROM sc GROUP BY sid) AS t1 ,
(SELECT @curRank:=0,@perScore := NULL) AS t2
ORDER BY sum_score DESC
分析:整体还是比较好理解的,创建两个用户变量@curRank (当前排名)和@perScore(前一个学生的总分),在遍历时比较当前的总分和前一个的总分是否相等,相等的话@curRank 不变,否则@curRank +1,每次遍历都需要更新一次@perScore
为了模拟同分的情况!sid=3,cid=1的分数从80改成50
16.2 查询学生的总成绩,并进行排名,总分重复时不保留名次空缺
keyword : 用户变量
SELECT
t1.*, @curRank := IF(sum_score=@preScore, @curRank, @totalRank) AS 'rank',
@preScore := sum_score,
@totalRank := @totalRank+1
FROM
(SELECT sid,SUM(score) AS sum_score FROM sc GROUP BY sid) AS t1,
(SELECT @curRank:=1, @totalRank:=1, @preScore:=NULL) t2
ORDER BY sum_score DESC
分析:和上一题基本一致,再加多了一个用户变量@totalRank,每次遍历都+1,然后当前总分和上一个学生总分相同时,排名是@curRank不变,否则
更新@curRank为@totalRank
为了模拟同分的情况,sid=3,cid=1的分数我从80改成50
统计各科成绩各分数段人数:课程编号,课程名称,[100-85],[85-70],[70-60],[60-0] 及所占百分比
keyword : CASE WHEN
SELECT
sc.cid AS 课程编号,
cname AS 课程名称,
SUM( CASE WHEN score >= 0 AND score <= 60 THEN 1 ELSE 0 END ) AS '[60-0]',
SUM( CASE WHEN score >= 0 AND score <= 60 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[60-0]百分比',
SUM( CASE WHEN score >= 60 AND score <= 70 THEN 1 ELSE 0 END ) AS '[70-60]',
SUM( CASE WHEN score >= 60 AND score <= 70 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[70-60]百分比',
SUM( CASE WHEN score >= 70 AND score <= 85 THEN 1 ELSE 0 END ) AS '[85-70]',
SUM( CASE WHEN score >= 70 AND score <= 85 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[85-70]百分比',
SUM( CASE WHEN score >= 85 AND score <= 100 THEN 1 ELSE 0 END ) AS '[100-85]',
SUM( CASE WHEN score >= 85 AND score <= 100 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[100-85]百分比'
FROM
sc JOIN course ON sc.cid = course.cid
GROUP BY sc.cid,cname
查询各科成绩前三名的记录
keyword :HAVING
SELECT *, (
SELECT COUNT(score) + 1
FROM sc b
WHERE b.cid = a.cid AND b.score > a.score) AS ranking
FROM sc a
HAVING ranking <= 3
ORDER BY cid, ranking
查询每门课程被选修的学生数
keyword : GROUP BY
SELECT cid,COUNT(sid) AS num
FROM sc
GROUP BY cid
查询出只选修两门课程的学生学号和姓名
keyword : INNER JOIN、HAVING
SELECT sc.sid,sname
FROM sc INNER JOIN student ON sc.sid = student.sid
GROUP BY sc.sid
HAVING COUNT(cid) = 2
查询男生、女生人数
keyword : GROUP BY
SELECT ssex,COUNT(sid) as num
FROM student
GROUP BY ssex
查询名字中含有「风」字的学生信息
keyword : LIKE
SELECT *
FROM student
WHERE sname LIKE '%风%'
查询同名同性学生名单,并统计同名人数
keyword : LEFT JOIN、GROUP BY
SELECT a.sname,a.ssex,COUNT(1) AS 人数
FROM student a LEFT JOIN student b ON a.sname=b.sname
WHERE a.ssex=b.ssex AND a.sid!=b.sid
GROUP BY a.sname,a.ssex
查询 1990 年出生的学生名单
keyword : 时间函数
SELECT *
FROM student
WHERE YEAR (sage) = '1990'
查询每门课程的平均成绩,结果按平均成绩降序排列,平均成绩相同时,按课程编号升序排列
keyword : ORDER BY
SELECT cid,AVG(score) as avgScore
FROM sc
GROUP BY cid
ORDER BY avgScore DESC,cid ASC
查询平均成绩大于等于 85 的所有学生的学号、姓名和平均成绩
keyword : AVG()、HAVING
SELECT s.sid,sname,AVG(score) as avgScore
FROM sc,student s
WHERE sc.sid = s.sid
GROUP BY sid
HAVING avgScore > 85
查询课程名称为「数学」,且分数低于 60 的学生姓名和分数
keyword : INNER JOIN
SELECT s.sname,sc.score
FROM student AS s INNER JOIN sc ON s.sid = sc.sid
WHERE sc.score < 60
AND sc.cid IN (SELECT cid FROM course WHERE cname = '数学')
查询所有学生的课程及分数情况(存在学生没成绩,没选课的情况)
keyword : INNER JOIN
SELECT sname,cid,score
FROM sc INNER JOIN student AS s ON sc.sid = s.sid
查询任何一门课程成绩在 70 分以上的姓名、课程名称和分数
keyword : INNER JOIN
SELECT sname,cid,score
FROM sc INNER JOIN student AS s ON sc.sid = s.sid
WHERE score > 70
查询不及格的课程
keyword : DISTINCT
SELECT DISTINCT sc.cid
FROM sc INNER JOIN student AS s ON sc.sid = s.sid
WHERE score < 60
31.查询课程编号为 01 且课程成绩在 80 分以上的学生的学号和姓名
keyword : INNER JOIN
SELECT s.sid,sname
FROM sc INNER JOIN student AS s ON sc.sid = s.sid
WHERE sc.cid = '01' AND score >= 80
32.求每门课程的学生人数
keyword :GROUP BY、COUNT()
SELECT cid,COUNT(sid) AS num
FROM sc
GROUP BY cid
33.成绩不重复的情况下,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩
keyword :INNER JOIN、MAX()
SELECT *,MAX(score)
FROM student s INNER JOIN sc ON s.sid =sc.sid
WHERE sc.cid IN (
SELECT cid
FROM course c,teacher t
WHERE c.tid = t.tid AND t.tname = '张三'
)
34.成绩重复的情况下,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩
keyword :多重JOIN、MAX()
SELECT student.*, sc.cid, score
FROM student
INNER JOIN sc ON student.sid = sc.sid
LEFT JOIN course ON sc.cid = course.cid
LEFT JOIN teacher ON course.tid = teacher.tid
WHERE tname = '张三'
AND score = (
SELECT MAX(score)
FROM sc
INNER JOIN course ON sc.cid = course.cid
LEFT JOIN teacher ON course.tid = teacher.tid
WHERE tname = '张三'
)
为了模拟同分的情况,sid=7,cid=2的分数我从89改成90
35.查询不同课程成绩相同的学生的学生编号、课程编号、学生成绩
keyword :DISTINCT
SELECT DISTINCT a.*
FROM sc AS a INNER JOIN sc AS b
WHERE a.score = b.score AND a.cid != b.cid
查询每门成绩最好的前两名
keyword :窗口函数
SELECT *
FROM
( SELECT *, RANK ( ) OVER ( PARTITION BY cid ORDER BY score DESC ) AS ranking FROM sc ) t
WHERE ranking <= 2
37.统计每门课程的学生选修人数(超过 5 人的课程才统计)
keyword :GROUP BY、HAVING
SELECT cid,COUNT(sid)
FROM sc
GROUP BY cid
HAVING COUNT(sid) > 5
38.检索至少选修两门课程的学生学号
*keyword :GROUP BY、HAVING *
SELECT sid,COUNT( cid )
FROM sc
GROUP BY sid
HAVING COUNT( cid ) >= 2
39.查询选修了全部课程的学生信息
keyword:INNER JOIN
SELECT s.*
FROM sc INNER JOIN student AS s ON sc.sid = s.sid
WHERE cid = ( SELECT COUNT( 1 ) FROM course );
40.查询各学生的年龄,只按年份来算
keyword:YEAR()
SELECT sname,YEAR(NOW()) - YEAR(sage) AS 年龄
FROM student
按照出生日期来算,当前月日 < 出生年月的月日则,年龄减一
keyword:CASE WHEN、YEAR()
SELECT sname,
CASE WHEN ( DATE_FORMAT( NOW( ), '%m-%d' ) - DATE_FORMAT( sage, '%m-%d' ) ) < 0
THEN YEAR ( NOW( ) ) - YEAR ( sage ) - 1
ELSE YEAR ( NOW( ) ) - YEAR ( sage )
END AS 年龄
FROM student
42.查询本周过生日的学生
keyword:WEEK()
SELECT sname
FROM student
WHERE WEEK(sage) = WEEK(NOW())
43.查询下周过生日的学生
keyword:WEEK()
SELECT sname
FROM student
WHERE WEEK(sage) = WEEK(NOW()) + 1
44.查询本月过生日的学生
keyword:MONTH ()
SELECT sname,sage
FROM student
WHERE MONTH (sage) = MONTH (NOW())
45.查询下月过生日的学生
keyword:MONTH ()
SELECT sname,sage
FROM student
WHERE MONTH (sage) = MONTH (NOW()) + 1
结语:终于做完了(有一些题目是有小题的,总体还是有50题的),如果有错误或者更好的方法,欢迎指出