Ist es für SQL Server zulässig, PERSISTED-Spalten mit Daten zu füllen, die nicht der Definition entsprechen?

16

Ich verfolge diese Frage nach seltsamen Werten in einer PERSISTEDberechneten Spalte. Die Antwort darauf lässt ein paar Vermutungen darüber aufkommen, wie dieses Verhalten zustande gekommen ist.

Ich frage folgendes: Ist das nicht ein völliger Fehler? Dürfen sich PERSISTEDSpalten jemals so verhalten?

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
*/

Beachten Sie, dass die Daten als "unmöglich" erscheinen, da die Werte der berechneten Spalte nicht ihrer Definition entsprechen.

Es ist bekannt, dass sich nicht deterministische Funktionen in Abfragen seltsam verhalten können, aber hier scheint dies den Vertrag von persistierten berechneten Spalten zu verletzen und sollte daher illegal sein.

Das Einfügen von Zufallszahlen mag ein ausgedachtes Szenario sein, aber was ist, wenn wir NEWID()Werte einfügen oder SYSUTCDATETIME()? Ich denke, dies ist ein relevantes Thema, das sich praktisch manifestieren könnte.

usr
quelle

Antworten:

9

Dies ist sicherlich ein Fehler. Die Tatsache, dass die col1Werte zufällig das Ergebnis eines Ausdrucks mit Zufallszahlen sind, ändert eindeutig nichts an dem Wert, für den der richtige Wert col2angenommen wird. DBCC CHECKDBGibt einen Fehler zurück, wenn dies für eine permanente Tabelle ausgeführt wird.

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

Gibt (für meinen Testlauf, der eine "unmögliche" Reihe hatte)

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

Es wird auch darüber berichtet

repair_allow_data_loss ist die Mindestreparaturstufe für die von DBCC CHECKDB gefundenen Fehler

Und wenn die Reparaturoption verwendet wird, wird die gesamte Zeile kurzerhand gelöscht, da nicht festgestellt werden kann, welche Spalte beschädigt ist.

Wenn Sie einen Debugger anhängen, NEWID()wird der zweimal pro eingefügter Zeile ausgewertet. Einmal bevor der CASEAusdruck ausgewertet wird und einmal drin.

Bildbeschreibung hier eingeben

Eine mögliche Problemumgehung könnte sein, zu verwenden

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

Was aus dem einen oder anderen Grund das Problem vermeidet und den Ausdruck nur einmal pro Zeile auswertet.

Martin Smith
quelle
2

Nach dem Kommentargespräch scheint der Konsens darin zu bestehen, dass die Antwort auf die Frage des OP darin besteht, dass dies einen Fehler darstellt (dh illegal sein sollte).

Das OP verweist auf Vladimir Baranovs Analyse zu StackOverflow, in der es heißt:

"Erstes Mal für Col1, zweites Mal für die CASE - Anweisung der persistierten Spalte.

Der Optimierer weiß es nicht oder kümmert sich in diesem Fall nicht darum, dass NEWID eine nicht deterministische Funktion ist und sie zweimal aufruft. "

Anders ausgedrückt ist zu erwarten, dass [die NEWID () in] col1 denselben Wert hat, den Sie gerade eingefügt haben, als wenn Sie die Berechnung durchführen.

Dies ist gleichbedeutend mit dem Fehler, bei dem NEWID für Col1 erstellt und dann für die persistierte Spalte erneut erstellt wird:

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

In meinen Tests führten andere nicht deterministische Funktionen wie RAND und Zeitwerte nicht zum gleichen Fehler.

Laut Martin wurde dies Microsoft gemeldet ( https://connect.microsoft.com/SQLServer/Feedback/Details/2751288 ), wo Kommentare zu dieser Seite und zur StackOverflow-Analyse (siehe unten) vorliegen.

John
quelle