Tabellenwertfunktion mit mehreren Anweisungen im Vergleich zur Inline-Tabellenwertfunktion


198

Ein paar Beispiele zu zeigen, nur für den Fall:

Inline-Tabelle bewertet

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

Multi Statement Table bewertet

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

Gibt es einen Vorteil bei der Verwendung eines Typs (Inline- oder Multi-Anweisung) gegenüber dem anderen? Gibt es bestimmte Szenarien, in denen eines besser ist als das andere, oder sind die Unterschiede rein syntaktisch? Mir ist klar, dass die beiden Beispielabfragen unterschiedliche Aufgaben haben, aber gibt es einen Grund, warum ich sie so schreiben würde?

Das Lesen über sie und die Vorteile / Unterschiede wurde nicht wirklich erklärt.


Einer der großen Vorteile der Inline-Funktion besteht auch darin, dass Sie ROWID-Spalten (TIMESTAMP) auswählen können, während Sie in der Multistatement-Funktion keine TIMESTAMP-Daten in die Rückgabetabelle einfügen können!
Artru

3
Danke für einen ausgezeichneten Thread. Ich habe viel gelernt Beachten Sie jedoch, dass der Profiler beim ÄNDERN einer ITV-Funktion in MSTV denkt, dass Sie eine ITV ändern. Unabhängig davon, was Sie tun, um die Syntax aus MSTV-Sicht richtig zu machen, schlägt die Neukompilierung immer fehl, normalerweise um die erste Anweisung nach BEGIN. Der einzige Weg, dies zu umgehen, bestand darin, die alte Funktion zu TROPFEN und die neue als MSTV zu erstellen.
Fandango68

Antworten:


141

Bei der Untersuchung von Matts Kommentar habe ich meine ursprüngliche Aussage überarbeitet. Er hat Recht, es wird einen Leistungsunterschied zwischen einer Inline-Tabellenwertfunktion (ITVF) und einer Tabellenfunktion mit mehreren Anweisungen (MSTVF) geben, selbst wenn beide einfach eine SELECT-Anweisung ausführen. SQL Server behandelt eine ITVF ähnlich wie eineVIEW, dass ein Ausführungsplan unter Verwendung der neuesten Statistiken zu den betreffenden Tabellen berechnet wird. Ein MSTVF entspricht dem Einfügen des gesamten Inhalts Ihrer SELECT-Anweisung in eine Tabellenvariable und dem anschließenden Verbinden mit dieser. Daher kann der Compiler keine Tabellenstatistiken für die Tabellen in der MSTVF verwenden. Wenn alle Dinge gleich sind (was sie selten sind), wird der ITVF eine bessere Leistung erbringen als der MSTVF. In meinen Tests war der Leistungsunterschied in der Fertigstellungszeit vernachlässigbar, jedoch aus statistischer Sicht spürbar.

In Ihrem Fall sind die beiden Funktionen funktional nicht äquivalent. Die MSTV-Funktion führt bei jedem Aufruf eine zusätzliche Abfrage durch und filtert vor allem die Kunden-ID. In einer großen Abfrage kann das Optimierungsprogramm andere Join-Typen nicht nutzen, da es die Funktion für jede übergebene Kunden-ID aufrufen muss. Wenn Sie Ihre MSTV-Funktion jedoch wie folgt neu geschrieben haben:

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 einer Abfrage könnte der Optimierer diese Funktion einmal aufrufen und einen besseren Ausführungsplan erstellen, aber er wäre immer noch nicht besser als ein äquivalentes, nicht parametrisiertes ITVS oder ein VIEW.

ITVFs sollten MSTVFs vorgezogen werden, wenn dies möglich ist, da die Datentypen, die Nullbarkeit und die Sortierung aus den Spalten in der Tabelle stammen, während Sie diese Eigenschaften in einer Tabellenfunktion mit mehreren Anweisungen deklarieren und vor allem bessere Ausführungspläne von der ITVF erhalten. Nach meiner Erfahrung habe ich nicht viele Umstände gefunden, unter denen ein ITVF eine bessere Option als ein VIEW war, aber der Kilometerstand kann variieren.

Danke an Matt.

Zusatz

Da ich dies kürzlich gesehen habe, ist hier eine hervorragende Analyse von Wayne Sheffield, die den Leistungsunterschied zwischen Inline Table Valued-Funktionen und Multi-Statement-Funktionen vergleicht.

Sein ursprünglicher Blogbeitrag.

Kopieren auf SQL Server Central


