使用 HAVING GROUP_CONCAT 计算记录行

Posted

技术标签:

【中文标题】使用 HAVING GROUP_CONCAT 计算记录行【英文标题】:Counting record rows with HAVING GROUP_CONCAT 【发布时间】:2014-03-12 10:55:02 【问题描述】:

我的问题(可能)有点复杂,我试着把它写下来,以便理解。 我在 php 中有一个类,它根据几种情况和参数生成 mysql 查询(然后在 dinamyc 表中显示查询结果)。 例如,这里是表格和示例查询:

很简单,有用户,每个用户可以分配一个或多个配置文件。 例如,我们只有一个用户,他有两个个人资料:

[USER]
userid    login     password     status    reg_date
------    -------   ----------   ------    -------------------
1         JohnDoe   hd98349...   0         2014-01-09 16:00:55

[PROFILE]
profile_id  profile_name    
----------  ------------
1           admin
2           root
3           user

[USER_PROFILE]
relation_id   user   profile
-----------   -----  -------
1             1      1
2             1      2

我的 PHP 代码会生成一个如下所示的查询:

SELECT 
    userid AS 'User id', 
    login AS 'User name', 
    GROUP_CONCAT(profile_name SEPARATOR ', ') AS 'Profile(s)', 
    `status`  
FROM `user` LEFT JOIN user_profile ON `user`.userid = user_profile.`user`
         LEFT JOIN `profile` ON user_profile.`profile` = `profile`.profile_id  
WHERE 
    status IN (0, 1) 
ORDER BY reg_date ASC 
LIMIT 0 ,10

这个查询的结果是:

[RESULT]
User id    User name    Profile(s)   status
-------    ---------    ----------   ------
1          JohnDoe      root, admin  0

当我想对源自子查询或复杂表达式(如 GROUP_CONCAT(profile_name SEPARATOR ', '))的列使用过滤器时,我得到错误的结果:

SELECT COUNT(*) AS 'all_rows'  
FROM `user` LEFT JOIN user_profile ON `user`.userid = user_profile.`user`
      LEFT JOIN `profile` ON user_profile.`profile` = `profile`.profile_id  WHERE          status IN (0, 1) 
HAVING GROUP_CONCAT(profile_name SEPARATOR ', ') LIKE '%root%'

这将导致:'all_rows' => 2。 这是因为 HAVING 中的 GROUP_CONCAT 不会分组,但是如果我将它放在 SELECT 中,例如: SELECT COUNT(GROUP_CONCAT(profile_name SEPARATOR ', ')) 我会收到错误

所以问题是如何在过滤器(WHERE / HAVING)表达式中使用 GROUP_CONCAT 获取所有行数?

编辑:在我运行主查询之前,我需要知道它会产生多少记录行,没有 LIMIT 限制。我使用它来计算 LIMIT 的起始行值以用于分页目的。

【问题讨论】:

你能在 SQL Fiddle (www.sqlfiddle.com) 上进行设置吗? 就个人而言,我倾向于完全避免使用 GROUP_CONCAT(和 CONCAT)。我更喜欢在应用程序级别处理给定问题的这些方面。 GROUP_CONCAT() row count when grouping by a text field的可能重复 您的查询中没有GROUP BY 子句。因此,您将所有与 WHERE 条件匹配的行聚合成一个结果。 SQL_CALC_FOUND_ROWS 选项不能解决您遇到的问题吗? 【参考方案1】:

你能试试这个结果吗?您没有告诉查询引擎要分组的内容,它似乎猜错了。

SELECT `user`.userid,
    GROUP_CONCAT(profile_name SEPARATOR ', ') as roles,
    COUNT(*) AS 'all_rows'  
FROM `user` 
    LEFT JOIN user_profile ON `user`.userid = user_profile.`user`
    LEFT JOIN `profile` ON user_profile.`profile` = `profile`.profile_id  
WHERE          status IN (0, 1) 
GROUP BY `user`.userid
HAVING GROUP_CONCAT(profile_name SEPARATOR ', ') LIKE '%root%'

