【问题标题】:Do I outsmart SQL Server or there is a reason for this?我比 SQL Server 更聪明还是有原因?
【发布时间】:2013-09-21 17:17:38
【问题描述】:

我有以下表格

CREATE TABLE Foos (
    [Id] INT IDENTITY,
    -- Other fields
)

CREATE TABLE Boos (
    [Id] INT IDENTITY,
    [FooId] INT,
    -- Other fields
)

我正在尝试执行一个非常简单的查询:

SELECT f.Id, COUNT(*)
FROM Foos f
JOIN Boos b on f.Id = b.FooId
GROUP BY b.FooId

显然,由于GROUP BY,我收到了错误消息。错误信息是

选择列表中的列“Foo.Id”无效,因为它不是 包含在聚合函数或 GROUP BY 子句中。

当我将分组更改为 GROUP BY f.Id 时,一切都恢复正常了。

我的问题是,为什么 SQL Server 已经确定连接中的 f.Id = b.FooIdFoo.IdIDENTITY 中是唯一的,并且分组将在逻辑上返回相同的计数,但为什么会抛出该错误由于主键上的连接?

【问题讨论】:

  • 我很确定这只是准确实施并考虑所有情况的复杂性。 SQl-Server 更容易对其规则非常严格。如果列在功能上依赖于 group by 中的列,则 SQL 标准允许列在 select 中而不是 group by 或聚合中。 PostgreSQL 是最接近准确实现这一点的 DBMS,但它仍然没有完全实现标准(例如,您的查询失败,但如果您按 f.ID 分组,则可以包括 Foos 中的所有列 - sqlfiddle.com/#!12/b420c/3

标签: sql sql-server select join group-by


【解决方案1】:

SQL Server 无法一致地推断此类键关系 - 如果 boos.FooId 可以为空并且您的查询是:

SELECT f.Id, COUNT(*)
FROM Foos f
  LEFT JOIN Boos b on f.Id = b.FooId
GROUP BY b.FooId

您有一组记录,其中 b.fooId 为 NULL,而 f.Id 可能不同。

您还可以在其他可能导致 NULLS 或其他类型的不等价的条件下加入 - 例如你可以像这样加入:

SELECT f.Id, COUNT(*)
FROM Foos f
  JOIN Boos b on SUBSTRING(f.Id, 2, 4) = SUBSTRING(b.FooId, 2, 4)
GROUP BY b.FooId

所以 - 是的,这可以在一个简单、常见的情况下进行推理,但仅限于这种情况。这会导致不一致,并最终证明让开发人员更加沮丧。

【讨论】:

    【解决方案2】:

    SQL Server 根本没有考虑这些事实,因此它无法推断在此特定查询中b.FooId 将始终与f.Id 相同。

    【讨论】:

      【解决方案3】:

      我相信您期望看到的结果是两列数据,例如:

      ID   COUNT(*)
      --   --------
       2          7
       3          2
       4         13
      

      SQL 引擎需要在 ID 列中显示一些内容,您必须明确告诉它该分组/显示项是什么。引擎不会推断您想看到的内容。

      如果您在 Boos 中没有记录而在 Foos 中进行记录时,这将特别重要。在这种情况下,引擎需要知道在零计数旁边显示哪个 ID。

      【讨论】:

        【解决方案4】:

        我想给你以下关于 group by 子句用法的建议

        标准分组和查询 示例部分中的所有 group by 和 having 查询都遵循 SQL 标准,该标准规定使用 group by、have 和 vector 聚合函数的查询使用以下准则为每个组生成一行和一个汇总值:

        选择列表中的列也必须在 group by 表达式中,或者它们必须是聚合函数的参数。

        group by 表达式只能包含选择列表中的列名。但是,仅用作选择列表中聚合函数参数的列不符合条件。

        having 表达式中的列必须是单值的——例如聚合的参数——并且它们必须在选择列表或 group by 子句中。具有选择列表聚合和 have 子句的查询必须具有 group by 子句。如果在没有选择列表聚合的查询中省略 group by,则所有未被 where 子句排除的行都被视为一个组。

        在非分组查询中,“where excludes rows”的原则似乎很简单。在分组查询中,原理扩展为“where 在 group by 之前排除行,并且从结果显示中排除行。”

        SQL 标准允许连接两个或多个表的查询使用 group by 和 having,前提是它们也遵守上述准则。在指定联接或其他复杂查询时,请使用 group by 和 have 的标准语法,直到您完全理解 Transact-SQL 扩展对这两个子句的影响。

        为帮助您避免扩展问题,Adaptive Server 为 set 命令提供了 fipsflagger 选项,该选项在查询中每次出现 Transact-SQL 扩展时都会发出非致命警告。更多信息请参见集合。

        现在在第一个场景组中按用法无效,但在第二种情况下则不是。

        谢谢 尼拉吉拉蒂

        【讨论】:

        • 选择列表中的列也必须在 group by 表达式中,或者它们必须是聚合函数的参数。,这在某一点上肯定是正确的,但是因为2003 标准列可以包含在选择列表中,而不是 group by 或聚合函数中,只要它们在功能上依赖于 group by 中包含的列。
        【解决方案5】:

        您仍然需要应用您在原始选择语句Select f.Id... 中定义的适当分组,因此您需要按此列分组。你没有超越 sql server。

        【讨论】:

        • 因为它们来自不同的表,并且不能保证会出现相同的 id。如果 b.FooId 是外键,那么可能会有所不同,但没有定义表明这一点。
        【解决方案6】:

        SQL Server 以传统方式检查您的查询比确保:

        • GROUP BY 子句中的b.FooId 与SELECT 子句中的f.Id 完全相同
        • 它们不为空
        • 连接是内部的,而不是外部的

        【讨论】:

          猜你喜欢
          • 2022-12-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-26
          • 2011-11-24
          • 2021-07-14
          • 2016-05-04
          • 1970-01-01
          相关资源
          最近更新 更多