Créer des vues indexéesCreate Indexed Views

CETTE RUBRIQUE S’APPLIQUE À :ouiSQL Server (à partir de la version 2008)ouiAzure SQL DatabasenonAzure SQL Data Warehouse nonParallel Data Warehouse THIS TOPIC APPLIES TO:yesSQL Server (starting with 2008)yesAzure SQL DatabasenoAzure SQL Data Warehouse noParallel Data Warehouse Cette rubrique décrit comment créer des index sur une vue. This topic describes how to create indexes on a view. Le premier index créé sur une vue doit être un index cluster unique.The first index created on a view must be a unique clustered index. Après avoir créé l'index cluster unique, vous pouvez créer davantage d'index non cluster.After the unique clustered index has been created, you can create more nonclustered indexes. La création d'un index cluster unique sur une vue améliore les performances des requêtes, car la vue est stockée dans la base de données au même titre qu'une table avec un index cluster.Creating a unique clustered index on a view improves query performance because the view is stored in the database in the same way a table with a clustered index is stored. L'optimiseur de requête peut utiliser des vues indexées pour accélérer l'exécution des requêtes.The query optimizer may use indexed views to speed up the query execution. Il n'est pas nécessaire de référencer la vue dans la requête pour que l'optimiseur envisage d'utiliser cette vue.The view does not have to be referenced in the query for the optimizer to consider that view for a substitution.

Avant de commencerBefore You Begin

Les étapes suivantes de création d'une vue indexée sont essentielles à la réussite de l'implémentation de la vue indexée :The following steps are required to create an indexed view and are critical to the successful implementation of the indexed view:

  1. Vérifiez que les options SET sont correctes pour toutes les tables existantes qui seront référencées dans la vue.Verify the SET options are correct for all existing tables that will be referenced in the view.

  2. Vérifiez que les options SET de la session sont définies correctement avant de créer des tables et la vue.Verify that the SET options for the session are set correctly before you create any tables and the view.

  3. Vérifiez que la définition de la vue est déterministe.Verify that the view definition is deterministic.

  4. Créez la vue avec l'option WITH SCHEMABINDING.Create the view by using the WITH SCHEMABINDING option.

  5. Créez l'index cluster unique sur la vue.Create the unique clustered index on the view.

Options SET requises pour les vues indexéesRequired SET Options for Indexed Views

L'évaluation de la même expression peut produire des résultats différents dans le Moteur de base de donnéesDatabase Engine si des options SET différentes sont actives lors de l'exécution de la requête.Evaluating the same expression can produce different results in the Moteur de base de donnéesDatabase Engine when different SET options are active when the query is executed. Par exemple, si l’option SET CONCAT_NULL_YIELDS_NULL est activée (ON), l’expression «abc» + NULL retourne la valeur Null.For example, after the SET option CONCAT_NULL_YIELDS_NULL is set to ON, the expression 'abc' + NULL returns the value NULL. Cependant, si l’option CONCAT_NULL_YIEDS_NULL est désactivée (OFF), la même expression retourne «abc».However, after CONCAT_NULL_YIEDS_NULL is set to OFF, the same expression produces 'abc'.

