Funzione con valori di tabella multiistruzione e funzione con valori di tabella incorporati


198

Alcuni esempi da mostrare, solo in caso di:

Tabella in linea valutata

CREATE FUNCTION MyNS.GetUnshippedOrders()
RETURNS TABLE
AS 
RETURN SELECT a.SaleId, a.CustomerID, b.Qty
    FROM Sales.Sales a INNER JOIN Sales.SaleDetail b
        ON a.SaleId = b.SaleId
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.ShipDate IS NULL
GO

Tabella multiistruzione valutata

CREATE FUNCTION MyNS.GetLastShipped(@CustomerID INT)
RETURNS @CustomerOrder TABLE
(SaleOrderID    INT         NOT NULL,
CustomerID      INT         NOT NULL,
OrderDate       DATETIME    NOT NULL,
OrderQty        INT         NOT NULL)
AS
BEGIN
    DECLARE @MaxDate DATETIME

    SELECT @MaxDate = MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID

    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a INNER JOIN Sales.SalesOrderHeader b
        ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.OrderDate = @MaxDate
        AND a.CustomerID = @CustomerID
    RETURN
END
GO

C'è un vantaggio nell'usare un tipo (in linea o multiistruzione) rispetto all'altro? Ci sono alcuni scenari in cui uno è migliore dell'altro o le differenze sono puramente sintattiche? Mi rendo conto che le due query di esempio stanno facendo cose diverse ma c'è un motivo per scriverle in quel modo?

Leggendo su di loro e i vantaggi / differenze non sono stati davvero spiegati.


Inoltre uno dei grandi vantaggi della funzione inline è che puoi selezionare le colonne ROWID (TIMESTAMP), mentre non puoi inserire i dati TIMESTAMP nella tabella di ritorno nella funzione multistatement!
Artru,

3
Grazie per l'eccellente discussione. Ho imparato molto Tuttavia, una cosa da tenere a mente è quando ALTERING una funzione che era ITV a MSTV, il profiler pensa che tu stia modificando un ITV. Indipendentemente da ciò che fai per ottenere la sintassi dal punto di vista MSTV, la ricompilazione fallisce sempre, di solito attorno alla prima istruzione dopo BEGIN. L'unico modo per aggirare questo problema era GOCCARE la vecchia funzione e CREARE quella nuova come MSTV.
Fandango68,

Risposte:


141

Nel ricercare il commento di Matt, ho rivisto la mia dichiarazione originale. Ha ragione, ci sarà una differenza nelle prestazioni tra una funzione con valori di tabella inline (ITVF) e una funzione con valori di tabella con più istruzioni (MSTVF) anche se entrambi eseguono semplicemente un'istruzione SELECT. SQL Server tratterà un ITVF in qualche modo come unVIEWin quanto calcolerà un piano di esecuzione utilizzando le ultime statistiche sulle tabelle in questione. Un MSTVF equivale a riempire l'intero contenuto dell'istruzione SELECT in una variabile di tabella e quindi unirsi a quello. Pertanto, il compilatore non può utilizzare le statistiche di tabella sulle tabelle in MSTVF. Quindi, a parità di condizioni (che raramente lo sono), ITVF avrà prestazioni migliori rispetto a MSTVF. Nei miei test, la differenza di prestazioni nel tempo di completamento era trascurabile, tuttavia dal punto di vista statistico, era evidente.

Nel tuo caso, le due funzioni non sono funzionalmente equivalenti. La funzione MSTV esegue una query aggiuntiva ogni volta che viene chiamata e, soprattutto, filtra sull'ID cliente. In una query di grandi dimensioni, l'ottimizzatore non sarebbe in grado di sfruttare altri tipi di join in quanto dovrebbe chiamare la funzione per ciascun cliente passato. Tuttavia, se hai riscritto la tua funzione MSTV in questo modo:

CREATE FUNCTION MyNS.GetLastShipped()
RETURNS @CustomerOrder TABLE
    (
    SaleOrderID    INT         NOT NULL,
    CustomerID      INT         NOT NULL,
    OrderDate       DATETIME    NOT NULL,
    OrderQty        INT         NOT NULL
    )
AS
BEGIN
    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a 
        INNER JOIN Sales.SalesOrderHeader b
            ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c 
            ON b.ProductID = c.ProductID
    WHERE a.OrderDate = (
                        Select Max(SH1.OrderDate)
                        FROM Sales.SalesOrderHeader As SH1
                        WHERE SH1.CustomerID = A.CustomerId
                        )
    RETURN
END
GO