40
Dies ist einfach nicht wahr - Funktionen mit mehreren Anweisungen sind sehr oft ein großer Leistungseinbruch, da sie den Abfrageoptimierer daran hindern, Statistiken zu verwenden. Wenn ich für jedes Mal, wenn ich gesehen habe, dass die Verwendung von Funktionen mit mehreren Anweisungen zu einer sehr schlechten Auswahl des Ausführungsplans führt, 1 US-Dollar hätte (hauptsächlich, weil die Anzahl der zurückgegebenen Zeilen normalerweise auf 1 geschätzt wird), hätte ich genug, um ein kleines Auto zu kaufen.
Matt Whitfield

Die beste Erklärung, die ich je gefunden habe, ist die erste Antwort und der zugehörige Beitrag: stackoverflow.com/questions/4109152/… Verpassen Sie nicht das zugehörige Dokument, Sie können es schnell lesen und es ist äußerst interessant.
JotaBe

1
Wird es ein Update für diese Antwort für SQL Server 2017 geben?: Youtube.com/watch?time_continue=2&v=szTmo6rTUjM
Ralph

29

Intern behandelt SQL Server eine Inline-Tabellenwertfunktion ähnlich wie eine Ansicht und eine Tabellenfunktion mit mehreren Anweisungen ähnlich wie eine gespeicherte Prozedur.

Wenn eine Inline-Tabellenwertfunktion als Teil einer äußeren Abfrage verwendet wird, erweitert der Abfrageprozessor die UDF-Definition und generiert einen Ausführungsplan, der mithilfe der Indizes für diese Objekte auf die zugrunde liegenden Objekte zugreift.

Für eine Tabellenwertfunktion mit mehreren Anweisungen wird ein Ausführungsplan für die Funktion selbst erstellt und im Ausführungsplan-Cache gespeichert (sobald die Funktion zum ersten Mal ausgeführt wurde). Wenn Tabellenfunktionen mit mehreren Anweisungen als Teil größerer Abfragen verwendet werden, weiß der Optimierer nicht, was die Funktion zurückgibt, und trifft daher einige Standardannahmen. Tatsächlich wird davon ausgegangen, dass die Funktion eine einzelne Zeile zurückgibt und dass die Rückgabe von Auf die Funktion wird zugegriffen, indem ein Tabellenscan für eine Tabelle mit einer einzelnen Zeile verwendet wird.

Funktionen mit Tabellenwerten mit mehreren Anweisungen können eine schlechte Leistung erbringen, wenn sie eine große Anzahl von Zeilen zurückgeben und in äußeren Abfragen miteinander verknüpft werden. Die Leistungsprobleme sind hauptsächlich auf die Tatsache zurückzuführen, dass der Optimierer einen Plan erstellt, vorausgesetzt, dass eine einzelne Zeile zurückgegeben wird, was nicht unbedingt der am besten geeignete Plan ist.

Als allgemeine Faustregel haben wir festgestellt, dass Funktionen mit Inline-Tabellenwerten aufgrund dieser potenziellen Leistungsprobleme nach Möglichkeit gegenüber Funktionen mit mehreren Anweisungen bevorzugt werden sollten (wenn die UDF als Teil einer äußeren Abfrage verwendet wird).


2
Obwohl Funktionen mit Tabellenwerten mit mehreren Anweisungen ähnlich wie gespeicherte Prozeduren behandelt werden können, ist eine funktional identische gespeicherte Prozedur viel schneller als eine Funktion mit Tabellenwerten für große Datenmengen. Ich bleibe bei gespeicherten Prozessen über Funktionen mit Tabellenwerten mit mehreren Anweisungen.
Kekoa

6
Es sei denn, Sie müssen diese Ergebnisse in einer anderen Abfrage verknüpfen.
Guillermo Gutiérrez

warum nicht beide verwenden? Ein gespeicherter Prozess, der das Ergebnis einer Tabellenwertfunktion mit mehreren Anweisungen zurückgibt. Beste aus beiden Welten.
Robino

13

Es gibt noch einen anderen Unterschied. Eine Inline-Tabellenwertfunktion kann wie eine Ansicht in eine Funktion eingefügt, aktualisiert und aus dieser gelöscht werden. Ähnliche Einschränkungen gelten - Funktionen können nicht mithilfe von Aggregaten aktualisiert werden, berechnete Spalten können nicht aktualisiert werden usw.


3

Ich denke, Ihre Beispiele beantworten die Frage sehr gut. Die erste Funktion kann als Einzelauswahl ausgeführt werden und ist ein guter Grund, den Inline-Stil zu verwenden. Die zweite könnte wahrscheinlich als einzelne Anweisung ausgeführt werden (unter Verwendung einer Unterabfrage, um das maximale Datum zu erhalten), aber einige Codierer finden es möglicherweise einfacher zu lesen oder natürlicher, sie in mehreren Anweisungen auszuführen, wie Sie es getan haben. Einige Funktionen können einfach nicht in einer Anweisung ausgeführt werden und erfordern daher die Version mit mehreren Anweisungen.

