当 sql_mode=only_full_group_by 时,在 mysql 中完成 Group By 工作的最佳方法是啥

Posted

技术标签:

【中文标题】当 sql_mode=only_full_group_by 时,在 mysql 中完成 Group By 工作的最佳方法是啥【英文标题】:What is the best way to do the job of Group By in mysql when sql_mode=only_full_group_by当 sql_mode=only_full_group_by 时,在 mysql 中完成 Group By 工作的最佳方法是什么 【发布时间】:2021-03-21 13:29:24 【问题描述】:

我想执行一个查询,例如获取数据库中任何类型的最后一条记录,在我的本地主机上,我使用 Maria Db,查询如下:

SELECT * 
  FROM table_a
 WHERE column_a=999
    OR column_b=999 
 GROUP 
    BY `group`;

group 是我在其中保存类型的列,例如:营销、博客、订单等

此查询在本地运行良好,但在服务器上出现以下错误:

SQLSTATE[42000]: 
Syntax error or access violation: 
1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column
'db_name.table_a.id' which is not functionally dependent on columns in GROUP BY clause;
this is incompatible with sql_mode=only_full_group_by\n
The SQL being executed was: 
SELECT * FROM `table_a` WHERE (`column_a`=999) OR (`column_b`=999) GROUP BY `group`"

根据mysql document,我可以使用以下命令来实现这一点:

SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));

但是我对 Db 没有足够的权限并得到以下错误:

#1227 - Access denied; you need (at least one of) the SUPER privilege(s) for this operation

我让主机帮我做这件事,他们回答说他们不想做这个操作

我使用的是 YII2 框架,现在我想在框架的 database_config.php 选项中添加它,或者将查询更改为具有相同结果的其他内容,并且不损失性能

【问题讨论】:

SELECT * 没有意义。当您执行GROUP BY group 时,具有相同group 值的所有行都将被视为一个组。对于此行组,必须为每一列返回一个值,但存在很多值。如何确定您需要什么值?首先(按什么顺序?)?最大?随机的?还有什么? 托管公司已为sql_mode 配置了正确的值。您不应尝试更改 sql_mode 以允许无效查询。您应该改正您的查询。我写了一个答案来解释为什么这种类型的查询在这里无效:***.com/a/13999903/20860 但我对 Db 没有足够的权限 当然。但是如果你真的想改变 SQL 模式并执行你不合逻辑的查询,那么你必须改变 SESSION 而不是 GLOBAL 设置。 最简单的选择是完全拒绝问题的前提。这简直是​​荒谬的。 @Akina 这正是我想要的,我想要每种类型的最后一条记录,可能是 8 种类型所以 8 条记录 【参考方案1】:

ONLY_FULL_GROUP_BY一件好事,它强制执行基本的 ANSI SQL 规则。不要更改它,而是修复您的代码。

从那里开始:你想要完整的记录,所以你不应该考虑聚合,而应该考虑过滤

那么:在一个数据库表中,记录是无序的;为了使您的问题有意义,您需要一个定义每个组中行的顺序的列,因此“最后”记录的含义是明确的。假设您有这样的专栏,名为id

这是解决此 top-1-per-group 问题的典型方法,使用相关子查询进行过滤:

SELECT * 
FROM table_a a
WHERE 
    999 IN (column_a, column_b)
    AND id = (
        SELECT MAX(a1.id) 
        FROM table_a a1 
        WHERE 999 IN (a1.column_a, a1.column_b) AND a1.grp = a.grp
    )

或者,如果您运行的是 MySQL 8.0,则可以使用窗口函数:

SELECT *
FROM (
    SELECT a.*,
        ROW_NUMBER() OVER(PARTITION BY grp ORDER BY id DESC) rn
    FROM table_a a
    WHERE 999 IN (column_a, column_b)
) a
WHERE rn = 1

旁注:group 是语言关键字,因此不适合作为列名。我在查询中将其重命名为grp

【讨论】:

这是个好办法;但是,我宁愿根据yiiframework.com/doc/guide/2.0/en/db-active-record 使用 Active Record,而不是为我的代码的所有部分编写查询(可能需要重构 100 多个查询)。而且我的代码使用其他托管服务没有这个问题,但是我现在的客户坚持使用这个托管,所以如果我没有找到任何其他解决方案,我会尝试这种方法。【参考方案2】:

有几种方法可以“绕过”sql_mode但请注意,您得到的结果可能不正确。

首先你可以使用ANY_VALUE()。像这样的例子:

SELECT any_value(column_a), any_value(column_b), `group` FROM table_a 
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;

使用ANY_VALUE() 函数时,您必须从表中写入SELECT 中的所有列,并附加ANY_VALUE(),除了您在GROUP BY 中使用的列。

使用 MAX() 或 MIN() 可以返回结果,但仍然可能不正确 结果,尤其是对于计数超过 1 的任何行:

SELECT MAX(column_a), MAX(column_b), `group`
FROM table_a 
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;

使用GROUP_CONCAT 可以让您了解非分组列中的值。将结果与上面的其他查询进行比较,您可以看到在返回多个计数的行上,其他查询是否根据您的要求返回?

SELECT group_concat(column_a), group_concat(column_b), group_concat(`group`)
FROM table_a 
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;

我不确定您是否可以这样做,但您可以暂时关闭 sql_mode,然后您应该能够运行您的查询:

SET sql_mode=""; -- you don't need to set global privilege.
SELECT * FROM table_a 
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;

Demo here.

不过,最好的选择是保留 sql_mode 原样并根据要求构造查询。

P/S:GROUP 是 MySQL 和 MariaDB 中的保留字。您可以将其用作列名,但您必须始终添加反引号来定义列,否则,运行查询将返回类似的错误

查询:select * from table_a group by group

错误代码:1064 您的 SQL 语法有错误;查看与您的 MariaDB 服务器版本相对应的手册,了解在第 1 行的“组”附近使用的正确语法

【讨论】:

嗨,我确实使用了 any_value 并将所有列添加到 select 中,我的 4 列有关系,但我仍然得到同样的错误。 GROUP BY group 也有同样的错误。对于您的第三个选项,我正在寻找它,我可以逐个查询进行查询,但可能有超过 100 个查询使用了 group by,我正在寻找一种方法来通过设置 Yii2 框架来添加它,到目前为止,没有找到解决方案。

以上是关于当 sql_mode=only_full_group_by 时,在 mysql 中完成 Group By 工作的最佳方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

[SUCTF 2019]EasySQL1 及sql_mode

mysql 从聚合函数group by到sql_mode

GROUP BY不适用于MySQL 5.7,因为5.7使用SQL_MODE的“ONLY_FULL_GROUP_BY”选项。

mysql出现GROUP BY clause错误解决办法

MySQL的SQL_MODE

mysql ANSI_QUOTES 这个sql_mode的作用