【问题标题】:SQL Server 2008 - False error for "Msg 8120"?SQL Server 2008 - “Msg 8120”的错误错误?
【发布时间】:2017-12-06 17:38:09
【问题描述】:

我正在 SQL Server 2008 中编写查询(我相信是 Express?)。我目前收到此错误:

消息 8120,第 16 级,状态 1,第 16 行
列 'AIM.dbo.AggTicket.TotDirectHrs' 在选择列表中无效,因为它不包含在聚合函数或 GROUP BY 子句中。

我正在尝试对我们的生产 WIP(在制品)进行历史分析。 我创建了一个独立的日历表(实际上位于同一服务器上名为 BAS 的单独数据库中,不会干扰运行 AIM 数据库的 ERP)。几天来,我已经被一些用于创建运行总查询/视图/表的示例所淹没,所以现在我只打算在 Crystal Reports 2016 中处理这部分。我的想法是我想返回我的日历表中每天每个订单的记录(将来会缩小到仅与 AIM 数据库中的记录匹配的日期)。我认为我需要的价值观是:

  • 记录日期(不是唯一的)
  • 订单号(每天唯一)
  • 预计工作时间
  • 截至今天的当前工作的总工作小时数(以防估计的小时数大大低于预算)
  • 在所述记录日期计入工作的直接工时总和
  • 在所述记录日期出席的员工人数的 COUNT。
  • 员工在上述记录日期参加的小时数的总和。

我使用的表格如下:

BAS 数据库:

  • dbo.DateDimension - 用于从 1987 年 1 月 1 日到 2036 年 12 月 31 日的完整日历

AIM 数据库:

  • dbo.AggAttend - 包含每个员工在给定日期的出勤时间的一条或多条记录(即每个打卡/打卡一条记录。应该等于间接 + 直接人工)
  • dbo.AggTicket - 包含一个或多个记录,每个员工的直接工期记入特定订单号
  • dbo.ModOrders - 包含每个订单的一条记录,包括预计小时数、开始日期和结束日期(我会担心稍后使用开始日期和结束日期来计算每个日期有多少可用小时数)

这是我在查询中使用的代码:

;WITH OrderTots AS
(
    SELECT
        AggTicket.OrderNo,
        SUM(AggTicket.TotDirectHrs) AS TotActHrs
    FROM
        AIM.dbo.AggTicket
    GROUP BY
        AggTicket.OrderNo
)
SELECT
    d.Date,
    t.OrderNo,
    o.EstHrs,
    OrderTots.TotActHrs,
    SUM(t.TotDirectHrs) OVER (PARTITION BY t.TicketDate) AS DaysDirectHrs,
    COUNT(a.EmplCode) AS NumEmployees,
    SUM(a.TotHrs) AS DaysAttendHrs
FROM
    BAS.dbo.DateDimension d
INNER JOIN 
    AIM.dbo.AggAttend a ON d.Date = a.TicketDate
LEFT OUTER JOIN 
    AIM.dbo.AggTicket t ON d.Date = t.TicketDate
LEFT OUTER JOIN 
    AIM.dbo.ModOrders o ON t.OrderNo = o.OrderNo
LEFT OUTER JOIN 
    OrderTots ON t.OrderNo = OrderTots.OrderNo
GROUP BY
    d.Date, t.TicketDate, t.OrderNo, o.EstHrs,
    OrderTots.TotActHrs
ORDER BY
    d.Date

当我在 SQL Server Management Studio 2017 中运行该查询时,出现上述错误。

这些是我向社区提出的问题:

  1. 此错误消息是否正确描述了我的代码中的错误?
  2. 如果是这样,为什么该错误是错误? (据我所知,所有内容都已包含在聚合函数或 GROUP BY 子句中...smh)
  3. 编写此查询以使其正常运行的更好方法是什么?

提前向大家表示感谢!

【问题讨论】:

  • 欢迎来到 Stack Overflow!我建议您编辑以在帖子附近或顶部总结您的问题。那么 TL;DR 的懒惰程序员可能会更鼓励提供帮助。不要道歉;你可能认识这里一半以上的人。
  • 感谢@allanberry,非常感谢您的建议!

标签: sql-server sql-server-2008 ssms common-table-expression


【解决方案1】:

我正在 SQL Server 2008 中编写查询(我相信是 Express?)。

SELECT @@VERSION 会让你知道你使用的是什么版本。

选择列表中的“AIM.dbo.AggTicket.TotDirectHrs”列无效 因为它既不包含在聚合函数中,也不包含在 GROUP BY 子句。

问题在于您的SUM OVER() 声明:

SUM(t.TotDirectHrs) OVER (PARTITION BY t.TicketDate) AS DaysDirectHrs

在这里,由于您使用的是OVER 子句,因此您必须将其包含在GROUP BY 中。 OVER 子句用于确定窗口函数 的行集的分区和顺序。因此,当您使用带有SUM 的聚合时,您是在窗口函数 中执行此操作。窗口函数属于一种称为“集合函数”的函数,这意味着适用于一组行的函数。 “窗口”一词用于指代该函数所处理的行集。

因此,将t.TotDirectHrs 添加到GROUP BY

