V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
sniperking1234
V2EX  ›  MySQL

mysql 语句中 group by 的问题

  •  
  •   sniperking1234 · 2023-04-28 11:51:31 +08:00 · 3007 次点击
    这是一个创建于 607 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有个需求以用户为维度进行统计订单,并显示出用户相关信息,那么我写 sql 的时候,用下面哪种方式比较合适

    第一种是把用户信息相关的字段放到 group by 中,在 select 中就也可以显示相应的字段,这样一个 sql 语句就可以查询出来统计信息和用户信息。

    select count(userid), user_id ,user_name ,user_type ,user_level ...  from user group by user_id, user_name, user_type, user_level
    

    第二种是先按照 user_id 进行聚合统计,然后再根据 userid 查表,补全用户信息,这样就需要查两次

    select count(userid), user_id, ...  from ... group by user_id
    
    select ... from ... where user_id in (...)
    

    第一种方式明显是更简单的,但是把很多冗余的字段放到 group by 的条件中,会不会有性能问题,或者第一种写法可能会带来其他问题,求助下大家的意见,如果有更好的 sql 写法,也希望大家能够帮忙分享一下经验。

    ps:无论第一种还是第二种,都需要联表查询

    28 条回复    2023-06-02 07:45:06 +08:00
    dongtingyue
        1
    dongtingyue  
       2023-04-28 11:56:35 +08:00
    第一种加索引添加测试数据能跑出来就用第一种,自己程序处理不一定比数据库好。
    optional
        2
    optional  
       2023-04-28 12:10:10 +08:00 via iPhone   ❤️ 1
    第二种,第一种没有优化空间,压力全部给到数据库。 第二个,还可以看看有没有基于 id 的用户缓存。
    甚至,都不应该去 group by
    liuxu
        3
    liuxu  
       2023-04-28 12:24:03 +08:00
    第二种,看你是在扫全表,第一个临时表大概率要缓存到磁盘,第二个 sql 可以控制下 in 的数量,业务上并发查询
    lysS
        4
    lysS  
       2023-04-28 13:55:21 +08:00
    in 表达式里面是有列时,它的代价也等价于联表。

    而且你这两个不等价吧? 如果只根据 user_id 分组的话,第一个就不对。
    sniperking1234
        5
    sniperking1234  
    OP
       2023-04-28 14:11:20 +08:00
    @lysS 第一个是联表查询,sql 没有写全
    lysS
        6
    lysS  
       2023-04-28 14:15:39 +08:00
    那我建议第二种吧
    lysS
        7
    lysS  
       2023-04-28 14:17:32 +08:00
    不过 in 里面的 id 会越来越多吧?
    sniperking1234
        8
    sniperking1234  
    OP
       2023-04-28 14:19:42 +08:00
    @lysS 分页显示,理论上 id 数量可控
    yinmin
        9
    yinmin  
       2023-04-28 14:27:13 +08:00 via iPhone
    除非记录数上千万级别,否则建议第一种。程序简洁易维护更重要。
    liprais
        10
    liprais  
       2023-04-28 14:40:42 +08:00
    数据量两千万以下就第一种,
    两千万以上第二种也不好使,自己搞个 streaming 的拉倒
    opengps
        11
    opengps  
       2023-04-28 14:48:46 +08:00
    为了将来数据量变大时候轻松,一定要选第二种,云架构下的数据库也是尽可能避免高级用法,数据库层面回归原始的语句
    BugCry
        12
    BugCry  
       2023-04-28 14:51:38 +08:00
    ```
    with dt as ( select userid, count(userid) cnt from user group by userid)
    select cnt, user.user_id ,user_name ,user_type ,user_level ...
    from user, dt
    where user.userid = dt.userid
    ```
    我是这样操作的,不知道算不算最佳实践
    抛砖引玉哈
    sniperking1234
        13
    sniperking1234  
    OP
       2023-04-28 15:02:32 +08:00
    @BugCry 这样写 sql 的确很清晰,不清楚性能如何
    xinxingi
        14
    xinxingi  
       2023-04-28 15:29:14 +08:00
    对于这个需求,第一种方式是更合适的。使用 group by 语句来聚合数据并显示用户信息是一种常见的 SQL 操作,也是最常用的方式之一。第一种方式可以一次性查询出统计信息和用户信息,而且语句简单易懂。

    对于性能问题,将用户信息相关的字段放到 group by 中的确会增加查询语句的复杂度,但是如果你的数据库表结构设计得当,并且使用了正确的索引,那么性能问题应该不大。你可以考虑在 user_id 上创建索引以提高查询性能。

    第二种方式需要进行两次查询,这样的话会增加查询的复杂度和执行时间,而且也不太容易理解和维护。因此,如果数据量较大或者查询频率较高,第二种方式可能会导致性能问题。

    总的来说,第一种方式更简单、可读性更高,而且性能也不错,因此我建议你采用第一种方式来实现这个需求。
    xinxingi
        15
    xinxingi  
       2023-04-28 15:29:31 +08:00
    请列出两种方式的空间复杂度和时间复杂度

    第一种方式的时间复杂度为 O(NlogN),其中 N 为用户数量,因为查询语句需要进行 group by 操作,这需要对用户表中的数据进行排序。如果你在 user_id 上创建了索引,这个时间复杂度可能会更低。

    第一种方式的空间复杂度取决于查询语句返回的行数和每行数据的大小。如果查询结果集中的行数较少,那么空间复杂度也相对较低。如果每行数据的大小较大,查询结果集中的数据量也会增加,从而增加空间复杂度。

    第二种方式的时间复杂度为 O(NlogN),其中 N 为用户数量。第一次查询需要对用户表中的数据进行 group by 操作,这需要排序,因此时间复杂度与第一种方式相同。第二次查询需要根据 user_id 来查询用户信息,这个查询的时间复杂度为 O(MlogM),其中 M 为查询结果集中的行数。

    第二种方式的空间复杂度取决于第二次查询返回的行数和每行数据的大小。如果第二次查询返回的行数较少,那么空间复杂度也相对较低。如果每行数据的大小较大,查询结果集中的数据量也会增加,从而增加空间复杂度。
    xinxingi
        16
    xinxingi  
       2023-04-28 15:36:39 +08:00
    上面两段是,gpt 的问答,我个人建议你直接用第一种方式。可读性比第二种高,性能也比第二种更好。如果把数据量计算在内的话,当数据量超出一定量时,直接移交数据部门(大数据)去做,这种分析对于 mpp 架构的数据库来说,小菜一碟 ,OLAP 数据库 干的活就别为难 OLTP 数据库了。
    sniperking1234
        17
    sniperking1234  
    OP
       2023-04-28 15:46:38 +08:00
    @xinxingi 哈哈,一眼没看出来是 gpt 回答的
    house191
        18
    house191  
       2023-04-28 15:56:56 +08:00
    如果只用一张包含用户数据的订单表,第二种写法会输出重复的用户数据。一个用户有五个订单就会在结果里出现五次。还要再套一个去重逻辑。
    fiypig
        19
    fiypig  
       2023-04-28 16:04:48 +08:00
    我的话就第一种了, 第二种存在性能问题
    sniperking1234
        20
    sniperking1234  
    OP
       2023-04-28 16:08:08 +08:00
    @house191 第二种写法的话,第一条 sql 已经根据 user_id 聚合了,不会出现 user_id 数据重复的问题
    txy3000
        21
    txy3000  
       2023-04-28 16:38:41 +08:00
    既要 AP 又要 TP 怎么办
    mysql ->binlog -> block parsor-> clickhouse
    🐶
    2123123
        22
    2123123  
       2023-04-28 17:00:03 +08:00   ❤️ 1
    确保数据一致的话,没必要写 group by 里,select 里用 ANY() 就行了
    shinyruo2020
        23
    shinyruo2020  
       2023-04-28 17:10:19 +08:00
    如果 userId 唯一,可以把 group by 后面的多余字段去掉
    cheng6563
        24
    cheng6563  
       2023-04-28 17:16:15 +08:00
    就是第一种。要问数据量大了怎么办?搞读写分离,OLAP 数据库,数仓什么的,然后还是用第一种方法搞。
    sniperking1234
        25
    sniperking1234  
    OP
       2023-04-28 19:53:11 +08:00
    @2123123 这个方法可行
    idontcare
        26
    idontcare  
       2023-04-29 06:36:05 +08:00
    对于你的需求,第一种方式是更合适的选择,因为它只需要一个 SQL 查询语句就可以完成,而且可以同时得到用户信息和统计信息,避免了多次查询的开销。

    虽然将用户信息相关的字段放在 GROUP BY 子句中可能会导致一些冗余字段,但是通常不会带来明显的性能问题,特别是如果你的表不是非常大,那么这种性能影响会更小。另外,如果你的数据库引擎支持聚合函数和 GROUP BY 子句的优化,比如 MySQL 的 GROUP BY 优化,那么这种影响就更小了。

    如果你真的很关注性能问题,也可以考虑使用 MySQL 的 GROUP BY WITH ROLLUP 扩展,可以将多个聚合值合并成一行,同时包括每个聚合值的总计,这样可以避免使用多个 GROUP BY 子句。

    最后,如果你需要更好的性能,你可以考虑创建索引,特别是对于用户信息相关的字段,以便加速查询操作。如果你需要更复杂的查询操作,你可能需要使用视图或子查询等高级技术来进行优化。

    综上所述,第一种方式是更好的选择,但是你可以通过其他手段来进一步优化查询性能。

    ChatGPT may produce inaccurate information about people, places, or facts. ChatGPT Mar 23 Version
    goalidea
        27
    goalidea  
       2023-05-27 14:12:45 +08:00
    实现就行,不行交接给下一位接盘
    akira
        28
    akira  
       2023-06-02 07:45:06 +08:00
    个人更喜欢第二种写法。
    先处理过滤条件,再根据过滤好的数据获取需要的字段。 如果后续你还需要合并其他数据,也是单独写好就合并进来了。

    写代码的时候,你知道在一个函数里面尽量保持简单的逻辑,写 sql 的时候其实也是一样的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3342 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 10:46 · PVG 18:46 · LAX 02:46 · JFK 05:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.