SQL 强化练习 (二)

继续 sql 搞起来, 面向过程来弄, 重点是分析的思路, 涉及的的 left join, inner join, group by +_ having, case when ... 等场景, 也是比较详细地记录整个分析的过程, 虽然实现并不难.

数据关系

这个是都次都要重复提及的. 只有熟练知晓表结构, 才能做各种查询呀.

需求

第 02 题

查询平均成绩大于80分的学生的学号和平均成绩.

分析

只用使用一个 score 表就搞定了.

mysql> select * from score;
+------+------+-------+
| s_id | c_id | score |
+------+------+-------+
| 0001 | 0001 |    80 |
| 0001 | 0002 |    90 |
| 0001 | 0003 |    99 |
| 0002 | 0002 |    60 |
| 0002 | 0003 |    80 |
| 0003 | 0001 |    80 |
| 0003 | 0002 |    80 |
| 0003 | 0003 |    80 |
+------+------+-------+
8 rows in set (0.00 sec)

直接对 score 表 对 s_id 做 group by 然后对 avg(score), 再组内过滤 (haing) 即可.

-- 先弄分组聚合, 注意要先写 s_id
select
  s_id,
  avg(score)
from score
group by
  s_id;
+------+------------+
| s_id | avg(score) |
+------+------------+
| 0001 |    89.6667 |
| 0002 |    70.0000 |
| 0003 |    80.0000 |
+------+------------+
3 rows in set (0.00 sec)

这样, 学号和平均成绩都有了, 然后还要一个, 平均成绩大于 80, 这就是 对 group by 后的一个筛选, 用 having.

select
  s_id as "学号",
  avg(score)  as "平均成绩"
from score
group by s_id
having avg(score) > 80;

这上面没过滤之前就能看出, 大于80的, 就只有1号老铁这个学霸.

+--------+--------------+
| 学号   | 平均成绩     |
+--------+--------------+
| 0001   |      89.6667 |
+--------+--------------+
1 row in set (0.00 sec)

补充

-- 增加对 group by 中没有用到的字段是没啥意义的
select
  s_id as "学号",
  avg(score)  as "平均成绩",
  c_id -- add
from score
group by s_id
having avg(score) > 80;
+--------+--------------+------+
| 学号   | 平均成绩     | c_id |
+--------+--------------+------+
| 0001   |      89.6667 | 0001 |
+--------+--------------+------+
1 row in set (0.00 sec)

这个 c_id 是没有任何意义的, 因为是平均成绩嘛, 因此, **最好不要将 group by 无关的字段给放在 select 中去哦.

其实 pandas 也是一样的处理方式, df.groupby("s_id").agg({"score": "mean"}) . 当然可能是习惯问题, 我总感觉 Python 这种写法会更加直观些, 先分组, 再聚合 , 说的是写法上哈.

本题的核心点: group by + having.

第 03 题

查询所有学生的学号, 姓名, 选课数, 总成绩.

分析

分析: 其实就是以学生表, 我主表, 然后左连接成绩表, 然后再按 s_id group by 再聚合即可.

首先用 学生表为主表, left join 成绩表即可.

select *
from student as a
left join  score as b
on a.s_id = b.s_id;

先连接上看看.

+------+-----------+------------+--------+------+------+-------+
| s_id | s_name    | birth_date | gender | s_id | c_id | score |
+------+-----------+------------+--------+------+------+-------+
| 0001 | 王二      | 1989-01-01 | 男     | 0001 | 0001 |    80 |
| 0001 | 王二      | 1989-01-01 | 男     | 0001 | 0002 |    90 |
| 0001 | 王二      | 1989-01-01 | 男     | 0001 | 0003 |    99 |
| 0002 | 星落      | 1990-12-21 | 女     | 0002 | 0002 |    60 |
| 0002 | 星落      | 1990-12-21 | 女     | 0002 | 0003 |    80 |
| 0003 | 胡小适    | 1991-12-21 | 男     | 0003 | 0001 |    80 |
| 0003 | 胡小适    | 1991-12-21 | 男     | 0003 | 0002 |    80 |
| 0003 | 胡小适    | 1991-12-21 | 男     | 0003 | 0003 |    80 |
| 0004 | 油哥      | 1996-10-01 | 男     | NULL | NULL |  NULL |
+------+-----------+------------+--------+------+------+-------+
9 rows in set (0.06 sec)

然后, 对 s_id 进行 group by, 对 c_id 进行 count, 对 score 进行 sum.

select
 a.s_id as "学号",
 a.s_name as "姓名",
 count(b.c_id) as "选课数",
 sum(b.score) as "总成绩"

