删除 ONLY_FULL_GROUP_BY 的替代方法

Ser*_*gio 6 mysql mysql-5.5 group-by mysql-5.7

从 MySQL 5.5 升级到 MySQL 5.7 后,我的一些查询出现错误:

错误 1055 (42000):

SELECT 列表的表达式 #1 不在 GROUP BY 子句中,并且包含非聚合列“grocery.Product_Category.category_id”,该列在功能上不依赖于 GROUP BY 子句中的列;这与 sql_mode=only_full_group_by 不兼容

我做了我的研究并找到了问题的原因以及如何解决它,基本上我只需要从@@sql_mode 中删除 ONLY_FULL_GROUP_BY ,一切都会再次工作。

但是,我想知道这是否是正确的做法。有没有替代方法,也许是构建查询的更好方法?

这是我的情况(http://sqlfiddle.com/#!9/6f1bd):

我有两个表(我在这里简化了它们的结构,但基本相同):产品和类别以及一个多对多关系表,以允许产品属于多个类别:

SELECT * FROM Product;

+------------+---------+
| product_id | name    |
+------------+---------+
|          1 | Tomato  |
|          2 | Orange  |
|          3 | Banana  |
|          4 | Lettuce |
|          5 | Carrot  |
+------------+---------+
5 rows in set (0,00 sec)

SELECT * FROM Category;

+-------------+------------+
| category_id | name       |
+-------------+------------+
|           1 | Fruits     |
|           2 | Vegetables |
+-------------+------------+
2 rows in set (0,00 sec)
Run Code Online (Sandbox Code Playgroud)

我想从两个类别中获取产品,因此最简单的查询是:

SELECT * FROM Product JOIN Product_Category USING(product_id)
  JOIN Category USING(category_id);

+-------------+------------+---------+------------+
| category_id | product_id | name    | name       |
+-------------+------------+---------+------------+
|           1 |          1 | Tomato  | Fruits     |
|           1 |          2 | Orange  | Fruits     |
|           1 |          3 | Banana  | Fruits     |
|           2 |          1 | Tomato  | Vegetables |
|           2 |          4 | Lettuce | Vegetables |
|           2 |          5 | Carrot  | Vegetables |
+-------------+------------+---------+------------+
6 rows in set (0,00 sec
Run Code Online (Sandbox Code Playgroud)

但是,如果一个产品同时存在于两个类别中,我只需要一次,那么执行 DISTINCT 选择将无济于事,因为 category_id 不同:

SELECT DISTINCT * FROM Product JOIN Product_Category USING(product_id) JOIN Category USING(category_id);

+-------------+------------+---------+------------+
| category_id | product_id | name    | name       |
+-------------+------------+---------+------------+
|           1 |          1 | Tomato  | Fruits     |
|           1 |          2 | Orange  | Fruits     |
|           1 |          3 | Banana  | Fruits     |
|           2 |          1 | Tomato  | Vegetables |
|           2 |          4 | Lettuce | Vegetables |
|           2 |          5 | Carrot  | Vegetables |
+-------------+------------+---------+------------+
6 rows in set (0,00 sec)
Run Code Online (Sandbox Code Playgroud)

因此,在 MySQL 5.5 中,我在 product_id 字段上使用了 GROUP BY 子句:

SELECT * FROM Product JOIN Product_Category USING(product_id)
  JOIN Category USING(category_id) GROUP BY product_id;

+-------------+------------+---------+------------+
| category_id | product_id | name    | name       |
+-------------+------------+---------+------------+
|           1 |          1 | Tomato  | Fruits     |
|           1 |          2 | Orange  | Fruits     |
|           1 |          3 | Banana  | Fruits     |
|           2 |          4 | Lettuce | Vegetables |
|           2 |          5 | Carrot  | Vegetables |
+-------------+------------+---------+------------+
5 rows in set (0,00 sec)
Run Code Online (Sandbox Code Playgroud)

这有效地删除了重复项,我知道结果不是确定性的,但我不在乎番茄是否出现在水果或蔬菜类别中,我所关心的只是在结果集中只得到一次。

但是这个使用 MySQL 5.7 的查询会导致上面提到的错误。

所以,我的问题是:是否有另一种(也许更好)的方法来获得相同的结果而不必从@@sql_mode 中删除 ONLY_FULL_GROUP_BY?

And*_*y M 5

我建议以最小化需要放入 GROUP BY 的列数的方式重写查询。在您的情况下,您可以通过仅将分组应用于表来做到这一点Product_Category

根据您的示例,该表具有以下条目:

+------------+-------------+
| product_id | category_id |
+------------+-------------+
|          1 |           1 |
|          2 |           1 |
|          3 |           1 |
|          1 |           2 |
|          4 |           2 |
|          5 |           2 |
+------------+-------------+
Run Code Online (Sandbox Code Playgroud)

由于您希望产品名称在输出中是唯一的,因此请将此表按 分组product_id,并category_id选择例如每个产品的最小值:

SELECT
  product_id,
  MIN(category_id) AS category_id
FROM
  Product_Category
GROUP BY
  product_id
Run Code Online (Sandbox Code Playgroud)

这会给你这样的输出:

+------------+-------------+
| product_id | category_id |
+------------+-------------+
|          1 |           1 |
|          2 |           1 |
|          3 |           1 |
|          4 |           2 |
|          5 |           2 |
+------------+-------------+
Run Code Online (Sandbox Code Playgroud)

您可以看到每个产品只列出一次。将该表连接到其他两个表不会产生重复项。因此,只需将上面的查询作为派生表替换为Product_Category查询中的(当然,也从中删除 GROUP BY ):

SELECT
  *
FROM
  Product
  JOIN (
    SELECT
      product_id,
      MIN(category_id) AS category_id
    FROM
      Product_Category
    GROUP BY
      product_id
  ) AS pc USING(product_id)
  JOIN Category USING(category_id)
;
Run Code Online (Sandbox Code Playgroud)