为什么我会收到有关 NULL 值和聚合函数的 ANSI 警告?

Mat*_*att 5 sql-server aggregate sql-server-2008-r2

这是确切的警告消息:

Warning: Null value is eliminated by an aggregate or other SET operation.

我理解这通常意味着什么,即在应用了聚合函数的列的一行或多行中有一个空值,但我无法弄清楚为什么我的特定查询会发生这种情况或如何重写它这样我就不会收到警告。

这是查询。该表跟踪单元对事件的响应,查询的目的是找到最后一个到达的单元,该单元是第一批收到事件通知的单元。通知时间可能会有所不同,并非所有单位都会到达。

SELECT inci_no
    ,unit
    ,notif_dttm
    ,arv_dttm
    ,resp_code
    ,alm_date
FROM (
    SELECT inci_no
        ,alm_date
        ,unit
        ,notif_dttm
        ,arv_dttm
        ,resp_code
        ,row_num = row_number() OVER (PARTITION BY inci_no ORDER BY arv_dttm DESC, unit_id ASC)
    FROM inc_unit AS u
    WHERE resp_code = 'E'
        AND arv_dttm IS NOT NULL
        AND notif_dttm <= (
            SELECT min(notif_dttm)
            FROM inc_unit AS notif
            WHERE notif.inci_no = u.inci_no
                --AND notif.arv_dttm IS NOT NULL
            GROUP BY notif.inci_no
            )
    ) AS erf_partition
WHERE row_num = 1
  and alm_date >= '2016-01-01'
Run Code Online (Sandbox Code Playgroud)

当我纠正返回不正确结果的逻辑错误时,警告开始出现。这是上面的注释行。当它被删除时,我收到警告,但当它被包含时,我没有收到警告。

起初我认为它与窗口函数有关,但现在我认为它更有可能min(notif_dttm)在最里面的子查询中。我认为我从子查询中删除的行恰好过滤掉了列中带有 NULL 的notif_dttm行。从逻辑上讲,由于最后一行的日期过滤器,我不应该得到任何这样的行。

最糟糕的是,我无法在较小的数据集上重现该问题。我编写了一个简化的模式和一些示例数据来发布这个问题,但我无法重现错误,这让我觉得我并没有真正理解问题的根源。

我熟悉查询的逻辑处理顺序(https://msdn.microsoft.com/en-us/library/ms189499.aspx),但我不能完全解析子查询和窗口函数的顺序。在阅读执行计划时,我几乎不识字,但在我看来,子查询正在对整个表上的 min() 进行分组和计算,然后将其连接回外部查询(它的谓词将消除具有空值的行,从而导致子查询中的警告)。

所以我的具体问题是:这真的发生了吗?如果是这样,notif_dttm is not null在子查询中添加子句是否会修复与该min(notif_dttm)函数相关的警告,而不管查询优化器将来会想出什么计划?

(警告本身是一个问题的原因是因为这个查询是作为 SSIS 包的一部分,并且警告导致包失败。我知道我可以set ansi_warnings off完成它,但我宁愿了解问题的根源.)

我从 BIDS 的进度/输出窗口中提取了错误(这是一个旧的 2008 R2 实例);没有 SQL 代理历史记录,因为我还没有部署更新。

我试图发布执行计划 XML,但它使发布时间太长,所以这是(大部分)它的屏幕截图。

在此处输入图片说明

更新

肯尼斯的回答帮助我确定了实际导致问题的行,结果证明这只是我认为导致问题的行的一小部分。min(notif_dttm)函数中肯定包含 NULL ,但我想确保我理解原因。

就最里面的两个子查询的逻辑处理顺序来说,准确地说是将inc_unit as uinc_unit as notif连接起来,然后WHERE应用它们的每个子句,然后SELECT应用它们的每个子句?

这将解释为什么在任何一个WHERE子句中消除带有 NULL 的行(如在我的原始查询中,尽管只是作为一种副作用)会阻止 NULL 值包含在最里面的 min() 函数中。我想我假设select min()最里面的子查询会它加入它包含子查询之前被评估,即整个子查询

SELECT min(notif_dttm)
FROM inc_unit AS notif
WHERE notif.inci_no = u.inci_no
GROUP BY notif.inci_no
Run Code Online (Sandbox Code Playgroud)

将被评估,并且该WHERE部分将有效地推迟,直到结果可以与包含查询连接。这就是为什么我无法使用简化的示例数据重现错误的原因 - 我的数据中有 NULL,只是没有正确的行和正确的 NULL。:)

Ken*_*her 5

从我所看到的你完全理解这个问题。听起来像是min(notif_dttm)在给你带来悲伤。你可以通过运行这样的代码很容易地检查这一点:

SELECT *
FROM inc_unit AS notif
WHERE notif_dttm IS NULL
  AND EXISTS (SELECT 1 FROM inc_unit u
                WHERE notif.inci_no = u.inci_no
                  AND resp_code = 'E'
                  AND arv_dttm IS NOT NULL)
Run Code Online (Sandbox Code Playgroud)

如果你得到任何行,那么这可能是你的问题。

这是一些简单的示例代码,可以重现您收到的警告。

CREATE TABLE #test (nullableCol1 int)
INSERT INTO #test VALUES (NULL),(1),(2)
SELECT MIN(nullableCol1) FROM #test
Run Code Online (Sandbox Code Playgroud)

就您的问题而言,SSIS 中有一个属性可以告诉它有多少错误acceptable。它被称为 MaximumErrorCount。增加该对象及其每个容器的值。这样你就可以得到警告(这不是什么大问题)并且你的代码仍然可以正确运行。

可以在此处找到按所需信息编辑的其他顺序。

但基本上 SQL 按以下顺序处理查询:

  1. 在哪里
  2. 通过...分组
  3. 立方体 | 卷起
  4. 选择
  5. 清楚的
  6. 订购者
  7. 最佳

因此,如果我将您的子查询按处理顺序放置,它将如下所示

        FROM inc_unit AS notif                  -- Find the table
        WHERE notif.inci_no = u.inci_no         -- Restrict the rows used
            --AND notif.arv_dttm IS NOT NULL    -- 
        GROUP BY notif.inci_no                  -- Group up what's left
        SELECT min(notif_dttm)                  -- Process any aggregates
Run Code Online (Sandbox Code Playgroud)

你可以看到这是一个非常合乎逻辑的进展。GROUP BY直到我们使用WHERE子句删除适当的行,我们才能做到。然后我们不能检查MIN直到我们完成所有的组合在一起。