In una query, l'ottimizzatore sarebbe in grado di chiamare quella funzione una volta e costruire un piano di esecuzione migliore, ma non sarebbe comunque migliore di un ITVS equivalente, non parametrizzato o a VIEW.

Gli ITVF dovrebbero essere preferiti agli MSTVF quando possibile perché i tipi di dati, il nullability e le regole di confronto dalle colonne nella tabella mentre si dichiarano tali proprietà in una funzione con valori di tabella multiistruzione e, soprattutto, si otterranno migliori piani di esecuzione dall'ITVF. Nella mia esperienza, non ho trovato molte circostanze in cui un ITVF era un'opzione migliore di una VISTA ma il chilometraggio può variare.

Grazie a Matt.

aggiunta

Da quando l'ho visto emergere di recente, ecco un'eccellente analisi fatta da Wayne Sheffield che confronta la differenza di prestazioni tra le funzioni di valori inline della tabella in linea e le funzioni multiistruzione.

Il suo post sul blog originale.

Copia su SQL Server Central


40
Questo semplicemente non è vero: le funzioni multiistruzione sono molto spesso un enorme successo in quanto impediscono all'ottimizzatore di query di utilizzare le statistiche. Se avessi $ 1 per ogni volta che ho visto l'uso della funzione multi-statement causare una scelta molto scadente del piano di esecuzione (principalmente perché stima di solito il conteggio delle righe restituite come 1), avrei abbastanza per comprare una macchina piccola.
Matt Whitfield,

La migliore spiegazione che abbia mai trovato è nella prima risposta e nel relativo post: stackoverflow.com/questions/4109152/… Non perdere il documento relativo, puoi leggerlo rapidamente ed è estremamente interessante.
Jota Be

1
Ci sarà un aggiornamento a questa risposta per SQL Server 2017 ?: youtube.com/watch?time_continue=2&v=szTmo6rTUjM
Ralph

29

Internamente, SQL Server considera una funzione con valori di tabella incorporata come se fosse una vista e tratta una funzione con valori di tabella multiistruzione simile a come sarebbe una procedura memorizzata.

Quando una funzione con valori di tabella incorporata viene utilizzata come parte di una query esterna, il processore di query espande la definizione UDF e genera un piano di esecuzione che accede agli oggetti sottostanti, utilizzando gli indici su questi oggetti.

Per una funzione con valori di tabella con più istruzioni, viene creato un piano di esecuzione per la funzione stessa e memorizzato nella cache del piano di esecuzione (una volta che la funzione è stata eseguita la prima volta). Se le funzioni con valori di tabella multiistruzione sono utilizzate come parte di query più grandi, l'ottimizzatore non sa cosa restituisce la funzione e quindi fa alcune ipotesi standard - in effetti presuppone che la funzione restituirà una singola riga e che i ritorni di si accederà alla funzione utilizzando una scansione della tabella su una tabella con una singola riga.

Il punto in cui le funzioni con valori di tabella con più istruzioni possono avere prestazioni scarse è quando restituiscono un numero elevato di righe e vengono unite nelle query esterne. I problemi di prestazioni sono principalmente dovuti al fatto che l'ottimizzatore produrrà un piano supponendo che venga restituita una singola riga, che non sarà necessariamente il piano più appropriato.