Pour pouvoir gérer correctement les vues et retourner des résultats cohérents, les vues indexées nécessitent des valeurs fixes pour plusieurs options SET.To make sure that the views can be maintained correctly and return consistent results, indexed views require fixed values for several SET options. Les options SET répertoriées dans le tableau ci-dessous doivent être définies avec les valeurs indiquées dans la colonne Valeur requise chaque fois que les conditions suivantes sont réunies :The SET options in the following table must be set to the values shown in the Required Value column whenever the following conditions occur:

  • La vue et les index suivants sur la vue sont créés.The view and subsequent indexes on the view are created.

  • Tables de base référencées dans la vue lorsque la table est créée.The base tables referenced in the view at the time the table is created.

  • Une insertion, une mise à jour ou une suppression est exécutée sur une table qui participe à la vue indexée.There is any insert, update, or delete operation performed on any table that participates in the indexed view. Cette exigence inclut des opérations telles que la copie en bloc, la réplication et les requêtes distribuées.This requirement includes operations such as bulk copy, replication, and distributed queries.

  • L'optimiseur de requête utilise la vue indexée pour générer le plan de requête.The indexed view is used by the query optimizer to produce the query plan.

    Options SETSET options Valeur requiseRequired value Valeur de serveur par défautDefault server value Valeur par défautDefault

    Valeur OLE DB et ODBCOLE DB and ODBC value
    Valeur par défautDefault

    Valeur DB-LibraryDB-Library value
    ANSI_NULLSANSI_NULLS ONON ONON ONON OFFOFF
    ANSI_PADDINGANSI_PADDING ONON ONON ONON OFFOFF
    ANSI_WARNINGS*ANSI_WARNINGS* ONON ONON ONON OFFOFF
    ARITHABORTARITHABORT ONON ONON OFFOFF OFFOFF
    CONCAT_NULL_YIELDS_NULLCONCAT_NULL_YIELDS_NULL ONON ONON ONON OFFOFF
    NUMERIC_ROUNDABORTNUMERIC_ROUNDABORT OFFOFF OFFOFF OFFOFF OFFOFF
    QUOTED_IDENTIFIERQUOTED_IDENTIFIER ONON ONON ONON OFFOFF

    *En définissant ANSI_WARNINGS sur ON, vous définissez implicitement ARITHABORT sur ON.*Setting ANSI_WARNINGS to ON implicitly sets ARITHABORT to ON.

    Si vous utilisez une connexion serveur OLE DB ou ODBC, la seule valeur qui doit être modifiée est ARITHABORT.If you are using an OLE DB or ODBC server connection, the only value that must be modified is the ARITHABORT setting. Toutes les valeurs DB-Library doivent être définies correctement au niveau du serveur à l’aide de sp_configure , ou dans l’application à l’aide de la commande SET.All DB-Library values must be set correctly either at the server level by using sp_configure or from the application by using the SET command.

Important

Il est vivement conseillé d'affecter la valeur ON à l'option utilisateur ARITHABORT sur le serveur dès la création de la première vue indexée ou du premier index sur une colonne calculée dans une base de données du serveur.We strongly recommend that you set the ARITHABORT user option to ON server-wide as soon as the first indexed view or index on a computed column is created in any database on the server.

Vues déterministesDeterministic Views

La définition d'une vue indexée doit être déterministe.The definition of an indexed view must be deterministic. Une vue est déterministe si toutes les expressions de la liste de sélection ainsi que les clauses WHERE et GROUP BY sont déterministes.A view is deterministic if all expressions in the select list, as well as the WHERE and GROUP BY clauses, are deterministic. Les expressions déterministes retournent toujours le même résultat chaque fois qu'elles sont évaluées avec un groupe de valeurs d'entrée spécifiques.Deterministic expressions always return the same result any time they are evaluated with a specific set of input values. Seules les fonctions déterministes peuvent participer à des expressions déterministes.Only deterministic functions can participate in deterministic expressions. Par exemple, la fonction DATEADD est déterministe, car elle retourne toujours le même résultat pour un groupe donné de valeurs d'arguments pour ses trois paramètres.For example, the DATEADD function is deterministic because it always returns the same result for any given set of argument values for its three parameters. La fonction GETDATE n'est pas déterministe, car elle est toujours appelée avec le même argument, mais la valeur qu'elle retourne change chaque fois qu'elle est exécutée.GETDATE is not deterministic because it is always invoked with the same argument, but the value it returns changes each time it is executed.

Pour déterminer si une colonne de vue est déterministe, utilisez la propriété IsDeterministic de la fonction COLUMNPROPERTY .To determine whether a view column is deterministic, use the IsDeterministic property of the COLUMNPROPERTY function. Pour déterminer si une colonne déterministe d'une vue avec une liaison de schéma est précise, utilisez la propriété IsPrecise de la fonction COLUMNPROPERTY.To determine if a deterministic column in a view with schema binding is precise, use the IsPrecise property of the COLUMNPROPERTY function. COLUMNPROPERTY retourne 1 pour TRUE, 0 pour FALSE et NULL pour une entrée non valide.COLUMNPROPERTY returns 1 if TRUE, 0 if FALSE, and NULL for input that is not valid. Cela signifie que la colonne n'est pas déterministe ou pas précise.This means the column is not deterministic or not precise.

