mysql代替distinct_mysql GROUP BY 代替DISTINCT 遇到的问题及解决

近日在做一个数据分析时,发现DISTINCT比较慢,想通过group by来替代。然而替代并非一帆风顺,在替代过程中,发现对于重复数据,group by会取第一次出现的记录。为得到我想要的统计数据,折腾了一番。下面用实例来描述我的问题及调整过程。

场景:有一张表,记录手机用户的常规信息,每天每个用户一条记录

CREATE TABLE `userinfo_test` (

`day` date NOT NULL DEFAULT '2016-06-01',

`username` varchar(64) NOT NULL,

`phone` varchar(16) NOT NULL DEFAULT '',

`cv` varchar(16) NOT NULL DEFAULT '',

PRIMARY KEY (`day`,`username`),

KEY `ix_day_username_phone_cv` (`day`,`username`,`phone`,`cv`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

现在需要统计每个月,每个机型每个版本的活跃人数(对于同一用户,如果升级了版本,只统计升级后的版本)

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000001','A', '1001');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000002','A', '1002');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000003','A', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000004','B', '1001');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000005','B', '1002');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000006','B', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000007','C', '1001');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000008','C', '1002');

INSERT INTO `userinfo_test` VALUES('2016-05-01','10000009','C', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000001','A', '1001');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000002','A', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000003','A', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000004','B', '1002');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000005','B', '1002');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000006','B', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000007','C', '1003');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000008','C', '1002');

INSERT INTO `userinfo_test` VALUES('2016-05-02','10000009','C', '1003');

记录每天的用户常规信息,若当天信息有变化,只记录最后的信息,每个用户每天只有一条记录信息

mysql> SELECT * FROM `userinfo_test`;

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

| day        | username | phone | cv   |

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

| 2016-05-01 | 10000001 | A     | 1001 |

| 2016-05-01 | 10000002 | A     | 1002 |

| 2016-05-01 | 10000003 | A     | 1003 |

| 2016-05-01 | 10000004 | B     | 1001 |

| 2016-05-01 | 10000005 | B     | 1002 |

| 2016-05-01 | 10000006 | B     | 1003 |

| 2016-05-01 | 10000007 | C     | 1001 |

| 2016-05-01 | 10000008 | C     | 1002 |

| 2016-05-01 | 10000009 | C     | 1003 |

| 2016-05-02 | 10000001 | A     | 1001 |

| 2016-05-02 | 10000002 | A     | 1003 |

| 2016-05-02 | 10000003 | A     | 1003 |

| 2016-05-02 | 10000004 | B     | 1002 |

| 2016-05-02 | 10000005 | B     | 1002 |

| 2016-05-02 | 10000006 | B     | 1003 |

| 2016-05-02 | 10000007 | C     | 1003 |

| 2016-05-02 | 10000008 | C     | 1002 |

| 2016-05-02 | 10000009 | C     | 1003 |

| 2016-05-02 | 10000019 | C     | 1003 |

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

19 rows in set (0.00 sec)

汇总每天各机型各版本的人数

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v, COUNT(cv)daynum FROM `userinfo_test` WHERE DAY = '2016-05-01' GROUP BY phone,cv WITH ROLLUP;

mysql> SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v, COUNT(cv)daynum FROM `userinfo_test` WHERE DAY = '2016-05-01' GROUP BY phone,cv WITH ROLLUP;

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

| p                 | v                 | daynum |

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

| A                 | 1001              |      1 |

| A                 | 1002              |      1 |

| A                 | 1003              |      1 |

| A                 | WITH_ROLLUP_TOTAL |      3 |

| B                 | 1001              |      1 |

| B                 | 1002              |      1 |

| B                 | 1003              |      1 |

| B                 | WITH_ROLLUP_TOTAL |      3 |

| C                 | 1001              |      1 |

| C                 | 1002              |      1 |

| C                 | 1003              |      1 |

| C                 | WITH_ROLLUP_TOTAL |      3 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |      9 |

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

13 rows in set (0.00 sec)

