如何找出重复的值 如何查找第N⾼高的数值 如何实现多表查询 查找不不在表中的数据 【题⽬目1】:如何找出重复的值 编写⼀一个SQL查询,查找学⽣生表中所有重复的学⽣生名。 解题思路路】 1.看到“找重复”的关键字眼,⾸首先要⽤用分组函数(group by),再 ⽤用聚合函数中的计数函数count()给姓名列列计数。 2. 分组汇总后,⽣生成了了⼀一个如下的表。从这个表⾥里里选出计数⼤大于1 的姓名,就是重复的姓名。 ⽅方法1: 1)创建⼀一个辅助表,将姓名列列进⾏行行⾏行行分组汇总 select 姓名, count (姓名)as 计数 from 学⽣生表 group by 姓名 2)选出辅助表中计数⼤大于1的姓名 select 姓名 from 辅助表 where 计数 >1 3)结合前两步,将“创建辅助表”的步骤放⼊入⼦子查询 select 姓名 from ( select 姓名, count (姓名)as 计数 from 学⽣生表 group by 姓名 ) as 辅助表 where 计数 >1 最优⽅方法: select 姓名 from 学⽣生表 group by 姓名 having count(姓名)>1 题⽬目⼆二:如何查找第N⾼高的数据 【题⽬目】 现在有“课程表”,记录了了学⽣生选修课程的名称以及成绩。 现在需要找出语⽂文课中成绩第⼆二⾼高的学⽣生成绩。如果不不存在第⼆二⾼高 成绩的学⽣生,那么查询应返回 null。 1.找出所有选修了了“语⽂文”课的学⽣生成绩 select * from 课程表 where 课程= ‘语⽂文ʼ 2.查找语⽂文课程成绩的第⼆二名 考虑到成绩可能有⼀一样的值,所以使⽤用distinct 成绩进⾏行行成绩去 重。 思路路1: 使⽤用⼦子查询找出语⽂文成绩查询最⼤大的成绩记为a,然后再找出⼩小于 a的最⼤大值就是课程成绩的第⼆二⾼高值。 max(列列名) 可以返回该列列的最⼤大值 可以⽤用下⾯面的sql语句句得到语⽂文课的最⼤大值 select max(distinct(成绩) from 课程表 where 课程= ‘语⽂文ʼ 然后再找出⼩小于a的最⼤大值就是课程成绩的第⼆二⾼高值。 select max(distinct(成绩) from 课程表 where 课程= ‘语⽂文ʼ and 成绩 < (select max(distinct(成绩) from 课程表 where 课程= ‘语⽂文ʼ) 思路路2:使⽤用 limit 和 offset 在《猴⼦子 从零学会sql》中讲过: limit n⼦子句句表示查询结果返回前n条数据 offset n表示跳过x条语句句 limit y offset x 分句句表示查询结果跳过 x 条数据,读取前 y 条数据 使⽤用limit和offset,降序排列列再返回第⼆二条记录可以得到第⼆二⼤大的 值 select distinct 成绩 from 成绩表 wheree 课程=‘语⽂文ʼ order by 课程,成绩 desc limit 1,1 (跳过⼀一条,读取第⼀一条) 3.考虑特殊情况 题⽬目要求,如果没有第⼆二⾼高的成绩,返回空值,所以这⾥里里⽤用判断空 值的函数(ifnull)函数来处理理特殊情况。 ifnull(a,b)函数解释: 如果value1不不是空,结果返回a 如果value1是空,结果返回b 对于本题的sql就是: select ifnull( (select max(distinct(成绩) from 课程表 where 课程= ‘语⽂文ʼ and 成绩 < (select max(distinct(成绩) from 课程表 where 课程= ‘语⽂文ʼ) ,null) as ‘语⽂文课第⼆二名成绩ʼ 【举⼀一反三】 查找 Employee 表中第⼆二⾼高的薪⽔水(Salary)。查询结果返回 200 作为第⼆二⾼高的薪⽔水。如果不不存在第⼆二⾼高的薪⽔水,那么查询应返回 null。 select ifnull( select distinct Salary from Employee order by Salary Desc limit 1,1), null ) as secondhighsalary 【题⽬目3】 多表联合查询 【题⽬目】 现在有两个表,“学⽣表”记录了学⽣的基本信息,有“学号”、“姓 名”。 “成绩”表记录了学⽣选修的课程,以及对应课程的成绩。 这两个表通过“学号”进⾏关联。 现在要查找出所有学⽣的学号,姓名,课程和成绩。 select a.学号,a.课程,b.课程,b.成绩 from 学⽣生 as a left join 成绩 as b on a.学号=b.学号 【举⼀一反三】 有下⾯面两个表 select a.firstName, a.LastName, b.City,b.state from Person as a Left join address as b on a.personid= b.personid 【题⽬目4】查找不不在表中的数据 下⾯面是学⽣生的名单,表名为“学⽣生表”;近视学⽣生的名单,表名 为“近视学⽣生表”。请问不不是近视眼的学⽣生都有谁? (“学⽣生表”表中的学号与“近视学⽣生”表中的学⽣生学号⼀一⼀一对应) select a.姓名 as 不不近视的学⽣生名单 from 学⽣生表 as a left join 近视学⽣生表 as b on a.学号=b.学⽣生学号 where b.序号 is null; 举⼀一反三】 查找“不不在表⾥里里的数据”应⽤用案例例: 某⽹网站包含两个表,顾客姓名表(表名Customers)和 购买记录 表(表名Orders)。找出所有从不不订购任何东⻄西的客户。 (“顾客姓名表”中的ID与“购买记录”表中的学⽣生学号CustomerId⼀一 ⼀一对应) select a.names as Customers from Customers as a left join Orders as b on a.ID =b.customerId where b.customerid is null 【题⽬目5】 “雇员表“中记录了了员⼯工的信息,“薪⽔水表“中记录了了对应员⼯工发放的 薪⽔水。两表通过“雇员编号”关联。 查找当前所有雇员⼊入职以来的薪⽔水涨幅,给出雇员编号以及其对应 的薪⽔水涨幅,并按照薪⽔水涨幅进⾏行行升序。 (注:薪⽔水表中结束⽇日期为2004-01-01的才是当前员⼯工,否则是 已离职员⼯工) select m.雇员编号,当前薪⽔水-⼊入职薪⽔水 as 薪⽔水涨幅 from (select 雇员编号,薪⽔水 as 当前薪⽔水 from 薪⽔水表 where 结束⽇日期 = '2004-01-01') as m left join (select a.雇员编号,薪⽔水 as ⼊入职薪⽔水 from 雇员表 as a left join 薪⽔水表 as b on a.雇员编号 = b.雇员编号 where a.雇⽤用⽇日期 = b.起始⽇日期 and a.雇员编号 in (select 雇员编号 from 薪⽔水表 where 结束⽇日期 = '2004-01-01')) as n on m.雇员编号 = n.雇员编号 order by 薪⽔水涨幅; 【举⼀一反三】 查找所有学⽣生开学以来的成绩涨幅,给出学⽣生编号以及其对应的成 绩涨幅,并按照成绩涨幅进⾏行行升序。 select m.学⽣生编号,当前成绩-⼊入学成绩 as 成绩涨幅 from (select 学⽣生编号,成绩 as 当前成绩 from 成绩表 where 结束⽇日期 = '2011-10-02') as m left join (select a.学⽣生编号,b.成绩 as ⼊入学成绩 from 学⽣生表 as a left join 成绩表 as b on a.学⽣生编号 = b.学⽣生编号 where a.⼊入学⽇日期 = b.起始⽇日期) as n on m.学⽣生编号 = n.学⽣生编号 order by 成绩涨幅; 【题⽬目6】如何⽐比较⽇日期数据 下⾯面是某公司每天的营业额,表名为“⽇日销”。“⽇日期”这⼀一列列的数据 类型是⽇日期类型(date)。 请找出所有⽐比前⼀一天(昨天)营业额更更⾼高的数据。(前⼀一天的意 思,如果“当天”是1⽉月,“昨天”(前⼀一天)就是1号) 例例如需要返回⼀一下结果: 【解题思路路】 1.交叉联结 使⽤用交叉联结会将两个表中所有的数据两两组合。如下图,是对 表“text”⾃自身进⾏行行交叉联结的结果: 直接使⽤用交叉联结的业务需求⽐比较少⻅见,往往需要结合具体条件, 对数据进⾏行行有⽬目的的提取,本题需要结合的条件就是“前⼀一天”。 2.本题的⽇日销表交叉联结的结果(部分)如下。这个交叉联结的结 果表,可以看作左边三列列是表a,右边三列列是表b。 红⾊色框中的每⼀一⾏行行数据,左边是“当天”数据,右边是“前⼀一天”的数 据。⽐比如第⼀一个红⾊色框中左边是“当天”数据(2号),右边是“前⼀一 天”的数据(1号)。 题⽬目要求,销售额条件是:“当天” > “昨天”(前⼀一天)。所以,对 于上⾯面的表,我们只需要找到表a中销售额(当天)⼤大于b中销售 额(昨天)的数据。 3.另⼀一个需要着重去考虑的,就是如何找到 “昨天”(前⼀一天),这 ⾥里里为⼤大家介绍两个时间计算的函数: datediff(⽇日期1, ⽇日期2): 得到的结果是⽇日期1与⽇日期2相差的天数。 如果⽇日期1⽐比⽇日期2⼤大,结果为正;如果⽇日期1⽐比⽇日期2⼩小,结果为负。 例例如:⽇日期1(2019-01-02),⽇日期2(2019-01-01),两个⽇日期 在函数⾥里里互换位置,就是下⾯面的结果 另⼀一个关于时间计算的函数是: timestampdiff(时间类型, ⽇日期1, ⽇日期2) 这个函数和上⾯面diffdate的正、负号规则刚好相反。 ⽇日期1⼤大于⽇日期2,结果为负,⽇日期1⼩小于⽇日期2,结果为正。 在“时间类型”的参数位置,通过添加“day”, “hour”, “second”等关 键词,来规定计算天数差、⼩小时数差、还是分钟数差。示例例如下 图: 【解题步骤】 1.将⽇日销表进⾏行行交叉联结 2.选出上图红框中的“a.⽇日期⽐比b.⽇日期⼤大⼀一天” 可以使⽤用“diffdate(a.⽇日期, b.⽇日期) = 1”或者“timestampdiff(day, a. ⽇日期, b.⽇日期) = -1”,以此为基准,提取表中的数据,这⾥里里先⽤用 diffdate进⾏行行操作。 3.找出a中销售额⼤大于b中销售额的数据 where a.销售额(万元) > b.销售额(万元) 4.删掉多余数据 题⽬目只需要找销售额⼤大于前⼀一天的ID、⽇日期、销售额,不不需要上表 那么多数据。所以只需要提取中上表的ID、⽇日期、销售额(万元) 列列。 select a.ID, a.⽇日期, a.销售额(万元) from ⽇日销 as a cross join ⽇日销 as b on datediff(a.⽇日期, b.⽇日期) = 1 where a.销售额(万元) > b.销售额(万元); 或者 select a.ID, a.⽇日期, a.销售额(万元) from ⽇日销 as a cross join ⽇日销 as b on timestampdiff(day, a.⽇日期, b.⽇日期) = -1 where a.销售额(万元) > b.销售额(万元); 【举⼀一反三】 下⾯面是⽓气温表,名为weather,date列列的数据格式为date,请找出 ⽐比前⼀一天温度更更⾼高的ID和⽇日期 select a.ID, a.date from weather as a cross join weather as b on datediff(a.date,b.date)=1 where a.temp >b.temp 【题⽬目7】交换数据 ⼩小明是⼀一所学校的⽼老老师,她有⼀一张 ‘学⽣生表ʼ,平时⽤用来存放座位号 和学⽣生的信息。其中,座位号是连续递增的。总的座位数是偶数。 现在,⼩小明想改变相邻俩学⽣生的座位。你能不不能帮她写⼀一个sql查 来输出想要的结果呢? 示例例查询结果如下: 【解题思路路】 第⼀一步:理理清换座位的逻辑 查询⽬目的是改变相邻学⽣生的座位号。为了了理理清逻辑,在原表中插⼊入 ⼀一列列叫做‘奇偶数ʼ,对应表示“座位号”的值是“奇数”还是“偶数”。 然后⽐比较原始表⾥里里的“座位号”和交换结果⾥里里的“座位号”(对⽐比分析 ⽅方法),可以发现下图的规律律。 1)如果原来座位号是奇数的学⽣生,换座位后,这名学⽣生的座位号 变为“座位号+1”。 2)如果原来座位号是偶数的学⽣生,换座位后,这名学⽣生的座位号 变为“座位号-1”。 第⼆二步:如何判断座位号是奇数,还是偶数 sql求余函数:mod(n,m) ,返回n除以m的余数。⽐比如mod(8,2) 的结 果是0。 如果n除以2的余数是0,说明n是偶数,否则是奇数。 转换为判断奇数,偶数的sql就是: 把前⾯面的逻辑写到sql⾥里里就是: 1)如果原来座位号是奇数的学⽣生,换座位后,这名学⽣生的座位号 变为“座位号+1”。 2)如果原来座位号是偶数的学⽣生,换座位后,这名学⽣生的座位号 变为“座位号-1”。 加⼊入select字句句,就是最好的结果: select (case when mod(座位号, 2) != 0 then 座位号 + 1 when mod(座位号, 2) = 0 then 座位号 - 1 end) as '交换后座位号', 姓名 from 学⽣生表; 【举⼀一反三】 原始座次表 ‘seatʼ如下,现需要更更换相邻位置学⽣生的座次。 注:该座次表‘seatʼ共有5名学⽣生,第5个 座位号是奇数的不不变 select (case # 当座位号是奇数并且不不是不不是最后⼀一个座位号时 when mod(id, 2) != 0 and counts!= id then id + 1 # 当座位号是奇数并且是最后⼀一个座位号时,座位号不不变 when mod(id, 2) != 0 and counts = id then id # 当座位号是偶数时 else id - 1 end) as id2,student from seat as a, (select count(*) as counts from seat) as b;