from student as a
left join  score as b
on a.s_id = b.s_id

group by a.s_id;

阅读顺序跟写的顺序是, 先写表连接的部分, 然后再 group by, 最后再 写最终要查询的字段 (包含聚合)

+--------+-----------+-----------+-----------+
| 学号   | 姓名      | 选课数    | 总成绩    |
+--------+-----------+-----------+-----------+
| 0001   | 王二      |         3 |       269 |
| 0002   | 星落      |         2 |       140 |
| 0003   | 胡小适    |         3 |       240 |
| 0004   | 油哥      |         0 |      NULL |
+--------+-----------+-----------+-----------+
4 rows in set (0.04 sec)

在这里看上去是没啥问题的, 对于这句 a.s_id as "学号", a.s_name as "姓名", 在本例, 学号跟姓名是 1:1 的关系, 是ok 的. 但假如说, 不是 1: 1 就可能产生很大的问题. 当然我用 pandas 时写也是这样类似的:

# 假设这里的 df 已经是 merge 好了的哈, 直接group by 

df.groupby("s_id").agg({"s_id":" count"},
                       {"score": "sum"}
                      )

其实套路都是一样的, 重点是理解这个 , apply -> group by -> aggregation 的通用套路. 就连大数据的 mapReduce 也是差不多的思想呀.

严格来讲, 这里的 a.s_name z 字段跟 group by 后的聚合字段, 是没啥关系的, 理应不能出现在此处 1:n 反而造成误解.

更严谨的写法能, 就是将这 a.s_name 也放在 s_id 后面 group by. (熟悉Excel透视表的小伙伴就很清楚啦).

select
 a.s_id as "学号",
 a.s_name as "姓名",
 count(b.c_id) as "选课数",
 sum(b.score) as "总成绩"

from student as a
left join  score as b
on a.s_id = b.s_id

group by a.s_id, a.s_name;

得到的结果是一样的.嗯, 这个操作, 我以前在用 excel 的时候, 会很频繁使用到, 透视字段这块.

+--------+-----------+-----------+-----------+

| 学号 | 姓名 | 选课数 | 总成绩 |

+--------+-----------+-----------+-----------+

| 0001 | 王二 | 3 | 269 |

| 0002 | 星落 | 2 | 140 |

| 0003 | 胡小适 | 3 | 240 |

| 0004 | 油哥 | 0 | NULL |

+--------+-----------+-----------+-----------+

4 rows in set (0.00 sec)

对于 Null 这里需要处理一下, 比较合理的方式呢, 是填充为 0. 也就是需要在 sum 那的时候, 加上一个判断

case 语法: case when then xxx else xxx end

select
 a.s_id as "学号",
 a.s_name as "姓名",
 count(b.c_id) as "选课数",

 sum(case when b.score is Null
     then 0
     else b.score end
    ) as "总成绩"

from student as a
left join  score as b
on a.s_id = b.s_id

group by a.s_id, a.s_name;
+--------+-----------+-----------+-----------+
| 学号   | 姓名      | 选课数    | 总成绩    |
+--------+-----------+-----------+-----------+
| 0001   | 王二      |         3 |       269 |
| 0002   | 星落      |         2 |       140 |
| 0003   | 胡小适    |         3 |       240 |
| 0004   | 油哥      |         0 |         0 |
+--------+-----------+-----------+-----------+
4 rows in set (0.07 sec)

这样 Null 的问题也解决了. 这种 case when xxx then xxx else xxx end 还是挺常用的.

case 与 if 的区别

if 是条件判断语句 不能在 查询语句中出现; case 是条件检索 可以再查询中出现

小结

  • group by + having 的用法 (严谨写法是, 跟 group by 无关的字段, 不要放 select)
  • left join 和 inner join 等的不同场景用法 (我感觉工作中用的最多的是 left join 更多一点)
  • 深刻理解 apply -> group by -> aggregation , 不论是 sql 还是编程, 数据分析必须迈过的坎
  • 字段值判断 case when xx then xxx else xxx end 的这种写法, 跟 if 的区别在于, if 用在函数, case 用在查询.

原文地址:https://www.cnblogs.com/chenjieyouge/p/12547750.html

时间: 2024-11-09 03:40:10

SQL 强化练习 (二)的相关文章

SQL总结(二)连表查询

SQL总结(二)连表查询 连接查询包括合并.内连接.外连接和交叉连接,如果涉及多表查询,了解这些连接的特点很重要. 只有真正了解它们之间的区别,才能正确使用. 1.Union UNION 操作符用于合并两个或多个 SELECT 语句的结果集. UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表. 当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行.两种情况下,派生表的每一行不是来自 TABLE1 就是

