Bonnes performances avec CAST dans T-SQL

12

Nous avons un générateur SQL qui émet des instructions conditionnelles SQL de manière générique pour les champs spécifiés (ce qui, pour les besoins de la discussion: nous appellerons myField).

Si myFieldest de type NVARCHAR, nous pouvons faire une comparaison dudit champ à une chaîne comme ceci: myField = 'foo'.

Cependant, cela ne fonctionne pas pour les champs de type NTEXT. Ainsi, nous devons faire la comparaison avec un casting: CAST(myField as NVARCHAR(MAX)) = 'foo'. Cela fonctionnera en fait si myFieldest de type NVARCHARou NTEXT.

Quelle est la performance de faire le casting susmentionné sur un champ qui est déjà de type NVARCHAR? Mon espoir est que SQL Server est assez intelligent pour reconnaître dynamiquement ce myFieldtype déjà NVARCHAR(en transformant efficacement le CASTno-op).

Paul White 9
la source
Une note rapide pour quiconque trouve cette question: NTEXT (et TEXT et IMAGE) sont officiellement obsolètes et devraient être supprimés dans une future version de SQL Server (bien que l'IICR ils fonctionnent toujours dans SQL1014), vous devez donc utiliser NVARCHR (MAX) (ou VARCHAR (MAX) ou VARBINARY (MAX)) à la place. Le remplacement de la colonne NTEXT par une NVARCHAR (MAX) dans cette instance supprimerait le besoin de transtypage car la comparaison peut être effectuée directement avec ce type, et il existe également d'autres gains d'efficacité potentiels ici et ailleurs. Malheureusement, vous ne pouvez pas indexer une colonne * (MAX), mais vous ne pouvez pas non plus une colonne TEXT / NTEXT.
David Spillett

Réponses:

12

Si le transtypage de la colonne a exactement le même type de données et la même longueur et que le prédicat de recherche est un littéral, il semble en effet ne pas en tenir compte ou le traiter comme un no-op et effectue une recherche d'index sur l'égalité.

Seek Keys[1]: Prefix: [tempdb].[dbo].[#test].name = Scalar Operator(N'rpc')

Si le transtypage de la colonne est du même type de données mais de plus grande longueur et que le prédicat de recherche est un littéral de chaîne, il provoque un balayage d'index. Ceci est évidemment à éviter.

Si le transtypage de la colonne est du même type de données et de la même longueur ou plus et que le prédicat de recherche est une variable locale, il ajoute un opérateur scalaire de calcul au plan d'exécution. Cela appelle GetRangeThroughConvertet génère une plage.

Cette plage est utilisée pour faire une recherche d'index et semble assez efficace

Seek Keys[1]: 
Start: [tempdb].[dbo].[#test].name > Scalar Operator([Expr1006]), 
End: [tempdb].[dbo].[#test].name < Scalar Operator([Expr1007])

Code de test

SELECT *
 INTO #test
  FROM [master].[dbo].[spt_values]

CREATE NONCLUSTERED INDEX [ixname] ON #test
(
    [name] ASC
)

DECLARE @name NVARCHAR(MAX)

SET @name = 'rpc'

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(35))= @name --Cast the same and local variable

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(MAX))=@name --Cast to longer and local variable

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(35))='rpc' --Cast the same and literal

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(MAX))='rpc' --Cast to longer and literal
Martin Smith
la source
6

En général, la fonction CASTtue les performances car elle invalide toute utilisation des recherches d'index comme le montre le dernier exemple de Martin Smith. La nvarchar(max)conversion vers ou vers une longueur différente signifie un type de données différent: le fait que tout nvarcharsoit sans importance.

En plus de cela, le type de données du côté droit de la comparaison importe également. S'il s'agit d'une variable locale ou d'un paramètre d'une longueur différente, un côté sera implicitement CASTau plus large des 2 types de données (voir la priorité des types de données ).

En gros, si vous avez un général CASTà nvarchar(max)elle bollix les choses. J'envisagerais de corriger l'utilisation de ntextavant d'ajouter un peu CASTpartout.

La conversion peut ne pas s'afficher dans le plan de requête. Voir l'article du blog de Paul White

gbn
la source
2

Juste une note, Casting comme ceci où Datecreated est datetime

 Cast (Datecreated as date) = cast(@MydatetimeValue as date)

Ne rompt pas la capacité de SQL à utiliser des index si des index existent et s'ils n'existent pas, peut entraîner la journalisation d'un index manquant.

De même, lors de la conversion de intvers tinyintou bigintvers intetc, la fonction de conversion n'empêche pas SQL d'utiliser des index SI l'optimiseur sait que l'opération de conversion ne change pas l'ordre de tri des 2 types de données comparables.

Voici un tas de tests que vous pouvez exécuter et afficher le plan réel en utilisant Adventureworks2008R2

select count(*) from Sales.SalesOrderDetail where SalesOrderID = 8 --1
select top 10 * from Sales.SalesOrderDetail where cast(SalesOrderID as tinyint) = 8  --2
select top 10 * from Sales.SalesOrderDetail where cast(SalesOrderID as bigint) = 8  --3
select top 10 SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate  as date) = '19780322' --4
select top 10 SalesOrderID from Sales.SalesOrderDetail where convert(date,ModifiedDate) = '19780322'  --5
select top 10 SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate as varchar(20)) = '1978'  --6 -- THIS WILL NOT USE INDEX
select  SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate  as date) between '19780101' and '19780109'  --7
Doran Mackay
la source
1
cast as date peut faire une recherche d'index mais a toujours des problèmes par rapport à l'expression en tant que recherche de plage sans cast.
Martin Smith