带有 ROW_NUMBER 的 DELETE 的奇怪查询结果

usr*_*usr 6 sql-server sql-server-2014

在以下DELETE语句中,我尝试删除除按某些条件排序的第一行之外的所有行。(实际查询更有意义,这只是一个再现。所有的sys.objects东西都只是为了生成测试数据。)

注意过滤器r <> 1。然而,该OUTPUT子句输出带有r = 1. 怎么会这样?

USE tempdb
SET XACT_ABORT ON

BEGIN TRAN

    SELECT *
    INTO #o
    FROM sys.objects

    SELECT TOP 2 name FROM #o ORDER BY object_id --debug output

    DELETE k
    OUTPUT Deleted.name, Deleted.r
    FROM (
        SELECT k.*, ROW_NUMBER() OVER (ORDER BY object_id) r
        FROM #o k
    ) k
    WHERE r <> 1 --OUTPUT returns rows with (r = 1)

    SELECT TOP 2 name FROM #o ORDER BY object_id --debug output

ROLLBACK
Run Code Online (Sandbox Code Playgroud)

查询结果:

在此处输入图片说明

(第三个结果集是完整的 - 只有一行。)

请注意,除第一行之外的所有行都已删除。列的编号顺序r似乎与请求的不匹配。并且有一排r = 1

这是启用了跟踪标志 4199 的 SQL Server 2014 CU3。

Mar*_*ith 12

这是预期的行为,在目前

该函数在 DELETE 流上进行评估。

所以它实际上表现得像这样(伪代码)

DELETE k
OUTPUT Deleted.name, 
       ROW_NUMBER() OVER (ORDER BY Deleted.object_id) as r
FROM (
    SELECT k.*, ROW_NUMBER() OVER (ORDER BY object_id) r
    FROM #o k
) k
WHERE r <> 1 --OUTPUT returns rows with (r = 1)
Run Code Online (Sandbox Code Playgroud)

尽管这是当前定义的预期行为,但实际上并不合理,他们说

从长远来看,我们需要实际修复 OUTPUT 子句的行为以匹配 ANSI SQL 标准的行为,这将导致结果的变化。因此,我们将查看 SQL Server 未来版本的正确语义,因为可能存在依赖于当前行为的应用程序。

我没有在 SQL Server 2014 上测试过,但在 2012 年的计划如下所示。

在此处输入图片说明

在删除操作符(左侧)之后,已删除行中的列值被重新object_id排序并重新应用 row_number。

从结果来看,您的情况似乎也发生了同样的情况。(临时表的 id 为负,并且排在第sysrscols一个低正值object_id之前3)。

除了结果的可疑语义外,object_id在该计划中,第二个排序依据似乎并不是绝对必要的,因为看起来它们很可能在任何情况下都已按该顺序排序。

关于此特定情况的变通方法,将输出子句更改为OUTPUT Deleted.name, 1 + Deleted.r AS r会起作用。

对于更复杂的WHERE子句,我认为您需要通过计算 row_number 然后进行连接。例如

ALTER TABLE #o
  ADD CONSTRAINT PK PRIMARY KEY (object_id);

WITH k AS
(
 SELECT *, 
        ROW_NUMBER() OVER (ORDER BY object_id) r
FROM #o
)
MERGE k AS k1
using k AS k2
ON k1.object_id = k2.object_id
WHEN matched AND k2.r <> 1 THEN
  DELETE
OUTPUT Deleted.name,
       k2.r;
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

结果

在此处输入图片说明