2016-06-30 32 views
6

Ottengo risultati strani quando si utilizza NEWID() in combinazione con una colonna calcolata persistente. Sto usando qualche funzione sbagliata?Risultati incoerenti con la colonna calcolata NEWID() e PERSISTED

L'utilizzo non persistente durante la creazione della colonna e, pertanto, il calcolo dei valori al momento della selezione, restituirà valori corretti. L'aggiornamento della colonna (col1) restituirà anche i valori corretti.

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

Penso che sia interessante notare che si ottiene il comportamento previsto con la parola chiave "PERSISTED" omessa. Potresti volerlo chiamare nella tua domanda. –

+0

@DanGuzman Buon punto, aggiornata la domanda. – Kristofer

+2

cross-postato su [dba.se] (http://dba.stackexchange.com/q/142675/68127) –

risposta

4

A quanto pare, il motore di query calcola il numero casuale due volte per ogni riga.

Prima volta per Col1, seconda volta per l'istruzione CASE della colonna persistente.

L'ottimizzatore non sa, o non si cura in questo caso che NEWID è una funzione non deterministica e la chiama due volte.

In realtà, potrebbe anche non avere una scelta. Volete che l'ottimizzatore crei una tabella temporanea dietro le quinte, compili il suo Col1 con i risultati dell'espressione che genera numeri casuali, quindi rileggi quella tabella temporanea e utilizzi questi risultati intermedi salvati per calcolare il risultato dell'espressione CASE, quindi esegui il finale INSERT? In questo caso, è più economico per l'ottimizzatore calcolare l'espressione due volte senza scrivere risultati intermedi su disco. In alcuni altri casi (ad esempio, quando non si hanno 5, ma 5 miliardi di righe o indici aggiuntivi), i costi stimati potrebbero essere diversi e questo comportamento cambierebbe.

Non penso che si possa fare molto a riguardo. Basta essere consapevoli di questo comportamento. Salva sempre esplicitamente il set generato di numeri casuali in una tabella, quindi esegui ulteriori calcoli basati su di essi.

L'ho riprodotto in SQL Server 2008 e 2014. Ecco un piano di esecuzione che ho ottenuto in SQL Server 2008, ma non è davvero interessante. Nel 2014 il piano è lo stesso, tranne che non c'è l'operatore Top.

plan 2008

Constant Scan operatore emette un elenco Union1009, che viene utilizzato nella Compute Scalar seguito. Immagino, si tratta di dettagli di implementazione degli operatori Constant Scan e/o Compute Scalar.

Il comportamento osservato indica che newid() viene chiamato due volte per riga qui.

+0

Stranamente, il piano di esecuzione mostra che i valori casuali sono calcolati solo una volta. Sono calcolati come parte della scansione costante. Successivamente, uno scalare di calcolo ha calcolato la colonna calcolata. – usr

+0

@usr, vedo un operatore 'Constant scan' e poi un' Compute Scalar' per il calcolo di 'CASE' che utilizza l'output da una' Scansione costante'.Non vedo esplicitamente nel piano che i risultati dell'operatore di 'Scansione costante' sono memorizzati da qualche parte nella memoria e non ricalcolati come necessario. In ogni caso, il comportamento osservato ci dice che 'NEWID()' è chiamato due volte per riga. –

+0

Sono d'accordo che è il problema. Non è visibile nel piano. Qualcuno con una profonda conoscenza interna probabilmente può spiegare come questo è implementato. Normalmente, gli scalari sono calcolati pigramente e una volta. Non è necessario alcun tavolo temporaneo per questo. – usr

1

Durante il test, ho rimosso le funzioni non correlate a NEWID e ho mostrato i risultati se il NEWID era stato calcolato in anticipo. Potrebbe essere utile per gli altri.

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