web-dev-qa-db-fra.com

Fonction à valeur de table multi-instruction et fonction à valeur de table en ligne

Quelques exemples à montrer, juste en cas:

valeur de la table en ligne

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

Valeur de la table d'instructions multiples

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

Y a-t-il un avantage à utiliser un type (déclaration en ligne ou multi) par rapport à l'autre? Existe-t-il certains scénarios où l’un est meilleur que l’autre ou les différences sont-elles purement syntaxiques? Je me rends compte que les deux exemples de requêtes font des choses différentes, mais y a-t-il une raison pour laquelle je les écrirais de cette façon?

La lecture à leur sujet et les avantages/différences n'ont pas vraiment été expliqués.

184
AndrewC

En recherchant le commentaire de Matt, j'ai révisé ma déclaration initiale. Il a raison, il y aura une différence de performance entre une fonction de valeur de table inline (ITVF) et une fonction de valeur de table multi-instruction (MSTVF), même s’ils exécutent simplement une instruction SELECT. SQL Server traitera un ITVF un peu comme un VIEW en ce sens qu'il calculera un plan d'exécution en utilisant les dernières statistiques sur les tables en question. Un fichier MSTVF équivaut à insérer le contenu entier de votre instruction SELECT dans une variable de table et à y adhérer. Ainsi, le compilateur ne peut utiliser aucune statistique de table sur les tables du fichier MSTVF. Ainsi, toutes choses étant égales par ailleurs (ce qui est rarement le cas), l'ITVF fonctionnera mieux que le MSTVF. Lors de mes tests, la différence de performance entre les temps de réalisation était négligeable, mais d’un point de vue statistique, elle était perceptible.

Dans votre cas, les deux fonctions ne sont pas équivalentes du point de vue fonctionnel. La fonction MSTV effectue une requête supplémentaire chaque fois qu'elle est appelée et, surtout, filtre sur l'ID client. Dans une requête volumineuse, l'optimiseur ne pourrait pas tirer parti d'autres types de jointures, car il lui faudrait appeler la fonction pour chaque ID client passé. Cependant, si vous réécrivez votre fonction MSTV comme suit:

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

Dans une requête, l'optimiseur pourrait appeler cette fonction une fois et créer un meilleur plan d'exécution, mais ce ne serait toujours pas meilleur qu'un ITVS équivalent non paramétré ou un VIEW.

Les ITVF doivent être préférés aux MSTVF lorsque cela est possible car les types de données, la nullabilité et le classement des colonnes de la table vous permettent de déclarer ces propriétés dans une fonction de table contenant plusieurs instructions et, ce qui est important, vous obtiendrez de meilleurs plans d'exécution de la part d'ITVF. D'après mon expérience, je n'ai pas trouvé beaucoup de circonstances dans lesquelles une ITVF était une meilleure option qu'une VIEW mais le kilométrage peut varier.

Merci à Matt.

Addition

Depuis que j'ai vu cela apparaître récemment, voici une excellente analyse effectuée par Wayne Sheffield qui compare la différence de performance entre les fonctions de tableau de valeurs en ligne et les fonctions d'instructions multiples.

Son article de blog original.

Copier sur SQL Server Central

134
Thomas

En interne, SQL Server traite une fonction de table inline de la même manière qu'une vue et traite une fonction de table d'instructions multiples similaire à celle d'une procédure stockée.

Lorsqu'une fonction table inline est utilisée dans le cadre d'une requête externe, le processeur de requête développe la définition UDF et génère un plan d'exécution qui accède aux objets sous-jacents, à l'aide des index de ces objets.

Pour une fonction à valeur de table à plusieurs instructions, un plan d'exécution est créé pour la fonction elle-même et stocké dans le cache du plan d'exécution (une fois que la fonction a été exécutée pour la première fois). Si des fonctions à valeurs de table multi-instructions sont utilisées dans le cadre de requêtes plus volumineuses, l'optimiseur ne sait pas ce que la fonction renvoie, et pose donc certaines hypothèses standard. En effet, il suppose que la fonction renvoie une seule ligne et que on accédera à la fonction en utilisant un balayage de table par rapport à une table avec une seule ligne.