Même si une expression est déterministe, si elle contient des expressions flottantes, le résultat exact dépend de l'architecture du processeur ou de la version du microcode.Even if an expression is deterministic, if it contains float expressions, the exact result may depend on the processor architecture or version of microcode. Pour garantir l'intégrité des données, ces expressions peuvent participer seulement sous forme de colonnes non clés de vues indexées.To ensure data integrity, such expressions can participate only as non-key columns of indexed views. Les expressions déterministes qui ne contiennent pas d'expressions flottantes s'appellent des expressions précises.Deterministic expressions that do not contain float expressions are called precise. Seules les expressions déterministes précises peuvent participer à des colonnes clés et dans les clauses WHERE et GROUP BY des vues indexées.Only precise deterministic expressions can participate in key columns and in WHERE or GROUP BY clauses of indexed views.

Note

Les vues indexées ne sont pas prises en charge sur les requêtes temporelles (requêtes qui utilisent la clause FOR SYSTEM_TIME ).Indexed views are not supported on top of temporal queries (queries that use FOR SYSTEM_TIME clause)

Autres conditions requisesAdditional Requirements

Outre les options SET et les conditions requises pour les fonctions déterministes, les conditions suivantes doivent être satisfaites :In addition to the SET options and deterministic function requirements, the following requirements must be met:

  • L'utilisateur qui exécute CREATE INDEX doit être le propriétaire de la vue.The user that executes CREATE INDEX must be the owner of the view.

  • Lorsque vous créez l'index, l'option IGNORE_DUP_KEY doit avoir la valeur OFF (valeur par défaut).When you create the index, the IGNORE_DUP_KEY option must be set to OFF (the default setting).

  • Les tables doivent être référencées par des noms en deux parties, schéma.nom_table , dans la définition de la vue.Tables must be referenced by two-part names, schema.tablename in the view definition.

  • Les fonctions définies par l'utilisateur référencées dans la vue doivent avoir été créées avec l'option WITH SCHEMABINDING.User-defined functions referenced in the view must be created by using the WITH SCHEMABINDING option.

  • Toutes les fonctions définies par l’utilisateur référencées dans la vue doivent être référencées par des noms en deux parties, schéma.fonction.Any user-defined functions referenced in the view must be referenced by two-part names, schema.function.

  • La propriété d'accès aux données d'une fonction définie par l'utilisateur doit avoir la valeur NO SQL, et la propriété d'accès externe doit avoir la valeur NO.The data access property of a user-defined function must be NO SQL, and external access property must be NO.

  • Les fonctions CLR (Common Language Runtime) peuvent s'afficher dans la liste SELECT de la vue mais ne peuvent pas faire partie de la définition de la clé d'index cluster.Common language runtime (CLR) functions can appear in the select list of the view, but cannot be part of the definition of the clustered index key. Ces fonctions ne peuvent pas apparaître dans la clause WHERE de la vue ou dans la clause ON d'une opération JOIN au sein de la vue.CLR functions cannot appear in the WHERE clause of the view or the ON clause of a JOIN operation in the view.

  • Les propriétés des méthodes et fonctions CLR des types CLR définis par l'utilisateur employés dans la définition de vue doivent être définies de la manière illustrée dans le tableau suivant.CLR functions and methods of CLR user-defined types used in the view definition must have the properties set as shown in the following table.

    PropriétéProperty RemarqueNote
    DETERMINISTIC = TRUEDETERMINISTIC = TRUE Doit être déclarée explicitement comme attribut de la méthode Microsoft .NET Framework.Must be declared explicitly as an attribute of the Microsoft .NET Framework method.
    PRECISE = TRUEPRECISE = TRUE Doit être déclarée explicitement comme attribut de la méthode .NET Framework.Must be declared explicitly as an attribute of the .NET Framework method.
    DATA ACCESS = NO SQLDATA ACCESS = NO SQL Déterminée en affectant à l'attribut DataAccess la valeur DataAccessKind.None et à l'attribut SystemDataAccess la valeur SystemDataAccessKind.None.Determined by setting DataAccess attribute to DataAccessKind.None and SystemDataAccess attribute to SystemDataAccessKind.None.
    EXTERNAL ACCESS = NOEXTERNAL ACCESS = NO Cette propriété a la valeur NO par défaut pour les routines CLR.This property defaults to NO for CLR routines.
  • La vue doit être créée avec l'option WITH SCHEMABINDING.The view must be created by using the WITH SCHEMABINDING option.

  • La vue doit référencer seulement des tables de base qui sont dans la même base de données que la vue.The view must reference only base tables that are in the same database as the view. La vue ne peut pas faire référence à d'autres vues.The view cannot reference other views.

  • L'instruction SELECT de la définition de la vue ne doit pas contenir les éléments Transact-SQL suivants :The SELECT statement in the view definition must not contain the following Transact-SQL elements:

    COUNTCOUNT Fonctions ROWSET (OPENDATASOURCE, OPENQUERY, OPENROWSET, AND OPENXML)ROWSET functions (OPENDATASOURCE, OPENQUERY, OPENROWSET, AND OPENXML) Jointures OUTER (LEFT, RIGHT ou FULL)OUTER joins (LEFT, RIGHT, or FULL)
    Table dérivée (définie en spécifiant une instruction SELECT dans la clause FROM)Derived table (defined by specifying a SELECT statement in the FROM clause) Jointures réflexivesSelf-joins Spécification des colonnes à l’aide de SELECT * ou de SELECT nom_table.Specifying columns by using SELECT * or SELECT *table_name.*
    DISTINCTDISTINCT STDEV, STDEVP, VAR, VARP ou AVGSTDEV, STDEVP, VAR, VARP, or AVG Expression de table commune (CTE)Common table expression (CTE)
    float\, **text, **ntext, **image, **XMLou **filestream* float\, **text, **ntext, **image, **XML, or **filestream* columns Sous-requêteSubquery Clause OVER, qui inclut des fonctions de classement ou d'agrégation de fenêtreOVER clause, which includes ranking or aggregate window functions
    Prédicats de texte intégral (CONTAIN, FREETEXT)Full-text predicates (CONTAIN, FREETEXT) Fonction SUM qui référence une expression acceptant les valeurs NULLSUM function that references a nullable expression ORDER BYORDER BY
    Fonction d'agrégation CLR définie par l'utilisateurCLR user-defined aggregate function Haut de la pageTOP Opérateurs CUBE, ROLLUP ou GROUPING SETSCUBE, ROLLUP, or GROUPING SETS operators
    MIN, MAXMIN, MAX Opérateurs UNION, EXCEPT ou INTERSECTUNION, EXCEPT, or INTERSECT operators TABLESAMPLETABLESAMPLE
    les variables de tables ;Table variables OUTER APPLY ou CROSS APPLYOUTER APPLY or CROSS APPLY PIVOT, UNPIVOTPIVOT, UNPIVOT
    Jeux de colonnes éparsesSparse column sets Fonctions Inline ou table à instructions multiplesInline or multi-statement table-valued functions OFFSETOFFSET
    CHECKSUM_AGGCHECKSUM_AGG

    \La vue indexée peut contenir des colonnes **float* , mais ces colonnes ne peuvent pas être incluses dans la clé d’index cluster.\The indexed view can contain **float* columns; however, such columns cannot be included in the clustered index key.

  • Si la clause GROUP BY est présente, la définition VIEW doit contenir COUNT_BIG(*), mais pas HAVING.If GROUP BY is present, the VIEW definition must contain COUNT_BIG(*) and must not contain HAVING. Ces restrictions de GROUP BY sont applicables seulement à la définition de la vue indexée.These GROUP BY restrictions are applicable only to the indexed view definition. Une requête peut utiliser une vue indexée dans son plan d'exécution, même si elle ne répond pas à ces restrictions de GROUP BY.A query can use an indexed view in its execution plan even if it does not satisfy these GROUP BY restrictions.

  • Si la définition de la vue contient une clause GROUP BY, la clé de l'index cluster unique peut référencer seulement les colonnes définies dans la clause GROUP BY.If the view definition contains a GROUP BY clause, the key of the unique clustered index can reference only the columns specified in the GROUP BY clause.

