如何解决 laravel eloquent 中与 sql_mode=only_full_group_by 不兼容的问题?

Posted

技术标签:

【中文标题】如何解决 laravel eloquent 中与 sql_mode=only_full_group_by 不兼容的问题?【英文标题】:How can I solve incompatible with sql_mode=only_full_group_by in laravel eloquent? 【发布时间】:2017-10-02 06:31:14 【问题描述】:

我的 laravel 雄辩是这样的:

$products = Product::where('status', 1)
            ->where('stock', '>', 0)
            ->where('category_id', '=', $category_id)
            ->groupBy('store_id')
            ->orderBy('updated_at', 'desc')
            ->take(4)
            ->get();

执行时出现如下错误:

SQLSTATE[42000]:语法错误或访问冲突:1055 表达式 #1 的 SELECT 列表不在 GROUP BY 子句中并且包含非聚合 列“myshop.products.id”在功能上不依赖于 GROUP BY 子句中的列;这与 sql_mode=only_full_group_by (SQL: select * from products where status = 1 and stock > 0 and category_id = 5 group by store_idupdated_at desc 限制 4) 排序

我该如何解决?

【问题讨论】:

【参考方案1】:

我有一个类似的问题,并通过在数据库连接设置中禁用 mysql 严格模式来解决它。

'connections' => [
    'mysql' => [
        // Behave like MySQL 5.6
        'strict' => false,

        // Behave like MySQL 5.7
        'strict' => true,
    ]
]

您可以在this blog post by Matt Stauffer找到更多配置设置

【讨论】:

如果我们设置 'strict' => false 那么它可能会产生安全漏洞 在浪费了一天之后来到这里,这解决了我的问题 @naabster 谢谢你,你可能为我节省了一天的时间来解决这个问题。问题:在这里不使用 strict 有什么缺点吗?另一条评论提到了安全漏洞。在我的情况下,原始 SQL 正在工作,但是当我通过 Laravel(查询生成器,5.6)时,我得到了“唯一完整组”错误。 花了一段时间才找到这个答案。最佳解决方案。太感谢了!请,OP。将此标记为正确答案。【参考方案2】:

在文件夹 config => database.php 确保 mysql strictfalse强>,像这样

'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8',
    'collation' => 'utf8_general_ci',
    'prefix' => '',
    'strict' => false,
    'engine' => null,
],

如果 strict 为真,则将其设为假,然后通过在 cmd 中运行此命令来清除配置现金

php 工匠配置:清除

【讨论】:

谢谢您,您的'strict' => false 建议对我很有效【参考方案3】:

我通过添加“模式”选项并仅在 config => database.php

中设置我想要启用的模式解决了这个问题
'mysql' => [
    ...
    'modes' => [
        'STRICT_ALL_TABLES',
        'ERROR_FOR_DIVISION_BY_ZERO',
        'NO_ZERO_DATE',
        'NO_ZERO_IN_DATE',
        'NO_AUTO_CREATE_USER',
    ],
],

在this tutorial查看更多详情

【讨论】:

【参考方案4】:

这是因为最新版本的 MySQL 在 group by 子句方面表现得与大多数 dbms 一样;一般规则是

如果您使用group by,则select 中的所有列必须存在于group by 中或由聚合函数聚合(sumcountavg 等)

您当前的查询按store_id 分组,但由于您选择了所有内容,因此不遵守上述规则。

【讨论】:

如果我在 mysql 中运行这个查询:select * from products where status = 1 and stock > 0 and category_id = 5 group by store_id order by updated_at desc limit 4,它可以工作。因此,错误似乎是通过雄辩的 laravel 我喜欢这个答案(它解释了为什么会发生这种情况),而不仅仅是“关闭严格模式”!更改查询似乎比关闭更全局的设置更合适和“本地”。谢谢【参考方案5】:

设置

'strict' => false

在您的 config/database.php 文件中。在数组中connections => mysql =>

就我而言,我使用的是 mysql 5.7 Laravel 5.7

【讨论】:

如果它不起作用,做一个php artisan config:clear【参考方案6】:

您不应禁用strict 或删除ONLY_FULL_GROUP_BY。问题是您的查询不明确。这可能不会对您的输出产生影响,或者可能会导致巨大的问题。你最好确定一下。

可以在Percona 上阅读很好的解释(总结如下)。

问题

考虑以下情况:

+----+--------------------+---------+---------------------+
| id | page_url           | user_id | ts                  |
+----+--------------------+---------+---------------------+
|  1 | /index.html        |       1 | 2019-04-17 12:21:32 |
|  2 | /index.html        |       2 | 2019-04-17 12:21:35 |
|  3 | /news.php          |       1 | 2019-04-17 12:22:11 |
|  4 | /store_offers.php  |       3 | 2019-04-17 12:22:41 |
|  5 | /store_offers.html |       2 | 2019-04-17 12:23:04 |
|  6 | /faq.html          |       1 | 2019-04-17 12:23:22 |
|  7 | /index.html        |       3 | 2019-04-17 12:32:25 |
|  8 | /news.php          |       2 | 2019-04-17 12:32:38 |
+----+--------------------+---------+---------------------+

现在我们要发出一个查询来计算访问次数最多的页面。这可能是你习惯写的:

SELECT page_url, user_id, COUNT(*) AS visits 
FROM web_log 
GROUP BY page_url 
ORDER BY COUNT(*) DESC;

但是看看结果:

+-------------------+---------+--------+
| page_url          | user_id | visits |
+-------------------+---------+--------+
| /index.html       |       1 |      3 |
| /news.php         |       1 |      2 |
| /store_offers.php |       3 |      2 |
| /faq.html         |       1 |      1 |
+-------------------+---------+--------+

查询有效,但并不真正正确。很容易理解,page_url 是分组函数的列,是我们最感兴趣的值,我们希望计数是唯一的。此外,visits 列也不错,因为它是计数器。但是user_id 呢?此列代表什么?

我们对page_url 进行了分组,因此user_id 返回的值只是该组中的值之一。事实上,不仅是用户访问了 index.html,用户 2 和 3 也访问了该页面。那么我们应该如何看待这个价值呢?是第一个访客吗?是最后一个吗?

我们不知道正确的答案! user_id 列的值是组的随机项!

解决方案

您需要考虑是否需要groupBy() 中未使用的值。如果没有,那么只需使用 select() 来明确命名您需要的列。

如果您确实需要 groupBy() 中未使用的列,请使用聚合函数(如 ANY_VALUE()GROUP_CONCAT()MAX())作为 Laravel selectRaw 查询的一部分。然后,您可以确定您的查询提供了您所期望的。

所以在上面的例子中,你可以这样做:

SELECT page_url, ANY_VALUE(user_id), COUNT(*) AS visits 
FROM web_log 
GROUP BY page_url 
ORDER BY COUNT(*) DESC;

或者在 Laravel 中:

WebLog::selectRaw('page_url', 'ANY_VALUE(user_id)', 'COUNT(*) AS visits')
->groupBy('page_url')
->orderBy('visits')
->get();

【讨论】:

【参考方案7】:

我通过在 config/database.php 文件中设置模式解决了这个问题。

设置模式如下:

'modes'  => [
                'STRICT_TRANS_TABLES',
                'NO_ZERO_IN_DATE',
                'NO_ZERO_DATE',
                'ERROR_FOR_DIVISION_BY_ZERO',
                'NO_ENGINE_SUBSTITUTION',
            ]

用于mysql驱动

'mysql' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST', 'localhost'),
        'port' => env('DB_PORT', '3306'),
        'database' => env('DB_DATABASE', 'forge'),
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        'charset' => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix' => '',
        'strict' => true,
        'engine' => null,
        'modes'  => [
            'ONLY_FULL_GROUP_BY',
            'STRICT_TRANS_TABLES',
            'NO_ZERO_IN_DATE',
            'NO_ZERO_DATE',
            'ERROR_FOR_DIVISION_BY_ZERO',
            'NO_ENGINE_SUBSTITUTION',
        ]
    ],

【讨论】:

它通过从列表中删除 'ONLY_FULL_GROUP_BY' 来工作。【参考方案8】:

.env 文件中添加变量:DB_STRICT=false