GROUP BY
    d.Date, t.TicketDate, t.OrderNo, o.EstHrs,
    OrderTots.TotActHrs, t.TotDirectHrs

如果这将您的结果缩小为您不想要的分组,那么您可以将其包装在另一个 CTE 中或使用相关子查询。 可能如下:

(SELECT SUM(t2.TotDirectHrs) OVER (PARTITION BY t2.TicketDate) AS DaysDirectHrs FROM  AIM.dbo.AggTicket t2 WHERE t2.TicketDate = t.TicketDate) as DaysDirectHrs,

示例

if object_id('tempdb..#test') is not null
drop table #test

create table #test(id int identity(1,1), letter char(1))
insert into #test
values
('a'),
('b'),
('b'),
('c'),
('c'),
('c')

鉴于上面的数据集,假设我们想要获取所有行的计数。很简单吧?

select 
    TheCount = count(*) 
from 
    #test

+----------+
| TheCount |
+----------+
|        6 |
+----------+

在这里,不需要GROUP BY,因为它暗示对所有列进行分组,因为SELECT 列表中没有指定列。请记住,GROUP BY 根据一个或多个列表达式列表中的值对 SELECT 语句结果进行分组。如果 SELECT 列表中包含聚合函数,则 GROUP BY 会计算每个组的汇总值。这些被称为向量聚合。[MSDN]

现在,假设我们要计算表格中的每个字母。我们至少可以通过两种方式做到这一点。将COUNT(*) 与选择列表中的letter 列一起使用,或者将COUNT(letter) 与选择列表中的letter 列一起使用。但是,为了让我们用字母来属性计数,我们需要返回字母列。因此,我们必须在GROUP BY 中包含letter,以告诉SQL Server 要将汇总表应用于什么。

select 
    letter
    ,TheCount = count(*) 
from 
    #test
group by
    letter

+--------+----------+
| letter | TheCount |
+--------+----------+
| a      |        1 |
| b      |        2 |
| c      |        3 |
+--------+----------+

现在,如果我们想返回相同的计数,但我们还想返回所有行怎么办?这就是窗口函数的用武之地。窗口函数在这种情况下的工作方式类似于GROUP BY,它告诉SQL Server 应用聚合的行集。然后,为此窗口/分区中的每一行返回它的值。因此,它返回一个应用于每一行的列,使其与从选择列表返回的任何列或计算列一样。

select
    letter
    ,TheCountOfTheLetter = count(*) over (partition by letter)
from 
    #test

+--------+---------------------+
| letter | TheCountOfTheLetter |
+--------+---------------------+
| a      |                   1 |
| b      |                   2 |
| b      |                   2 |
| c      |                   3 |
| c      |                   3 |
| c      |                   3 |
+--------+---------------------+

现在我们来看看您想在窗口函数中使用聚合和聚合的情况。请记住,窗口函数的返回被视为与任何其他列一样,因此必须在GROUP BY 中应用。伪看起来像这样,但GROUP BY 子句中不允许使用窗口函数。

select 
    letter
    ,TheCount = count(*) 
    ,TheCountOfTheLetter = count(*) over (partition by letter)
from 
    #test
group by
    letter
    ,count(*) over (partition by letter)

--returns an error

因此,我们必须使用相关子查询或 cte 或其他方法。

select 
    t.letter
    ,TheCount = count(*) 
    ,TheCountOfTheLetter = (select distinct count(*) over (partition by letter) from #test t2 where t2.letter = t.letter)
from 
    #test t
group by
    t.letter

+--------+----------+---------------------+
| letter | TheCount | TheCountOfTheLetter |
+--------+----------+---------------------+
| a      |        1 |                   1 |
| b      |        2 |                   2 |
| c      |        3 |                   3 |
+--------+----------+---------------------+

【讨论】:

  • SELECT @@VERSION 返回“Microsoft SQL Server 2008 R2 (SP2) - 10.50.4000.0 (X64) Jun 28 2012 08:36:30 版权所有 (c) Microsoft Corporation Express Edition with Advanced Services (64 -bit) 在 Windows NT 6.2 (Build 9200: )”上,因此值得称赞。我能够得到你的答案来执行并返回我所要求的。但是,在我选择这个作为答案之前,您能否解释一下为什么该字段需要在 GROUP BY 子句中,而它甚至与数据的分区方式无关,更不用说分组了?相比之下,我发现的其他 Msg 8120 错误似乎相当明显..
  • 当您在 select 语句中有聚合时,您要返回的所有其他不属于聚合的列必须包含在 group by 子句中。否则,没有物理方法可以显示准确的结果。您的案例可能会令人困惑,因为它使用和聚合。但是,它使用一个 over 子句告诉 sql server 分区,然后在行级别应用它。请记住,在考虑 SQL Server 和大多数关系数据库时,您总是需要综合考虑
  • @Smizles 我试图在我的编辑中解释这一点。希望这会有所帮助
  • 是的,基本上。我可以理解应该隐含 group by 的想法,但请记住有 group by rollup 和 group by cube 等......
  • 谢谢@scsimon。现在我需要按汇总查找分组并按立方体分组大声笑
猜你喜欢
  • 1970-01-01
  • 2014-09-07
  • 2015-05-13
  • 2011-09-13
  • 2015-05-28
  • 2014-12-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多