RecommandationsRecommendations

Si vous faites référence aux littéraux de chaîne datetime et smalldatetime au sein de vues indexées, il est recommandé de convertir explicitement le littéral en type date souhaité à l’aide d’un style de format de date déterministe.When you refer to datetime and smalldatetime string literals in indexed views, we recommend that you explicitly convert the literal to the date type you want by using a deterministic date format style. Pour obtenir la liste des styles de formats de date qui sont déterministes, consultez CAST et CONVERT (Transact-SQL).For a list of the date format styles that are deterministic, see CAST and CONVERT (Transact-SQL). Les expressions qui impliquent une conversion implicite de chaînes de caractères en datetime ou smalldatetime sont considérées comme non déterministes.Expressions that involve implicit conversion of character strings to datetime or smalldatetime are considered nondeterministic. Cela est dû au fait que les résultats dépendent des paramètres LANGUAGE et DATEFORMAT de la session serveur.This is because the results depend on the LANGUAGE and DATEFORMAT settings of the server session. Par exemple, les résultats de l'expression CONVERT (datetime, '30 listopad 1996', 113) dépendent du paramètre LANGUAGE, car la chaîne 'listopad' désigne des mois différents selon la langue.For example, the results of the expression CONVERT (datetime, '30 listopad 1996', 113) depend on the LANGUAGE setting because the string 'listopad' means different months in different languages. De même, dans l'expression DATEADD(mm,3,'2000-12-01'), SQL ServerSQL Server interprète la chaîne '2000-12-01' en fonction du paramètre DATEFORMAT.Similarly, in the expression DATEADD(mm,3,'2000-12-01'), SQL ServerSQL Server interprets the string '2000-12-01' based on the DATEFORMAT setting.