并从以下位置替换文件:config/database.php,下一个代码'strict' => true ON 'strict' => (env('DB_STRICT', 'true') === 'true' ? true : false).

祝你好运。

【讨论】:

我已经在 Lumen 8 中完成了它,但仍然无法正常工作,还有其他建议。谢谢。【参考方案9】:

我做了什么作为一种变通方法,为了防止进一步的安全问题,我做了这样的事情:

 public function getLatestModels ()
        \DB::statement("SET SQL_MODE=''");
        $latestInserted = Glasses::with('store.deliveryType','glassesHasTags','glassesHasColors','glassesHasSizes','glassesHasImages','glassesBrand','glassesMaterial')->whereRaw("store_id in (select distinct store_id from glasses)")->groupBy('store_id')->orderBy('created_at')->take(8)->get();
        \DB::statement("SET SQL_MODE=only_full_group_by");

        return $latestInserted;
    

这是其他答案的一种组合。 另外,如果您正在使用 “使用 Illuminate\Support\Facades\DB;” 您不需要在上面的那些 DB 语句中使用反斜杠。

这里唯一的缺点是我们对 db 进行了三个调用:(

附言 正如我看到@Felipe Pena 的回答,我猜第二个语句是不必要的

【讨论】:

【参考方案10】:
 #Have the following method in your helper file
if (!function_exists('set_sql_mode')) 
/**
 * @param string $mode
 * @return bool
 */
function set_sql_mode($mode = '')

    return \DB::statement("SET SQL_MODE=''");


然后调用 set_sql_mode('');就在雄辩/查询之前

【讨论】:

这会破坏整个设置。您可能只想从中提取 ONLY_FULL_GROUP_BY ..【参考方案11】:

如前所述,将严格模式设置为 false 可能会产生安全错误,我正在做的是在需要它的查询之前将 sql_mode 设置为空。请注意,这是一个临时更改,一旦您的连接关闭(通过 laravel 请求),您将被设置为原始 sql_mode=only_full_group_by (或更高)。

DB::statement("SET sql_mode = ''");

干杯,编码愉快...

ps.:这不是 laravel 的错误,如果您尝试直接在数据库上执行此查询,您将面临相同的结果。这种解决方法适用于 mysql 以及第一个语句,并且再次将是临时会话更改,而不是永久性的。

【讨论】:

我使用的是 Lumen 8。我添加了这条语句 "DB::statement("SET sql_mode = '' ");"在每个控制器的构造函数中,它对我有用。谢谢亲爱的。【参考方案12】:

检查查询:

Product::where('status', 1)
            ->where('stock', '>', 0)
            ->where('category_id', '=', $category_id)
            ->groupBy('store_id')
            ->orderBy('updated_at', 'desc')
            ->take(4)
            ->get();

在这里,您按store_id 对数据进行分组,并获取结果集中不允许的所有列。要解决它,请选择store_id 或对其进行聚合函数或将系统变量sql_mode=only_full_group_by 更改为SET sql_mode = ''

Reference

要在 Laravel 中设置这个,试试这个:

'strict' => false

in your config/database.php file. In array connections => mysql =>

【讨论】:

如果我在 mysql 中运行这个查询:select * from products where status = 1 and stock > 0 and category_id = 5 group by store_id order by updated_at desc limit 4,它可以工作。因此,错误似乎是通过雄辩的 laravel laravel如何设置sql模式? 如何在共享服务器上更改 SET sql_mode = ''?【参考方案13】:

要仅选择聚合列,请使用raw methods provided by Laravel 之一。例如:

Product::selectRaw('store_id')
        ->where('status', 1)
        ->groupBy('store_id')
        ->get();

【讨论】:

以上是关于如何解决 laravel eloquent 中与 sql_mode=only_full_group_by 不兼容的问题?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Laravel/Eloquent 中获得完整的关系

如何使用 Laravel Eloquent 和 Laravel Query Builder 进行联合查询?

Laravel Eloquent 序列化:如何重命名属性?

如何使用 eloquent 在 laravel 迁移中删除 POSTGRES 表或视图?

Laravel 按 Eloquent 关系分组

使用 Laravel Eloquent 使用复合键更新表