Come regola generale abbiamo scoperto che, ove possibile, le funzioni con valori di tabella incorporate dovrebbero essere utilizzate in preferenza rispetto a quelle multiistruzione (quando l'UDF verrà utilizzato come parte di una query esterna) a causa di questi potenziali problemi di prestazioni.


2
Sebbene possa trattare funzioni con valori di tabella con più istruzioni simili a una procedura memorizzata, una procedura memorizzata funzionalmente identica è molto più veloce di una funzione con valori di tabella per set di dati di grandi dimensioni. Mi attengo ai proc memorizzati sulle funzioni con valori di tabella multiistruzione.
Kekoa,

6
A meno che non sia necessario unire tali risultati in un'altra query.
Guillermo Gutiérrez,

perché non usare entrambi? Un proc memorizzato che restituisce il risultato di una funzione con valori di tabella multiistruzione. Il meglio di entrambi i mondi.
Robino,

13

C'è un'altra differenza Una funzione inline con valori di tabella può essere inserita, aggiornata ed eliminata da - proprio come una vista. Si applicano restrizioni simili: impossibile aggiornare le funzioni utilizzando gli aggregati, impossibile aggiornare le colonne calcolate e così via.


3

I tuoi esempi, credo, rispondono molto bene alla domanda. La prima funzione può essere eseguita come una singola selezione ed è una buona ragione per usare lo stile in linea. Il secondo potrebbe probabilmente essere fatto come una singola istruzione (usando una sottoquery per ottenere la data massima), ma alcuni programmatori potrebbero trovare più facile da leggere o più naturale farlo in più istruzioni come hai fatto. Alcune funzioni semplicemente non possono essere eseguite in un'unica istruzione, quindi richiedono la versione multiistruzione.

Suggerisco di utilizzare il più semplice (in linea) ogni volta che è possibile e di utilizzare più istruzioni quando necessario (ovviamente) o quando la preferenza / leggibilità personale rende necessaria la digitazione aggiuntiva.


Grazie per la risposta. Quindi, in sostanza, la multiistruzione deve essere utilizzata solo quando la funzione è più complicata di quanto sia possibile fare in una funzione in linea, per motivi di leggibilità? Ci sono dei vantaggi in termini di prestazioni nella multi-dichiarazione?
AndrewC

Non lo so, ma non la penso così. Probabilmente è meglio lasciare che sql server capisca le ottimizzazioni che potresti provare a fare manualmente (usando variabili, tabelle temporanee o altro). Anche se potresti certamente fare alcuni test delle prestazioni per dimostrare / confutare questo in casi specifici.
Ray

Mille grazie ancora. Potrei approfondire questo aspetto quando avrò più tempo! :)
AndrewC


0

Non l'ho provato, ma una funzione multiistruzione memorizza nella cache il set di risultati. Ci possono essere casi in cui c'è troppo da fare per l'ottimizzatore per incorporare la funzione. Ad esempio, supponiamo di avere una funzione che restituisce un risultato da database diversi a seconda di ciò che si passa come "Numero società". Normalmente, è possibile creare una vista con un sindacato, quindi filtrare in base al numero di società, ma ho scoperto che a volte il server sql ritira l'intero sindacato e non è abbastanza intelligente da chiamare quello selezionato. Una funzione di tabella può avere una logica per scegliere l'origine.


0

Un altro caso per utilizzare una funzione multilinea sarebbe quello di evitare che il server sql spinga la clausola where.

Ad esempio, ho una tabella con nomi di tabella e alcuni nomi di tabella sono formattati come C05_2019 e C12_2018 e tutte le tabelle formattate in questo modo hanno lo stesso schema. Volevo unire tutti quei dati in una tabella e analizzare 05 e 12 in una colonna CompNo e 2018.2019 in una colonna dell'anno. Tuttavia, ci sono altre tabelle come ACA_StupidTable che non riesco a estrarre CompNo e CompYr e otterrei un errore di conversione se provassi. Quindi, la mia query era in due parti, una query interna che restituiva solo tabelle formattate come 'C_______', quindi la query esterna faceva una sotto stringa e una conversione int. cioè Cast (Sottostringa (2, 2) come int) come CompNo. Tutto sembra a posto, tranne che il server sql ha deciso di mettere la mia funzione Cast prima che i risultati fossero filtrati e quindi ho un errore di conversione confuso. Una funzione di tabella multiistruzione può impedire che ciò accada,


0

Forse in modo molto condensato. ITVF (inline TVF): più se sei una persona DB, è una specie di vista parametrizzata, prendi una singola SELEZIONA st

MTVF (TVF multiistruzione): sviluppatore, crea e carica una variabile di tabella.


-2

se hai intenzione di fare una query puoi unirti alla tua funzione Inline Table Valued come:

SELECT
    a.*,b.*
    FROM AAAA a
        INNER JOIN MyNS.GetUnshippedOrders() b ON a.z=b.z

comporterà un piccolo sovraccarico e funzionerà bene.

se si tenta di utilizzare la tabella multiistruzione valutata in una query simile, si avranno problemi di prestazioni:

SELECT
    x.a,x.b,x.c,(SELECT OrderQty FROM MyNS.GetLastShipped(x.CustomerID)) AS Qty
    FROM xxxx   x

poiché eseguirai la funzione 1 volta per ogni riga restituita, man mano che il set di risultati aumenta, verrà eseguito sempre più lentamente.


Ah, quindi diresti che l'inline è molto meglio in termini di prestazioni?
AndrewC

1
No, entrambi restituiscono una tabella, il che rende il secondo SQL non valido mentre si tenta di inserire una tabella in una colonna.
cjk

1
@ck, ho aggiornato la query che hai commentato. i parametri della funzione utilizzata nella seconda funzione lo prestano ad essere utilizzato come sottoquery, con conseguenti peggiori prestazioni.
KM.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.