La conversion implicite de données caractères non-Unicode entre les classements est également considérée comme non déterministe.Implicit conversion of non-Unicode character data between collations is also considered nondeterministic.

ObservationsConsiderations

La valeur de l’option large_value_types_out_of_row des colonnes contenues dans une vue indexée est héritée de la valeur de la colonne correspondante dans la table de base.The setting of the large_value_types_out_of_row option of columns in an indexed view is inherited from the setting of the corresponding column in the base table. Cette valeur est définie à l’aide de sp_tableoption.This value is set by using sp_tableoption. La valeur par défaut des colonnes constituées à partir d'expressions est 0.The default setting for columns formed from expressions is 0. Cela signifie que les types de valeurs élevées sont stockés dans la ligne.This means that large value types are stored in-row.

Des vues indexées peuvent être créées sur une table partitionnée, et elles peuvent elles-mêmes être partitionnées.Indexed views can be created on a partitioned table, and can themselves be partitioned.

Pour empêcher le Moteur de base de donnéesDatabase Engine d'utiliser des vues indexées, incluez l'indicateur OPTION (EXPAND VIEWS) dans la requête.To prevent the Moteur de base de donnéesDatabase Engine from using indexed views, include the OPTION (EXPAND VIEWS) hint on the query. En outre, si des options sont définies incorrectement, l'optimiseur ne peut pas utiliser les index des vues.Also, if any of the listed options are incorrectly set, this will prevent the optimizer from using the indexes on the views. Pour plus d’informations sur l’indicateur OPTION (EXPAND VIEWS), consultez SELECT (Transact-SQL).For more information about the OPTION (EXPAND VIEWS) hint, see SELECT (Transact-SQL).

Si une vue est supprimée, tous ses index le sont également.All indexes on a view are dropped when the view is dropped. Tous les index non cluster et les caractéristiques créées automatiquement d'une vue sont supprimés lorsque son index cluster l'est.All nonclustered indexes and auto-created statistics on the view are dropped when the clustered index is dropped. Les statistiques créées par l'utilisateur sur la vue sont conservées.User-created statistics on the view are maintained. Les index non cluster peuvent toutefois être supprimés individuellement.Nonclustered indexes can be individually dropped. Lorsque l'index cluster de la vue est supprimé, le jeu de résultats stocké est aussi supprimé, et l'optimiseur traite de nouveau la vue comme une vue standard.Dropping the clustered index on the view removes the stored result set, and the optimizer returns to processing the view like a standard view.

Les index sur les tables et les vues peuvent être désactivés.Indexes on tables and views can be disabled. Lorsqu'un index cluster sur une table est désactivé, les index sur les vues associées à la table le sont également.When a clustered index on a table is disabled, indexes on views associated with the table are also disabled.