【讨论】:

答案还是一样,我的朋友..它的 2【参考方案2】:

您有一个异常查询,其中计数将返回 0 或 1。您绝对可以通过使用子查询来解决此问题:

SELECT COUNT(*) AS 'all_rows'  
FROM (SELECT GROUP_CONCAT(profile_name SEPARATOR ', ') as pns
      FROM `user` LEFT JOIN
           user_profile
           ON `user`.userid = user_profile.`user` LEFT JOIN
           `profile`
           ON user_profile.`profile` = `profile`.profile_id
      WHERE status IN (0, 1) 
     ) t
WHERE pns LIKE '%root%';

您还可以通过将逻辑移至where 子句来解决问题:

SELECT COUNT(*)
FROM `user` LEFT JOIN
     user_profile
     ON `user`.userid = user_profile.`user` LEFT JOIN
     `profile`
     ON user_profile.`profile` = `profile`.profile_id
WHERE status IN (0, 1) and profile_name like '%root%';

【讨论】:

这些是好主意,它们应该可以工作,但在我的情况下,查询是动态生成的,无法将原始列与子查询或复杂表达式的结果列区分开来。我的班级将 SELECT 部分中的所有列一起处理,因此我不能将 GROUP_CONCAT(profile_name SEPARATOR ', ') 部分与其他部分分开。【参考方案3】:

我喜欢一个解决方案: 这是记录计数查询的外观:

SELECT COUNT(*) AS 'all_rows' 
FROM (SELECT 
         userid AS 'User id', 
         login AS 'User name',    
         GROUP_CONCAT(profile_name SEPARATOR ', ') AS 'Profile(s)', 
         status  
      FROM `user` LEFT JOIN user_profile ON `user`.userid = user_profile.`user` 
            LEFT JOIN `profile` ON user_profile.`profile` = `profile`.profile_id  
      HAVING 
          status IN (0,    1) 
          AND  GROUP_CONCAT(profile_name SEPARATOR ', ') LIKE '%root%') t

这是主要的查询:

SELECT 
    userid AS 'User id', 
    login AS 'User name', 
    GROUP_CONCAT(profile_name SEPARATOR ', ') AS 'Profil(ok)', 
    status  
FROM `user` LEFT JOIN user_profile ON `user`.userid = user_profile.`user`
            LEFT JOIN `profile` ON user_profile.`profile` = `profile`.profile_id  
HAVING 
    status IN (0, 1) 
    AND  GROUP_CONCAT(profile_name SEPARATOR ', ') LIKE '%root%'  
ORDER BY reg_date ASC 
LIMIT 0 ,10

我必须在任何地方都使用HAVING 而不是WHERE,因为正如我所说,主查询是通过程序生成的。这就是我在 SELECT 部分设置列的方式:

$listObj->setColumns(array('userid' => 'User id',
               'login'  => 'User name',
               'GROUP_CONCAT(profile_name SEPARATOR \', \')' => 'Profile(s)'));

当用户在过滤器输入中键入 %root% 表达式时,我无法确定 GROUP_CONCAT(profile_name SEPARATOR ', ') LIKE %root% 是否进入 WHERE 部分或 HAVING 部分,因为它与任何其他列(如 userid AS 'User id'login AS 'User name' 列)

剩下的技术问题是,在任何地方都使用HAVING 是不是一个错误,即使一个简单的WHERE 就足够了?

【讨论】:

以上是关于使用 HAVING GROUP_CONCAT 计算记录行的主要内容,如果未能解决你的问题,请参考以下文章

使用 GROUP_CONCAT、GROUP BY、HAVING 进行选择

为啥 HAVING 和 GROUP_CONCAT 在此查询中不能一起工作?

mysql 数据操作 单表查询 having 过滤 练习

如何使用 group_concat 和左连接计算 mysql 查询的结果

CakePHP 2.2.4 无法使用 HAVING 子句和计算字段对结果进行分页 - Haversine 公式

如何在codeigniter中计算group_concat