SQL Server 使用与定义不匹配的数据填充 PERSISTED 列是否合法?

usr*_*usr 16 sql-server sql-server-2014 computed-column

我正在跟进有关计算列中奇怪值的问题PERSISTED。那里的答案对这种行为是如何产生的做出了一些猜测。

我在问以下问题:这不是一个彻头彻尾的错误吗?被PERSISTED列曾经允许这样的行为?

DECLARE @test TABLE (
    Col1 INT,
    Contains2 AS CASE WHEN 2 IN (Col1) THEN 1 ELSE 0 END PERSISTED) --depends on Col1

INSERT INTO @test (Col1) VALUES
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5))

SELECT * FROM @test --shows impossible data

UPDATE @test SET Col1 = Col1*1 --"fix" the data by rewriting it

SELECT * FROM @test --observe fixed data

/*
Col1    Contains2
2   0
2   0
0   1
4   0
3   0

Col1    Contains2
2   1
2   1
0   0
4   0
3   0
*/
Run Code Online (Sandbox Code Playgroud)

请注意,数据看起来“不可能”,因为计算列的值与其定义不对应。

众所周知,查询中的非确定性函数可能表现得很奇怪,但这似乎违反了持久计算列的约定,因此应该是非法的。

插入随机数可能是一个人为的场景,但如果我们插入NEWID()值或SYSUTCDATETIME()? 我认为这是一个可能实际表现出来的相关问题。

Mar*_*ith 9

这当然是一个错误。这些col1值恰好是涉及随机数的表达式的结果这一事实显然不会改变正确值col2应该是什么。DBCC CHECKDB如果这是针对永久表运行的,则返回错误。

create table test (
    Col1 INT,
    Contains2 AS CASE WHEN 2 IN (Col1) THEN 1 ELSE 0 END PERSISTED);

INSERT INTO test (Col1) VALUES
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5));

DBCC CHECKDB
Run Code Online (Sandbox Code Playgroud)

给出(对于我的测试运行,其中有一个“不可能”的行)

Msg 2537, Level 16, State 106, Line 17
Table error: object ID 437576597, index ID 0, partition ID 72057594041008128, alloc unit ID 72057594046251008 (type In-row data), page (1:121), row 0. The record check (valid computed column) failed. The values are 2 and 0.
DBCC results for 'test'.
There are 5 rows in 1 pages for object "test".
CHECKDB found 0 allocation errors and 1 consistency errors in table 'test' (object ID 437576597).
Run Code Online (Sandbox Code Playgroud)

它还报告说

repair_allow_data_loss 是 DBCC CHECKDB 发现的错误的最低修复级别

如果采用修复选项,则会毫不客气地删除整行,因为它无法判断哪一列已损坏。

附加调试器显示NEWID()每个插入的行正在评估两次。一次在CASE表达式被计算之前,一次在它内部。

在此处输入图片说明

一种可能的解决方法可能是使用

INSERT INTO @test
            (Col1)
SELECT ( ABS(CHECKSUM(NEWID()) % 5) )
FROM   (VALUES (1),(1),(1),(1),(1)) V(X); 
Run Code Online (Sandbox Code Playgroud)

出于某种原因,这避免了这个问题,并且每行只计算一次表达式。