2016-06-30 31 views
6

Otrzymuję nieparzyste wyniki, gdy używam NEWID() w połączeniu z kolumną trwałych obliczeń. Czy używam jakiejś funkcji nieprawidłowej?Niespójne wyniki z kolumnami NEWID() i PERSISTED obliczonymi

Nieużywanie po utworzeniu kolumny nie było trwałe, a zatem obliczanie wartości podczas ich wybierania zwróci poprawne wartości. Aktualizacja kolumny (col1) również zwróci poprawne wartości.

DECLARE @test TABLE (
    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)) 

SELECT * FROM @test 
UPDATE @test SET Col1 = Col1*1 
SELECT * FROM @test 

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

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

Myślę, że warto zauważyć, że uzyskuje się oczekiwane zachowanie z pominięciem słowa kluczowego "PERSISTED". Możesz to sprawdzić w swoim pytaniu. –

+0

@DanGuzman Dobra uwaga, zaktualizowałem pytanie. – Kristofer

+2

przekierowanie do [dba.se] (http://dba.stackexchange.com/q/142675/68127) –

Odpowiedz

4

Najwyraźniej mechanizm kwerend oblicza losową liczbę dwa razy dla każdego wiersza.

Po raz pierwszy dla Col1, po raz drugi dla oświadczenia CASE z zachowanej kolumny.

Optymalizator nie wie, lub nie obchodzi w tym przypadku, że NEWID jest niedeterministyczną funkcją i wywołuje ją dwukrotnie.

W rzeczywistości może nawet nie mieć wyboru. Czy chcesz, aby optymalizator tworzył tymczasową tabelę za kulisami, zapełniał jej Col1 wynikami wyrażenia, które generuje liczby losowe, a następnie odczytuje tę tabelę tymczasową i używa tych zapisanych wyników pośrednich do obliczenia wyniku wyrażenia CASE, a następnie wykonuje końcowy INSERT? W takim przypadku dla optymalizatora taniej jest obliczyć wyrażenie dwa razy bez zapisywania wyników pośrednich na dysk. W niektórych innych przypadkach (powiedzmy, jeśli nie masz 5, ale 5 miliardów wierszy lub dodatkowych indeksów), szacowane koszty mogą się różnić i to zachowanie będzie się zmieniać.

Nie sądzę, że można wiele z tym zrobić. Po prostu bądź świadomy tego zachowania. Zawsze jawnie zapisuj wygenerowany zestaw liczb losowych do tabeli, a następnie wykonaj dalsze obliczenia na ich podstawie.

I odtworzyć go w SQL Server 2008 i 2014. Oto plan wykonania, który dostałem w SQL Server 2008, ale nie jest to naprawdę interesujące. W 2014 r. Plan jest taki sam, z wyjątkiem braku operatora Top.

plan 2008

Constant Scan operator wysyła listę Union1009, który jest używany w Compute Scalar później. Domyślam się, że sprowadza się do szczegółów implementacji operatorów Constant Scan i/lub Compute Scalar.

Obserwowane zachowanie mówi, że newid() jest tutaj wywoływane dwa razy w jednym wierszu.

+0

Co dziwne, plan wykonania pokazuje, że losowe wartości są obliczane tylko raz. Są one obliczane jako część ciągłego skanowania. Następnie skalar obliczeniowy obliczył obliczoną kolumnę. – usr

+0

@usr, Widzę operatora 'Constant scan', a następnie' Compute Scalar' do obliczania 'CASE', który używa danych wyjściowych z' Constant scan'.Nie widzę wyraźnie w planie, że wyniki operatora "Constant scan" są przechowywane gdzieś w pamięci i nie są ponownie obliczane w razie potrzeby. W każdym przypadku obserwowane zachowanie mówi nam, że 'NEWID()' jest wywoływane dwa razy w wierszu. –

+0

Zgadzam się, że to jest problem. Po prostu nie jest to widoczne w planie. Ktoś z głęboką wiedzą wewnętrzną prawdopodobnie może wyjaśnić, w jaki sposób jest to realizowane. Zwykle skalary są obliczane leniwie i raz. Brak tabeli tymczasowej do tego potrzebnej. – usr

1

Podczas testów usunąłem funkcje niezwiązane z NEWID i pokazałem wyniki, jeśli NEWID zostały obliczone z wyprzedzeniem. Może być pomocny dla innych.

DECLARE @test TABLE (
InsertType VARCHAR(30), 
Col1 VARCHAR(5), 
Contains2 AS CASE WHEN (Col1) LIKE '%2%' THEN 1 ELSE 0 END) --depends on Col1 

INSERT INTO @test (InsertType, Col1) VALUES 
    ('Compute With Insert', LEFT(NEWID(), 5)), 
    ('Compute With Insert', LEFT(NEWID(), 5)), 
    ('Compute With Insert', LEFT(NEWID(), 5)), 
    ('Compute With Insert', LEFT(NEWID(), 5)), 
    ('Compute With Insert', LEFT(NEWID(), 5)) 

SELECT * FROM @test 

DECLARE @A VARCHAR(5) = LEFT(NEWID(), 5); 
DECLARE @B VARCHAR(5) = LEFT(NEWID(), 5); 
DECLARE @C VARCHAR(5) = LEFT(NEWID(), 5); 
DECLARE @D VARCHAR(5) = LEFT(NEWID(), 5); 
DECLARE @E VARCHAR(5) = LEFT(NEWID(), 5); 

SELECT @A, @B, @C, @D, @E; 

INSERT INTO @Test (InsertType, Col1) VALUES 
('Compute Before Insert', @A), ('Compute Before Insert', @B), ('Compute Before Insert', @C), ('Compute Before Insert', @D), ('Compute Before Insert', @E) 

SELECT * FROM @test 

InsertType     Col1  Contains2 
Compute With Insert  C5507  0 
Compute With Insert  C17D7  0 
Compute With Insert  D9087  1 
Compute With Insert  E2DB0  0 
Compute With Insert  7D1AF  1 
Compute Before Insert  31050  0 
Compute Before Insert  2954C  1 
Compute Before Insert  9E205  1 
Compute Before Insert  DDF05  0 
Compute Before Insert  ED708  0