SQL开发技巧(二)

本系列文章旨在收集在开发过程中遇到的一些常用的SQL语句,然后整理归档,本系列文章基于SQLServer系列,且版本为SQLServer2005及以上…… 文章系列目录 SQL开发技巧(一) SQL开发技巧(二) 本文内容简介 这篇文章主要介绍以下内容: 快速查询表的总记录数 非递归查询树形结构表的所有子节点 清除查询缓存 编程中构建Where语句的小技巧 如何进行跨服务器的数据库查询 快速查询表的总记录数 什么,你还在用select count(*) from xxx?难道没有园友告诉你用se

SQL语句汇总(二)——数据修改、数据查询

首先创建一张表如下,创建表的方法在上篇介绍过了,这里就不再赘述. 添加新数据: INSERT INTO <表名> (<列名列表>) VALUES (<值列表>)  如: INSERT INTO t_student (student_id,student_name,student_age,student_sex) VALUES (1,'大毛',18,'男'); 其中列名可以省略,省略之后要求插入的值必须与列一一对应: INSERT INTO t_student VALUE

SQL开发技巧(二) 【转】感觉他写的很好

本文转自: http://www.cnblogs.com/marvin/p/DevelopSQLSkill_2.html 本系列文章旨在收集在开发过程中遇到的一些常用的SQL语句,然后整理归档,本系列文章基于SQLServer系列,且版本为SQLServer2005及以上…… 文章系列目录 SQL开发技巧(一) SQL开发技巧(二) 本文内容简介 这篇文章主要介绍以下内容: 快速查询表的总记录数 非递归查询树形结构表的所有子节点 清除查询缓存 编程中构建Where语句的小技巧 如何进行跨服务器的

SQL强化练习

SQL语句强化练习题及答案 一.简单查询 1.列出全部学生的信息. SELECT * FROM 学生 2.列出软件专业全部学生的学号及姓名. SELECT 学号,姓名 FROM 学生 WHERE 专业="软件" 3.列出所有必修课的课号. SELECT DISTINCT 课号 FROM 必修课 4.求1号课成绩大于80分的学生的学号及成绩,并按成绩由高到低列出. SELECT 学号,成绩 FROM 选课 WHERE 课号="1" AND 成绩>80 ORDER

PL/SQL 编程(二)游标、存储过程、函数

游标--数据的缓存区 游标:类似集合,可以让用户像操作数组一样操作查询出来的数据集,实质上,它提供了一种从集合性质的结果中提取单条记录的手段. 可以将游标形象的看成一个变动的光标,他实质上是一个指针,在一段Oracle存放数据查询结果集或者数据操作结果集的内存中,这个指针可以指向结果集任何一条记录. 游标分静态游标和REF游标两类,静态游标包含显式游标和隐式游标. 显式游标: 在使用之前必须有明确的游标声明和定义,这样的游标定义会关联数据查询语句,通常会返回一行或多行.打开游标后,用户可以利用游

mybatis强化(二)Parameters和Result

本文通过一个简单例子简单记录下参数的映射.转载注明出处:http://www.cnblogs.com/wdfwolf3/p/6804243.html,谢谢.文件目录如下, 1.配置文件mybatisconfig.xml如下,这里简要说明一下environments.MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一.所以,如果你想连接两个数据库,就需要创建两个 SqlSess

SQL Server之 (二) SQL语句 模糊查询 空值处理 聚合函数

(二) SQL语句  模糊查询  空值处理  聚合函数 自己学习笔记,转载请注明出处,谢谢!---酸菜 SQL :结构化查询语言(Structured Query Language),关系数据库管理系统的标准语言. Sybase与Mircosoft对标准SQL做了扩展:T-SQL (Transact-SQL); 注:①SQL对大小写的敏感取决于排序规则,一般不敏感; ②SQL对单引号的转义,用两个单引号来表示一个单引号; ③SQL执行顺序: 1→2→3→4 select  * ---------

(韩顺平讲解)pl/sql编程(二)

一.pl/sql进阶-控制结构 pl/sql中提供了三种条件分支语句 if -- then, if --then---else, if---then---elsif---else (1)简单的条件判断 if – then 问题:编写一个过程,可以输入一个雇员名,如果该雇员的工资低于2000,就给该员工工资增加10%. Sql代码 1. create or replace procedure sp_pro6(spName varchar2) is 2. --定义 3. v_sal emp.sal%