SQL ServerがPERSISTED列を定義と一致しないデータで埋めることは合法ですか?


16

計算列の奇妙な値に関するこの質問をフォローしてい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
*/

計算列の値がその定義に対応していないため、データは「不可能」に見えることに注意してください。

クエリ内の非決定的関数が奇妙に動作する可能性があることはよく知られていますが、ここでは永続化された計算列の規約に違反するため、違法であるはずです。

私たちが挿入された場合には、乱数を挿入すると、不自然なシナリオが、何であるかもしれないNEWID()値をかSYSUTCDATETIME()?これは関連する問題であり、実際に明らかになると思います。

回答:


9

これは確かにバグです。col1値が偶然乱数を含む式の結果であるという事実は、正しい値がどうあるべきかを明らかに変更しませんcol2DBCC 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

ギブ(「不可能な」行が1つあるテスト実行用)

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).

それも報告します

repair_allow_data_lossは、DBCC CHECKDBによって検出されたエラーの最小修復レベルです

また、修復オプションを使用すると、破損している列を特定する方法がないため、行全体が不意に削除されます。

デバッガーをアタッチすると、NEWID()挿入された行ごとに2回評価されることが示されます。CASE式が評価される前に1回、その中に1回。

ここに画像の説明を入力してください

可能な回避策は、使用することです

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

何らかの理由で問題を回避し、行ごとに1回だけ式を評価します。


2

コメントの会話ごとに、OPの質問に対する答えは、これがバグを構成する(つまり違法であるべき)ということであるというコンセンサスがあるようです。

OPは、StackOverflowに関するVladimir Baranovの分析を参照しています。

「Col1の1回目、永続化された列のCASEステートメントの2回目。

Optimiserは、この場合、NEWIDが非決定的関数であることを認識せず、また気にしません。

別の言い方をすれば、col1内の[NEWID()]には、計算を行ったときと同じ値が挿入されていることが予想されます。

これは、Col1に対してNEWIDが作成され、永続化された列に対して再度作成されるという、バグの発生と同義です。

INSERT INTO @Test (Col1, Contains2) VALUES
(NEWID(), CASE WHEN (NEWID()) LIKE '%2%' THEN 1 ELSE 0 END)

私のテストでは、RANDや時間値などの他の非決定的関数では同じバグは発生しませんでした。

Martinによると、これはMicrosoft(https://connect.microsoft.com/SQLServer/Feedback/Details/2751288)に提起されており、このページに戻るコメントとStackOverflow分析(下記)があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.