Ich schlage vor, wann immer möglich die einfachste (Inline) zu verwenden und bei Bedarf (offensichtlich) mehrere Anweisungen zu verwenden oder wenn persönliche Vorlieben / Lesbarkeit die zusätzliche Eingabe erforderlich machen.


Danke für die Antwort. Im Grunde genommen ist die Multi-Anweisung aus Gründen der Lesbarkeit nur dann wirklich zu verwenden, wenn die Funktion komplizierter ist als dies in einer Inline-Funktion möglich ist. Gibt es überhaupt Leistungsvorteile für Multi-Statements?
AndrewC

Ich weiß es nicht, aber ich würde es nicht glauben. Es ist wahrscheinlich besser, SQL Server die Optimierungen herausfinden zu lassen, die Sie möglicherweise manuell vornehmen möchten (mithilfe von Variablen, temporären Tabellen oder was auch immer). Sie könnten jedoch sicherlich einige Leistungstests durchführen, um dies in bestimmten Fällen zu beweisen / zu widerlegen.
Ray

Vielen Dank nochmal. Ich kann das genauer untersuchen, wenn ich mehr Zeit habe! :)
AndrewC


0

Ich habe dies nicht getestet, aber eine Funktion mit mehreren Anweisungen speichert die Ergebnismenge zwischen. Es kann Fälle geben, in denen zu viel los ist, als dass der Optimierer die Funktion einbinden könnte. Angenommen, Sie haben eine Funktion, die ein Ergebnis aus verschiedenen Datenbanken zurückgibt, je nachdem, was Sie als "Firmennummer" übergeben. Normalerweise können Sie eine Ansicht mit einer Union erstellen und dann nach Firmennummer filtern. Ich habe jedoch festgestellt, dass SQL Server manchmal die gesamte Union zurückzieht und nicht intelligent genug ist, um die eine Auswahl aufzurufen. Eine Tabellenfunktion kann logisch sein, um die Quelle auszuwählen.


0

Ein weiterer Fall für die Verwendung einer mehrzeiligen Funktion besteht darin, zu verhindern, dass SQL Server die where-Klausel herunterdrückt.

Zum Beispiel habe ich eine Tabelle mit Tabellennamen und einige Tabellennamen sind wie C05_2019 und C12_2018 formatiert, und alle auf diese Weise formatierten Tabellen haben dasselbe Schema. Ich wollte all diese Daten in einer Tabelle zusammenführen und 05 und 12 in einer CompNo-Spalte und 2018,2019 in einer Jahresspalte analysieren. Es gibt jedoch andere Tabellen wie ACA_StupidTable, die ich nicht mit CompNo und CompYr extrahieren kann und bei einem Versuch einen Konvertierungsfehler erhalten würde. Meine Abfrage bestand also aus zwei Teilen, einer inneren Abfrage, die nur Tabellen zurückgab, die wie 'C_______' formatiert waren. Dann führte die äußere Abfrage eine Konvertierung von Unterzeichenfolgen und Int durch. dh Cast (Teilstring (2, 2) als int) als CompNo. Alles sieht gut aus, außer dass der SQL Server beschlossen hat, meine Cast-Funktion zu setzen, bevor die Ergebnisse gefiltert wurden, und daher ein Konvertierungsfehler auftritt. Eine Tabellenfunktion mit mehreren Anweisungen kann dies verhindern.


0

Vielleicht auf sehr komprimierte Weise. ITVF (Inline-TVF): mehr, wenn Sie DB-Person sind, ist eine Art parametrisierte Ansicht, nehmen Sie eine einzelne SELECT st

MTVF (Multi-Statement TVF): Entwickler erstellt und lädt eine Tabellenvariable.


-2

Wenn Sie eine Abfrage durchführen möchten, können Sie sich an Ihrer Inline Table Valued-Funktion wie folgt beteiligen:

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

es wird wenig Overhead verursachen und gut laufen.

Wenn Sie versuchen, die Multi-Statement-Tabelle Valued in einer ähnlichen Abfrage zu verwenden, treten Leistungsprobleme auf:

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

Da Sie die Funktion 1 Mal für jede zurückgegebene Zeile ausführen, wird die Ergebnismenge immer langsamer und langsamer ausgeführt.


Ah, Sie würden also sagen, dass die Inline in Bezug auf die Leistung viel besser ist?
AndrewC

1
Nein, beide geben eine Tabelle zurück, wodurch Ihre zweite SQL ungültig wird, wenn Sie versuchen, eine Tabelle in eine Spalte einzufügen.
CJK

1
@ck, ich habe die Abfrage aktualisiert, die Sie kommentiert haben. Die Parameter der in der zweiten Funktion verwendeten Funktion ermöglichen die Verwendung als Unterabfrage, was zu einer schlechteren Leistung führt.
KM.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.