zoukankan      html  css  js  c++  java
  • 关于MAX()函数的一点思考

    本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/103

    考虑如下表和sql:

    CREATE TABLE `iknow_team_info` (
      `teamId` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
      `userNum` int(11) unsigned NOT NULL DEFAULT '0'',
      PRIMARY KEY (`teamId`),
    ) ENGINE=InnoDB  DEFAULT CHARSET=gbk'
    
    mysql> select teamId,userNum from iknow_team_info limit 10; 
    +--------+---------+
    | teamId | userNum |
    +--------+---------+
    |      1 |      73 |
    |      4 |     100 |
    |      8 |     112 |
    |      9 |     136 |
    |     10 |      58 |
    |     12 |      84 |
    |     16 |     141 |
    |     17 |     560 |
    |     18 |     114 |
    |     19 |       8 |
    +--------+---------+
    10 rows in set (0.01 sec)
    
    mysql> select teamId,max(userNum) from iknow_team_info;
    +--------+--------------+
    | teamId | max(userNum) |
    +--------+--------------+
    |      1 |         1000 |
    +--------+--------------+
    1 row in set (0.02 sec)
    

    关于最后一个sql:查找人数(userNum)最多的行对应的teamId,为什么会返回1呢?很显然人数最多的行对应的teamId不是1。

    在这里userNum列没有索引,mysql肯定会全表扫描:

    mysql> explain select teamId,max(userNum) from iknow_team_info;
    +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------+
    | id | select_type | table           | type | possible_keys | key  | key_len | ref  | rows  | Extra |
    +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------+
    |  1 | SIMPLE      | iknow_team_info | ALL  | NULL          | NULL | NULL    | NULL | 12191 |       |
    +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------+
    1 row in set (0.00 sec)
    

    我猜测的sql执行过程是这样的: 全表扫描,扫描的过程中记录下扫描过得最大的userNum以及对应的teamId,最后将结果返回。这个过程应该很清晰明了,为什么mysql没有返回正确结果呢?

    最后在官方手册中寻找到了答案:

    原来MAX()也是聚集函数的一种,所有聚集函数如下表:
    image

    当我们使用了上面表中的聚集函数但是却没有包含group by时,mysql会默认在所有满足条件的行上做聚集。

    If you use a group function in a statement containing no GROUP BY clause, it is equivalent to grouping on all rows.

    所以我们可以大胆的假设上面的sql等同于:

    mysql> select teamId,max(userNum) from iknow_team_info group by null;
    +--------+--------------+
    | teamId | max(userNum) |
    +--------+--------------+
    |      1 |         1000 |
    +--------+--------------+
    1 row in set (0.02 sec)
    

    mysql跟标准sql的一点不同是:mysql接受出现在select列表中但是没有出现在group by列表中的列。所以,当teamId不在group by的列表中时,mysql会在每一个分组中随机挑选出一个teamId,所以最后出现的teamId是1,不是正确的。

    If ONLY_FULL_GROUP_BY is disabled, a MySQL extension to the standard SQL use of GROUP BY permits the select list, HAVING condition, or ORDER BY list to refer to nonaggregated columns even if the columns are not functionally dependent on GROUP BY columns. This causes MySQL to accept the preceding query. In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.

    要想出现正确的结果,我们可以按照下面的方式书写sql:

    mysql> select teamId,userNum from iknow_team_info order by userNum desc limit 1;
    +--------+---------+
    | teamId | userNum |
    +--------+---------+
    |  88010 |    1000 |
    +--------+---------+
    1 row in set (0.01 sec)
    

    或者我们可以让teamId出现在group by的列表中,从而取出正确的teamId(即列出每个teamId组内的max(userNum)),再对所有的max(userNum)进行排序。

    mysql> select teamId,max(userNum) maxNum from iknow_team_info group by teamId order by maxNum desc limit 1;
    +--------+--------+
    | teamId | maxNum |
    +--------+--------+
    |  88041 |   1000 |
    +--------+--------+
    1 row in set (0.02 sec)
    

    参考资料:
    How does SQL MAX() works?
    Aggregate (GROUP BY) Function Descriptions

  • 相关阅读:
    JavaScript之DOM
    MapReduce多种join实现实例分析(一)
    JavaScript的文档对象模型DOM
    JavaScript原生实现观察者模式
    Idea环境下git 图形化操作
    一分钟教你如何实现唯美的文字描边
    Redis集群
    JAVA线程池的实际运用
    Java 线程池(ThreadPoolExecutor)原理解析
    win7开始菜单路径
  • 原文地址:https://www.cnblogs.com/zhangyachen/p/8033276.html
Copyright © 2011-2022 走看看