本章的内容是数据分析和统计,重点掌握几个分组(聚合)函数的用法以及分组的概念
之前我们建立的学生表包含了一些学生的基本信息,那么现在我想将学生一些科目和科目成绩记录下来,再没有学习表联接查询的情况下,我们暂时将科目和科目成绩也放入学生表中
首先我们仅仅添加一列记录学生某门课程的考试分数
接下来添加一些测试数据
我们可以发现总共有8个学生,因为有8条记录,如果当表的记录条数很多的时候,比如说上万条,我们是否仍然需要select * from 表名做1次全表查询然后慢慢等数据显示完毕后让数据库提示我们这张表里有几条数据呢?
我们有更方便直接的方法获得记录条数:select count(xxx) from 表名
count也是分组函数中的一种
例如,查询总共有多少个学生
需要注意count(*)和count(列名)的用法区别
使用count(列名) 当某列出现null值的时候,count(*)仍然会计算,但是count(列名)不会
我们也可以在使用count(distinct 列名),过滤掉重复数据
要找到最高最低分,我们可以用已学过的知识对学生按分数进行降序
但是,如果我们想要进一步对学生数据进行分析,比如计算总分,平均分,最高分,最低分等等,这个时候,就需要我们使用其他的一些分组(聚合)函数
计算总分,我们使用SUM
计算平均分,我们用AVG
如果想要仅仅显示小数点后1位,我们可以用substr函数 sqlserver中是substring
语法:substr(要截取的字符串,开始索引[,截取字符个数])
Avg函数的返回类型是数值类型,这个地方ORACLE帮我们做了数据类型转换,因此不需要使用to_char函数
获得最高分
最低分
单独对分数进行分析很多时候并不能满足我们的需求,比如我们需要在查询最高分的同时将考了最高分的学生名字也查询出来
这个时候,出现了错误,s_name不是分组函数……,我们后面会使用子查询来解决这个问题
首先,让我们增加需求,刚才的分数统统只能代表学生某1次考试的成绩,而我们需要记录学生的多个科目的考试成绩。在没有学习表连接查询之前,我们将科目也添加进学生表。
然后我们插入一些测试数据
刚才仅仅显示语文一门科目的学生成绩信息,那么,继续插入其他科目的信息
现在问题出来了,总共有4个学生,每个学生都分别拥有3门科目的考试成绩,也即1门科目有多个成绩。如果我要分科目计算每个科目的平均成绩,最高分,和最低分,应该怎样做?
这时我们应该按科目对成绩进行分组,然后再针对每个组即每个科目计算
语法:select 聚合函数(列名) from 表名 group by 分组列
例如,分别计算语文,数学和英语考试的总分
那么,现在的问题在于,没有区别到底哪个总分是哪一门科目的。很简单,查询的时候多查一列即可
接着,计算各科的最高分,最低分,平均分
group by 字句也可以和where条件语句结合在一起使用。当结合在一起时,where在前,group by 在后。即先对select xx from xx的记录集合用where进行筛选,然后再使用group by 对筛选后的结果进行分组
例如,我们发现在各个科目里有一些成绩不好的没及格的同学拖了平均分,甚至有10分的……
那么,我们想在计算各个科目的平均分的时候不将这些不及格的分数统计在内,我们可以先对所有的分数按照>=60的条件进行筛选驱除不及格的,然后再对筛选后的分数进行按科目分组计算平均分
经过筛选,祛除了不及格的分数后再进行分组计算,平均分大大提高了……
如果我们要查询各个科目的最高分的同时也将考了最高分的学生姓名一并列出来应该怎么办?
需要注意的是 在分组中(即出现了group by 子句)必须满足下列2个条件:
1.查询的列必须是分组(聚合)函数,例如max(s_score) avg(s_score)
2.或者查询的列必须是分组列
例如
s_subject之所以能够作为查询列是因为我们分组的条件列正是s_subject
接下来我们继续查看所有学生的个人平均成绩,即每个学生的每门课程分数相加再除以科目数,因为每一个学生都考了多门科目,因此是按学生姓名进行分组
带很多小数点不好看,如果单独截取字符串的话不能四舍五入,我们用ORACLE的一个数字函数round来解决数字格式化问题
下面让我们把个人平均分数都保留2位小数显示
继续,我们完整地对一个学生的所有考试里的最高分,最低分,平均分,考试科目总数使用分组函数做出计算
由此可见,聚合函数和分组的功能对于我们做分析数据时是很好很强大的。但是,关于OO和数据库的争论无论国内国外都从来没有停止过。
以下引用国内大湿banq的语录,可做参考
>原本打算用DDD建模,自己好好弄一把,可是一上来发现需求不是很复杂,如果先建表,做起来也许更快
需求就不是很复杂,用DDD更快,脑子都不用转弯的,为什么你现在觉得DB更快,因为你脑子已经先入为主了,DDD这样正常OO思维你没有了,习惯拐弯思维了,打个比喻:小孩不能学结巴,一旦学了,就一直结巴,正常话都不会说了,这个结巴就是DB,OO就是正常话。结巴的人想说快,总还是结巴,他以为快了,可是我们正常人都会心里发笑。
这里我不是歧视结巴,只是一个比喻,用来说明我们身在庐山中,不识真面貌的可悲状态。
我反对现在传统软件教育的原因,不是要他们全部用OO来教学,而是至少OO和数据库两种并行,让学生有选择,进行一个过渡。
你也不要和你身边DB的程序员争论,他们就是接受这样的教育,他们能找到工作就很满足,他们不是将程序作为事业或兴趣来做。关键是你自己需要恢复正常自然OO思维,这很难,因为让结巴说正常话的可能性有多少呢?
>此时我的类图就需要把这几万条记录load进内存,然后在把相应广告价格信息也load到内存,通过类之间的协作算出总的价钱,这需要很长的时间。而我用多表关联查询和sql函数可以很快的得到结果。当然,我所说的只是应用中的一例,从中,可以很明显的看出数据库的优势。
因为是两种思维,也就是两个不同世界,或者说两种不同语言,今天你可以举这个具体例子,明天还可以举其他例子,就像你今天指着一个东西说:我知道它的中文是“桌子”,可不知道它的英文是什么?所以,我觉得用“桌子”表示它比较方便。
先回答你这个具体问题,当你用数据库SQL函数查询时,你怎么不知道这几万条记录对数据库服务器不是一个很重的负载呢?如果有几万甚至几十万次个请求这条计算呢?你的数据库吃得消吗?但是你如果使用缓存,将计算结果保存在应用服务器中,那么几万次甚至几十万次的请求就被化解了。
那么就这一次计算相比:是用对象逐个计算还是用SQL函数来查呢?我建议还是用对象逐个计算,将负载拉到对象运行的应用服务器中,这样,这台服务器吃不消时,你可以使用分布式多台服务器来计算,当然,如果某次计算耗费很长时间,你可以抛出一个异步线程专门计算,使用JMS专门来进行计算,多台并行计算。这才是大思路,大架构,这比你成天围着数据库转有扩展性,有发展方向吧?
如果你将这个价格一次计算使用数据库计算,这是躲过了一时,躲不过一世,表面上,应用服务器性能好了,数据库服务器性能也还过得去,那么访问量上升怎么办?倒是成天为了一点性能调校数据库,一台数据库服务器的性能总是有天花板的啊,你能要到多少呢?就象一个人潜力总是有限的,不可能无限的吧?所以,数据库思维总是短视敷衍了事的思维,就象国有企业员工办事作风一样,这可能也是数据库特别符合中国国情原因所在吧。
你为什么从这里看出数据库的优势,而我没有看到,只看到数据库逆势,所以说:因为你是数据库思维,是结巴表达方式,在你的有色眼中,什么都是红色的。所以,当你觉得数据库有优势时,就要反问自己,真的是这样吗?
虽然看出,你对OO理论很清楚,但是一到动手,又回到数据库思路,没有将两者统一起来,这是中国程序员的通病,说明你们没有经过OO统一教育和培训,OO理论只是业余爱好,没有机会实践,也不知道如何实践,理论和实践严重脱节,这也是传统教育的最大罪过之一。
反对意见如下:
1。数据库也可以集群(fw注:这个和应用服务器集群还是有区别的)。
2。这里所说的算法不光是指冒泡算法之类的算法,更是指企业级的数据运算。比如说像MRP里面的计算,根据
各种各样的库存,采购计划、采购指令、合同、送货单(很多种)、送货指令(很多种)来计算,包括像汇总、排序、计算等等。针对这种大型企业计算,数据库为我们提供了很多经过数据库内核优化了的函数,像rank,row_Number,sum之类的,如果这种我们自己用OO写算法实现,我不敢想想。对于这种复杂运算我们通常是让数据库在夜间自动运算几个小时来实现,如果用OO,实现起来的效率何在。目前好像还没有谁用OO来做这种MRP的运算吧。
3.我是赞成用缓存的,但是难道要把数据库里所有的数据都缓存在内存种?我曾经做过电信方面的系统,很多表每天入库的数据都是几亿。
4.设计模式,面向对象这些思想也是很好的。但很多方面数据库也有自身的优势。所以取长补短。不能过度设计。
面向对象总是过分的强调扩展,以至于做了很多额外的工作。其实有些是不必要的。比如说数据库扩展,象这种变化一般是不会发生的,企业怎么可能一会又从ORACLE移植SQLSERVER呢,就算有也只是极少数,就好比打个比方,面向对象总是预测可能的一切变化,你出去旅游,你要考虑所有可能出现的情况,以至于你得做好一切准备,比如你要带好一汽车的东西去面对可能出现的问题,下雨了要伞,饿了要面包,生病了要药,走累了要椅子休息,包打不开又得准备老虎钳,老虎钳坏了又要准备好锤子,其实这些是不会发生的。以至于我们的系统过分的臃肿。有些扩展是没有必要的。
当然如果你是一个做中间件的公司,那是要适当考虑扩展的,因为你的产品是要给很多公司用的,得面对很多情况。
面向对象思想很好,但不要过度设计,数据库也非常重要。不要为了面向对象而面向对象,最终目的是给企业创作价值。谁少花钱又创造了更多的价值才是银蛋。
中立意见如下
>>db是存贮工具。性能可用缓存解决
不过我也很无奈,我得听别人的安排放弃一些东西
首先,我很理解这位兄弟!
其次我想说的是,现实总是在矛盾中,但是我只要看中了方向就不会放弃,我坚决支持OO,所以我不断的去学习理解OO!我想这个楼主说的不是没道理,只是因为一些事已成现实,他摆的是事实。但是我又认为这些事实如果推翻一切重来的话,一切从OO来过的话,不知道是什么样的?
所以你们的讨论对我们是种学习,谢谢精彩的分享!
00与数据库在一定时间内会共存.
任何事物都有存在原因.
新事物代替旧事物是必然.
00与数据库,
就社会主义的中国与资本主义的西欧与美国.
在一定时间内共存,互补.
中国向美国与西欧学习.
而美国与西欧在某些方面借鉴了中国.
那么,如果说我只想显示个人平均分在70分以上的这些学生信息呢?那么就需要我们使用having字句对分组后的结果进行筛选
语法和where差不多:having 条件表达式
需要注意having和where的用法区别:
1. having只能用在group by之后,对分组后的结果进行筛选(即使用having的前提条件是分组)
2. where肯定在group by 之前,即也在having之前
3. where后的条件表达式里不允许使用聚合函数,而having可以
如果再加上order by子句进行排序呢?
当然,这么复杂的SQL语句并不是经常遇到,那么,如果RP爆发遇到了之后
现在让我们总结一下,当一个查询语句同时出现了where,group by,having,order by的时候,执行顺序和编写顺序是:
1. 首先执行select xx from xx,返回一个结果记录集合
2. 使用where对结果记录集合里的数据进行筛选
3. 使用group by 对筛选后的结果分组
4. 使用having对分组后的结果再次筛选
5. 对最后剩下来的数据进行排序
现在我们来完成一个复杂的查询语句,需求如下:
按由高到低的顺序显示个人平均分在70分以上的学生姓名和平均分,为了尽可能地提高平均分,在计算平均分前不包括分数在60分以下的成绩,并且也不计算贱人(jr)的成绩
分析:
1.要求显示学生姓名和平均分
因此确定第1步select s_name,avg(score) from student
2.计算平均分前不包括分数在60分以下的成绩,并且也不计算贱人(jr)的成绩
因此确定第2步 where score>=60 and s_name!=’jr’
3.显示个人平均分
相同名字的学生(同一个学生)考了多门科目 因此按姓名分组
确定第3步 group by s_name
4.显示个人平均分在70分以上
因此确定第4步 having avg(s_score)>=70
5.按由高到低的顺序
因此确定第5步 order by avg(s_score) desc
最后按照上面的总结将5个步凑拼装起来,加上列别名和保留2位小数四舍五入
小技巧:怎样确定分组列,用一对多的关系去套,一的一方就是分组列
如 一个同样姓名的学生考了多门科目 一门科目有多个分数。
如果不能确定分组列的话,运用语法规定去套也能写出 SQL 语句。
例如上面的例子,在确定select s_name,avg(score)的时候,同时出现了分组函数和普通列,因此普通列s_name必须作为分组列才能满足语法,马上就能确定group by s_name.
确定avg(s_score)>=70后,由于avg是分组函数,只能用在having字句中,因此马上确定having avg(s_score)>=70
预习思考:如果想要查询每门科目的名称,最高的分数,以及对应的学生名字,应该怎样查询?(使用子查询)