mysql> SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v, COUNT(cv)daynum FROM `userinfo_test` WHERE DAY = '2016-05-02' GROUP BY phone,cv WITH ROLLUP;

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

| p                 | v                 | daynum |

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

| A                 | 1001              |      1 |

| A                 | 1003              |      2 |

| A                 | WITH_ROLLUP_TOTAL |      3 |

| B                 | 1002              |      2 |

| B                 | 1003              |      1 |

| B                 | WITH_ROLLUP_TOTAL |      3 |

| C                 | 1002              |      1 |

| C                 | 1003              |      3 |

| C                 | WITH_ROLLUP_TOTAL |      4 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |     10 |

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

10 rows in set (0.00 sec)

汇总每月各机型各版本的人数,若该月同一人有多个机型版本信息,则以最后记录的机型版本信息为准,只汇总这一条记录

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v,COUNT(cv) monthnum FROM (SELECT phone,cv FROM (SELECT * FROM (SELECT cv,phone,username FROM `userinfo_test` GROUP BY cv,phone,username) c ORDER BY cv DESC) d GROUP BY username) e GROUP

BY phone,cv WITH ROLLUP;

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

| p                 | v                 | monthnum |

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

| A                 | 1001              |        1 |

| A                 | 1003              |        2 |

| A                 | WITH_ROLLUP_TOTAL |        3 |

| B                 | 1002              |        2 |

| B                 | 1003              |        1 |

| B                 | WITH_ROLLUP_TOTAL |        3 |

| C                 | 1002              |        1 |

| C                 | 1003              |        3 |

| C                 | WITH_ROLLUP_TOTAL |        4 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |       10 |

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

10 rows in set (0.00 sec)

注意,上述sql 对cv 做了降序排列(考虑到通常都是升级了版本,即认为版本越大,代表了同一用户的最后的记录),group by 默认统计第一次出现的记录

如果不对cv做降序

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v,COUNT(cv) monthnum FROM (SELECT phone,cv,username FROM `userinfo_test` GROUP BY username) c GROUP BY phone,cv WITH ROLLUP;

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

| p                 | v                 | monthnum |

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

| A                 | 1001              |        1 |

| A                 | 1002              |        1 |

| A                 | 1003              |        1 |

| A                 | WITH_ROLLUP_TOTAL |        3 |

| B                 | 1001              |        1 |

| B                 | 1002              |        1 |

| B                 | 1003              |        1 |

| B                 | WITH_ROLLUP_TOTAL |        3 |

| C                 | 1001              |        1 |

| C                 | 1002              |        1 |

| C                 | 1003              |        2 |

| C                 | WITH_ROLLUP_TOTAL |        4 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |       10 |

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

13 rows in set (0.00 sec)

可以看到,对于同一用户,统计到的是第一次出现的记录。

如果用DISTINCT,下面的sql并不能得到我们想要的数据。

此外对于重复数据比较多时,group by要比DISTINCT快很多

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v, COUNT(DISTINCT username)daynum  FROM `userinfo_test` GROUP BY phone,cv WITH ROLLUP;

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

| p                 | v                 | daynum |

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

| A                 | 1001              |      1 |

| A                 | 1002              |      1 |

| A                 | 1003              |      2 |

| A                 | WITH_ROLLUP_TOTAL |      3 |

| B                 | 1001              |      1 |

| B                 | 1002              |      2 |

| B                 | 1003              |      1 |

| B                 | WITH_ROLLUP_TOTAL |      3 |

| C                 | 1001              |      1 |

| C                 | 1002              |      1 |

| C                 | 1003              |      3 |

| C                 | WITH_ROLLUP_TOTAL |      4 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |     10 |

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

13 rows in set (0.01 sec)

改用如下sql可以获得想要的结果

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v, COUNT(cv)daynum FROM(SELECT * FROM (SELECT DISTINCT username,phone,cv FROM `userinfo_test` ORDER BY cv DESC) c GROUP BY username)d GROUP BY phone,cv WITH ROLLUP;

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

| p                 | v                 | daynum |

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

| A                 | 1001              |      1 |

| A                 | 1003              |      2 |

| A                 | WITH_ROLLUP_TOTAL |      3 |

