Законно ли для 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()? Я думаю, что это актуальная проблема, которая может практически проявиться.

USR
источник

Ответы:

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

Дает (для моего тестового прогона, который имел один «невозможный» ряд)

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()выполняется оценка дважды для каждой вставленной строки. Один раз перед тем, как CASEвыражение будет оценено и один раз внутри него.

введите описание изображения здесь

Возможным обходным путем может быть использование

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

Который по той или иной причине избегает проблемы и оценивает выражение только один раз в строке.

Мартин Смит
источник
2

В разговоре с комментариями консенсус, по-видимому, заключается в том, что ответ на вопрос ОП заключается в том, что это действительно является ошибкой (т.е. должно быть незаконным).

ОП ссылается на анализ Владимира Баранова в StackOverflow, где говорится:

"Первый раз для Col1, второй раз для оператора CASE для сохраненного столбца.

Оптимизатор не знает или не заботится в этом случае, что NEWID является недетерминированной функцией и вызывает ее дважды ".

Другими словами, следует ожидать, что [NEWID () внутри] col1 имеет то же значение, которое вы только что вставили, как при выполнении вычисления.

Это было бы синонимом того, что происходит с ошибкой, когда NEWID создается для Col1, а затем снова создается для постоянного столбца:

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

В моем тестировании другие недетерминированные функции, такие как RAND и значения времени, не привели к той же ошибке.

По словам Мартина, этот вопрос был поднят в Microsoft ( https://connect.microsoft.com/SQLServer/Feedback/Details/2751288 ), где есть комментарии к этой странице и анализ StackOverflow (ниже).

Джон
источник