SécuritéSecurity

PermissionsPermissions

Nécessite l'autorisation CREATE VIEW dans la base de données et l'autorisation ALTER sur le schéma dans lequel la vue est créée.Requires CREATE VIEW permission in the database and ALTER permission on the schema in which the view is being created.

Utilisation de Transact-SQLUsing Transact-SQL

Pour créer une vue indexéeTo create an indexed view

  1. Dans l' Explorateur d'objets, connectez-vous à une instance de Moteur de base de donnéesDatabase Engine.In Object Explorer, connect to an instance of Moteur de base de donnéesDatabase Engine.

  2. Dans la barre d'outils standard, cliquez sur Nouvelle requête.On the Standard bar, click New Query.

  3. Copiez et collez l'exemple suivant dans la fenêtre de requête, puis cliquez sur Exécuter.Copy and paste the following example into the query window and click Execute. L'exemple suivant crée une vue et un index sur cette vue.The example creates a view and an index on that view. Deux requêtes sont incluses, lesquelles utilisent la vue indexée.Two queries are included that use the indexed view.

    USE AdventureWorks2012;  
    GO  
    --Set the options to support indexed views.  
    SET NUMERIC_ROUNDABORT OFF;  
    SET ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT,  
        QUOTED_IDENTIFIER, ANSI_NULLS ON;  
    GO  
    --Create view with schemabinding.  
    IF OBJECT_ID ('Sales.vOrders', 'view') IS NOT NULL  
    DROP VIEW Sales.vOrders ;  
    GO  
    CREATE VIEW Sales.vOrders  
    WITH SCHEMABINDING  
    AS  
        SELECT SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Revenue,  
            OrderDate, ProductID, COUNT_BIG(*) AS COUNT  
        FROM Sales.SalesOrderDetail AS od, Sales.SalesOrderHeader AS o  
        WHERE od.SalesOrderID = o.SalesOrderID  
        GROUP BY OrderDate, ProductID;  
    GO  
    --Create an index on the view.  
    CREATE UNIQUE CLUSTERED INDEX IDX_V1   
        ON Sales.vOrders (OrderDate, ProductID);  
    GO  
    --This query can use the indexed view even though the view is   
    --not specified in the FROM clause.  
    SELECT SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Rev,   
        OrderDate, ProductID  
    FROM Sales.SalesOrderDetail AS od  
        JOIN Sales.SalesOrderHeader AS o ON od.SalesOrderID=o.SalesOrderID  
            AND ProductID BETWEEN 700 and 800  
            AND OrderDate >= CONVERT(datetime,'05/01/2002',101)  
    GROUP BY OrderDate, ProductID  
    ORDER BY Rev DESC;  
    GO  
    --This query can use the above indexed view.  
    SELECT  OrderDate, SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Rev  
    FROM Sales.SalesOrderDetail AS od  
        JOIN Sales.SalesOrderHeader AS o ON od.SalesOrderID=o.SalesOrderID  
            AND DATEPART(mm,OrderDate)= 3  
            AND DATEPART(yy,OrderDate) = 2002  
    GROUP BY OrderDate  
    ORDER BY OrderDate ASC;  
    GO  
    

    Pour plus d’informations, consultez CREATE VIEW (Transact-SQL).For more information, see CREATE VIEW (Transact-SQL).

Voir aussiSee Also

CREATE INDEX (Transact-SQL) CREATE INDEX (Transact-SQL)
SET ANSI_NULLS (Transact-SQL) SET ANSI_NULLS (Transact-SQL)
SET ANSI_PADDING (Transact-SQL) SET ANSI_PADDING (Transact-SQL)
SET ANSI_WARNINGS (Transact-SQL) SET ANSI_WARNINGS (Transact-SQL)
SET ARITHABORT (Transact-SQL) SET ARITHABORT (Transact-SQL)
SET CONCAT_NULL_YIELDS_NULL (Transact-SQL) SET CONCAT_NULL_YIELDS_NULL (Transact-SQL)
SET NUMERIC_ROUNDABORT (Transact-SQL) SET NUMERIC_ROUNDABORT (Transact-SQL)
SET QUOTED_IDENTIFIER (Transact-SQL)SET QUOTED_IDENTIFIER (Transact-SQL)