Les fonctions valorisées dans les tables à plusieurs instructions peuvent être mal exécutées lorsqu'elles renvoient un grand nombre de lignes et sont jointes dans les requêtes externes. Les problèmes de performances sont principalement dus au fait que l’optimiseur produira un plan en supposant qu’une seule ligne est renvoyée, ce qui ne sera pas nécessairement le plan le plus approprié.

En règle générale, nous avons constaté que, dans la mesure du possible, les fonctions de table inline doivent être utilisées de préférence aux fonctions à instructions multiples (lorsque la fonction UDF sera utilisée dans le cadre d’une requête externe) en raison de ces problèmes de performances potentiels.

27
Paul McLoughlin

Il y a une autre différence. Une fonction de table en ligne peut être insérée, mise à jour et supprimée, exactement comme une vue. Des restrictions similaires s'appliquent: impossible de mettre à jour des fonctions à l'aide d'agrégats, de mettre à jour des colonnes calculées, etc.

13
Craig Beere

Je pense que vos exemples répondent très bien à la question. La première fonction peut être réalisée en une seule sélection et constitue une bonne raison d'utiliser le style en ligne. La seconde pourrait probablement être faite en une seule instruction (en utilisant une sous-requête pour obtenir la date maximale), mais certains codeurs peuvent trouver plus facile à lire ou plus naturel de le faire dans plusieurs instructions, comme vous l'avez fait. Certaines fonctions ne peuvent tout simplement pas être effectuées dans une seule instruction, et nécessitent donc la version multi-instruction.

Je suggère d'utiliser le plus simple (en ligne) dans la mesure du possible, et d'utiliser plusieurs déclarations lorsque cela est nécessaire (évidemment) ou lorsque les préférences personnelles/la lisibilité en font un type de frappe supplémentaire.

3
Ray

regardez en comparant les fonctions à valeurs de table en ligne et à instructions multiples vous pouvez trouver de bonnes descriptions et des points de repère de performance

0
hmfarimani

Un autre cas d’utilisation d’une fonction multiligne serait de contourner le serveur SQL d’enfoncer la clause where.

Par exemple, j'ai une table avec des noms de table et certains noms sont formatés comme C05_2019 et C12_2018 et toutes les tables ainsi formatées ont le même schéma. Je voulais fusionner toutes ces données dans une seule table et analyser 05 et 12 dans une colonne CompNo et 2018,2019 dans une colonne année. Cependant, il existe d'autres tables, telles que ACA_StupidTable, pour lesquelles je ne parviens pas à extraire CompNo et CompYr et obtiendraient une erreur de conversion si j'essayais. Donc, ma requête était en deux parties, une requête interne qui renvoyait uniquement des tables au format 'C_______', puis la requête externe effectuait une conversion de sous-chaîne et int. c'est-à-dire Cast (Substring (2, 2) as int) comme CompNo. Tout a l'air bien sauf que le serveur SQL a décidé de mettre ma fonction Cast avant que les résultats ne soient filtrés, ce qui entraîne une erreur de conversion. Une fonction de tableau à plusieurs instructions peut empêcher cela, car il s’agit en fait d’un "nouveau" tableau.

0
William Egge

Je n'ai pas testé cela, mais une fonction d'instructions multiples met en cache l'ensemble de résultats. Il peut y avoir des cas où il y a trop d'activités pour que l'optimiseur intègre la fonction. Par exemple, supposons que vous ayez une fonction qui renvoie le résultat de différentes bases de données en fonction de ce que vous transmettez en tant que "numéro d'entreprise". Normalement, vous pouvez créer une vue avec une union, puis filtrer par numéro d’entreprise, mais j’ai constaté que parfois, le serveur SQL récupère l’ensemble de l’union et n’est pas assez intelligent pour appeler l’unique select. Une fonction de table peut avoir une logique pour choisir la source.

0
William Egge