如何在MySQL中正确GROUP BY?

时间:2013-12-11 05:54:06

标签: mysql sql group-by

我有以下(为演示目的故意非规范化)样本 CARS 表:

| CAR_ID | OWNER_ID | OWNER_NAME | COLOR |
|--------|----------|------------|-------|
|      1 |        1 |       John | White |
|      2 |        1 |       John | Black |
|      3 |        2 |       Mike | White |
|      4 |        2 |       Mike | Black |
|      5 |        2 |       Mike | Brown |
|      6 |        3 |       Tony | White |

如果我想计算每位车主的车数并退回:

| OWNER_ID | OWNER_NAME | TOTAL |
|----------|------------|-------|
|        1 |       John |     2 |
|        2 |       Mike |     3 |
|        3 |       Tony |     1 |

我知道我可以写下面的查询:

SELECT owner_id, owner_name, COUNT(*) total FROM cars
GROUP BY owner_id, owner_name

但是,从owner_name子句中删除GROUP BY会给我相同的结果。

  1. 这两个查询之间有什么区别?
  2. 在什么情况下,我应该在SELECT声明中的所有非聚合字段进行分组,哪些不应该分组?
  3. 您能举例说明在删除非聚合字段时该分组会返回不同的结果并解释原因吗?

1 个答案:

答案 0 :(得分:2)

首先要明确的是SQL不是MySQL。

在标准SQL中,不允许按非聚合字段的子集进行分组。原因很简单。假设我正在运行此查询:

SELECT color, owner_name, COUNT(*) FROM cars
GROUP BY color

该查询没有任何意义。即使试图解释它也是不可能的。当然,它正在选择颜色并计算每种颜色的汽车数量。但是,它还添加了owner_name字段,并且对于给定颜色可以有许多所有者,因为它是White颜色的情况。因此,如果单个owner_name可能有多个color值恰好是GROUP BY子句中的唯一字段...那么将返回哪个owner_name?< / p>

如果需要返回owner_name,则应添加某种标准以仅选择其中一个,例如,按字母顺序选择第一个,在这种情况下为John。该标准将导致添加聚合函数MIN(owner_name),然后查询将再次有意义,因为它将至少通过select语句中的所有非聚合字段进行分组。

正如您所看到的,标准SQL在分组中缺乏灵活性是一个明确而实际的原因。如果不是这样,你可能会面临一个尴尬的情况,其中列的值将是不可预测的,这不是一个好词,特别是如果正在运行的查询显示您的银行帐户交易。

话虽如此,那为什么MySQL会允许那些可能没有意义的查询呢?更糟糕的是,上面的查询中的错误可能只是语法检测!简短的回答是:表现。长期的答案是,在某些情况下,根据数据关系,从组中获得不可预测的值将产生可预测的值。

如果你还没有想到它,那么你可以预测从一个组中获取不可预测元素所得到的值的唯一方法是,如果组中的所有元素都是相同的。这种情况的一个明显例子是在同一个问题中的示例查询中。查看表中owner_idowner_name的关系。很明显,给定任何owner_id,例如2,您只能有一个不同的owner_name。即使有很多行,通过选择任何行,您将得到Mike作为结果。在正式的数据库术语中,这可以解释为 owner_id在功能上确定owner_name

让我们仔细研究一下完全可用的MySQL查询:

SELECT owner_id, owner_name, COUNT(*) total FROM cars
GROUP BY owner_id

鉴于任何owner_id,这将返回相同的owner_name,因此将其添加到GROUP BY子句不会导致返回更多行。即使添加聚合函数MAX(owner_name)也不会导致返回的行数减少。结果数据将非常相似。在这两种情况下,查询都会立即转换为合法的标准SQL查询,因为至少所有非聚合字段都将按其分组。因此,有3种方法可以获得相同的结果。

但是,正如我之前提到的,这种非标准分组具有性能优势。您可以查看详细解释的so underrated link以获取更多详细信息,但我将引用最重要的部分:

  

您可以通过避免不必要的列排序和分组来使用此功能来获得更好的性能。 [...]服务器可以自由选择每个组中的任何值,因此除非它们相同,否则所选的值是不确定的。

值得一提的是,结果不一定是错误,而是不确定。换句话说,获得预期结果并不意味着您已经编写了正确的查询。编写正确的查询将始终为您提供预期的结果。

如您所见,将此MySQL扩展应用于GROUP BY子句可能是值得的。无论如何,如果这还不是100%明确,那么有一条经验法则可以确保您的分组始终是正确的:始终至少对select子句中的所有非聚合字段进行分组< / strong>即可。在某些情况下,您可能会浪费一些CPU周期,但这比返回不确定结果要好。如果您仍然害怕没有正确分组,那么更改ONLY_FULL_GROUP_BY SQL模式可能是最后的手段:)

祝你的分组正确且高效......或者至少是正确的。