9

Sto provando a chiamare un TVF due volte con parametri diversi nella stessa query, ma per qualche motivo, quando unisco i risultati insieme, uno dei risultati una specie di maschere l'altra. Ho ridotto il mio problema fino a questo piccolo esempio:Chiamare TVF multi-statement con parametri diversi in CTE separate che mostrano risultati errati

prendere questa linea TVF:

CREATE FUNCTION dbo.fnTestErrorInline(@Test INT) 
RETURNS TABLE 
AS 
RETURN 
(
    SELECT ID, Val 
    FROM (VALUES 
     (1, 1, 10), 
     (1, 2, 20), 
     (1, 3, 30), 
     (1, 4, 40), 
     (2, 1, 50), 
     (2, 2, 60), 
     (2, 3, 70), 
     (2, 4, 80) 
    ) t(Test, ID, Val) 
    WHERE [email protected] 
) 

e una funzione multilinea equivalente:

CREATE FUNCTION dbo.fnTestErrorMultiline(@Test INT) 
RETURNS @tbl TABLE (
    ID INT NOT NULL, 
    Val INT NOT NULL 
) 
AS 
BEGIN 
    IF @Test=1 
    INSERT INTO @tbl (ID, Val) VALUES 
    (1, 10), 
    (2, 20), 
    (3, 30), 
    (4, 40); 

    IF @Test=2 
    INSERT INTO @tbl (ID, Val) VALUES 
    (1, 50), 
    (2, 60), 
    (3, 70), 
    (4, 80); 

    RETURN 
END 

Se corro questa query:

WITH cte1 AS (
    SELECT ID, SUM(Val) AS Total 
    FROM dbo.fnTestErrorInline(1) 
    GROUP BY ID 
), cte2 AS (
    SELECT ID, SUM(Val) AS Total 
    FROM dbo.fnTestErrorInline(2) 
    GROUP BY ID 
) 
SELECT * 
FROM cte1 c1 
INNER JOIN cte2 c2 ON c1.ID=c2.ID; 

i risultati sono come previsto:

ID Total ID Total 
1 10 1 50 
2 20 2 60 
3 30 3 70 
4 40 4 80 

ma quando uso la versione più righe della funzione:

WITH cte1 AS (
    SELECT ID, SUM(Val) AS Total 
    FROM dbo.fnTestErrorMultiline(1) 
    GROUP BY ID 
), cte2 AS (
    SELECT ID, SUM(Val) AS Total 
    FROM dbo.fnTestErrorMultiline(2) 
    GROUP BY ID 
) 
SELECT * 
FROM cte1 c1 
INNER JOIN cte2 c2 ON c1.ID=c2.ID; 

i risultati non sono corretti - cte2 mostra gli stessi valori cte1:

ID Total ID Total 
1 10 1 10 
2 20 2 20 
3 30 3 30 
4 40 4 40 

Inoltre, vedo solo questo comportamento quando è presente lo GROUP BY. Senza di esso, i risultati vanno bene.

Stranamente, se posso aggiungere un'altra colonna alla seconda CTE, cambia il risultato:

WITH cte1 AS (
    SELECT ID, SUM(Val) AS Total 
    FROM dbo.fnTestErrorMultiline(1) 
    GROUP BY ID 
), cte2 AS (
    SELECT ID, SUM(Val) AS Total, SUM(Val+0) AS why 
    FROM dbo.fnTestErrorMultiline(2) 
    GROUP BY ID 
) 
SELECT * 
FROM cte1 c1 
INNER JOIN cte2 c2 ON c1.ID=c2.ID; 

rendimenti

ID Total ID Total why 
1 50 1 50 50 
2 60 2 60 60 
3 70 3 70 70 
4 80 4 80 80 

Sembra che la colonna in più ha bisogno di fare riferimento a una colonna della tabella TVF - un valore costante non cambia i risultati.

Cosa sta succedendo qui? Non dovresti chiamare un TVF multilinea più di una volta per query?

Ho provato questo su SQL Server 2008 R2 e il 2012

+0

sto passando da 3 a 'fnTestErrorMultiline' in seconda CTE ancora posso vedere i risultati. http://sqlfiddle.com/#!6/e0395/13. Il suo strano –

+0

@ MM93 è strano! Ciò sembra implicare che la funzione è in esecuzione una sola volta poiché il secondo CTE non avrebbe alcuna riga su cui partecipare. Guardando il piano delle query, mostra due volte "Funzione valori tabella", quindi non so davvero cosa sta succedendo – David

+0

Se cambio l'ordine (cioè) passando '3' a' fnTestErrorMultiline' nel primo CTE non vedo i risultati –

risposta

7

Questo è un bug noto in SQL Server in cui può erroneamente spooling i risultati per un'istanza del TVF e ripetere per l'altro (nonostante il infatti l'altro ha parametri diversi e restituisce risultati diversi).

Il bug esiste da qualche tempo ma le recenti modifiche allo stimatore di cardinalità indicano che nel 2014+ è ancora più probabile che questo problema si verifichi.

Vedere collegare articoli ..

NB: Il piano di esecuzione si presenta come di seguito.

enter image description here

Si utilizza un Common Subexpression Spool Tutti e tre bobine evidenziate sono in realtà lo stesso oggetto, nell'operatore giallo sono inserite le righe e poi vengono riprodotti nel operatori verdi.

Aggiunta

OPTION (QUERYRULEOFF GenGbApplySimple, QUERYRULEOFF BuildGbApply) 

evita il problema e dà un piano diverso con risultati corretti, ma questo non è qualcosa che avrei usato nella produzione.

enter image description here

+0

Vuoi evitare quelle opzioni di query per motivi di prestazioni o per altri motivi? Posso testare la mia query con queste opzioni vs costruire la query in modo diverso (variabile della tabella per contenere i risultati o qualcosa del genere) per vedere come si comporta, a meno che le opzioni non abbiano qualche altro effetto collaterale negativo. Questa particolare query non viene eseguita molto spesso, quindi posso tollerare un hit delle prestazioni se è tutto ciò che è. – David

+1

E wow, ho appena guardato più da vicino il primo problema di connessione che hai collegato. I nostri esempi erano quasi identici. – David

+1

@David non sono documentati, non supportati, ecc. La materializzazione in una variabile di tabella funzionerebbe. O semplicemente copia la definizione della funzione e disponi di due versioni identiche con nomi diversi in modo da evitare che anche l'auto join funzioni. –