| B                 | 1002              |      2 |

| B                 | 1003              |      1 |

| B                 | WITH_ROLLUP_TOTAL |      3 |

| C                 | 1002              |      1 |

| C                 | 1003              |      3 |

| C                 | WITH_ROLLUP_TOTAL |      4 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |     10 |

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

10 rows in set (0.00 sec)

group by 支持排序,group by的排序是按最后一个字段排序的

SELECT phone,cv,COUNT(cv)num FROM `userinfo_test` GROUP BY phone,cv WITH ROLLUP;

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

| phone | cv   | num |

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

| A     | 1001 |   2 |

| A     | 1002 |   1 |

| A     | 1003 |   3 |

| A     | NULL |   6 |

| B     | 1001 |   1 |

| B     | 1002 |   3 |

| B     | 1003 |   2 |

| B     | NULL |   6 |

| C     | 1001 |   1 |

| C     | 1002 |   2 |

| C     | 1003 |   4 |

| C     | NULL |   7 |

| NULL  | NULL |  19 |

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

13 rows in set (0.00 sec)

SELECT phone,cv,COUNT(cv)num FROM `userinfo_test` GROUP BY phone,cv DESC WITH ROLLUP;

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

| phone | cv   | num |

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

| A     | 1003 |   3 |

| A     | 1002 |   1 |

| A     | 1001 |   2 |

| A     | NULL |   6 |

| B     | 1003 |   2 |

| B     | 1002 |   3 |

| B     | 1001 |   1 |

| B     | NULL |   6 |

| C     | 1003 |   4 |

| C     | 1002 |   2 |

| C     | 1001 |   1 |

| C     | NULL |   7 |

| NULL  | NULL |  19 |

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

13 rows in set (0.01 sec)

explain SELECT cv,phone,COUNT(phone)num FROM `userinfo_test` GROUP BY cv,phone DESC WITH ROLLUP;

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

| cv   | phone | num |

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

| 1001 | C     |   1 |

| 1001 | B     |   1 |

| 1001 | A     |   2 |

| 1001 | NULL  |   4 |

| 1002 | C     |   2 |

| 1002 | B     |   3 |

| 1002 | A     |   1 |

| 1002 | NULL  |   6 |

| 1003 | C     |   4 |

| 1003 | B     |   2 |

| 1003 | A     |   3 |

| 1003 | NULL  |   9 |

| NULL | NULL  |  19 |

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

13 rows in set (0.00 sec)

如果用到group by 的排序,前面月活跃用户数的统计语句

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v,COUNT(cv) monthnum FROM (SELECT phone,cv FROM (SELECT * FROM (SELECT cv,phone,username FROM `userinfo_test` GROUP BY cv,phone,username) c ORDER BY cv DESC) d GROUP BY username) e GROUP

BY phone,cv WITH ROLLUP;

可以修改为

SELECT IFNULL(phone,'WITH_ROLLUP_TOTAL') p, IFNULL(cv,'WITH_ROLLUP_TOTAL') v,COUNT(cv) monthnum FROM (SELECT phone,cv FROM (SELECT cv,phone,username FROM `userinfo_test` GROUP BY username,phone,cv DESC) c GROUP BY username) d GROUP BY phone,cv WITH ROLLUP;

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

| p                 | v                 | monthnum |

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

| A                 | 1001              |        1 |

| A                 | 1003              |        2 |

| A                 | WITH_ROLLUP_TOTAL |        3 |

| B                 | 1002              |        2 |

| B                 | 1003              |        1 |

| B                 | WITH_ROLLUP_TOTAL |        3 |

| C                 | 1002              |        1 |

| C                 | 1003              |        3 |

| C                 | WITH_ROLLUP_TOTAL |        4 |

| WITH_ROLLUP_TOTAL | WITH_ROLLUP_TOTAL |       10 |

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

10 rows in set (0.01 sec)

后记:

1.由于天与天之间重复数据较多,因此group by 比DISTINCT 相对来说有优势

2.感觉sql还是写的比较复杂,欢迎指点,优化sql或者有更好的方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值