Desempenho atingido usando CAST em T-SQL

12

Temos um gerador SQL que emite instruções condicionais SQL genericamente para os campos especificados (que, para fins de discussão: rotularemos como myField).

Se myFieldé do tipo NVARCHAR, nós podemos fazer uma comparação do referido campo contra uma string assim: myField = 'foo'.

No entanto, isso não funciona para campos do tipo NTEXT. Assim, nós temos que fazer a comparação com um elenco: CAST(myField as NVARCHAR(MAX)) = 'foo'. De fato, isso funcionará se myFieldfor do tipo NVARCHARou NTEXT.

Qual é o impacto no desempenho de fazer o elenco mencionado em um campo que é do tipo NVARCHAR? Minha esperança é que o SQL Server seja inteligente o suficiente para reconhecer dinamicamente que myFieldjá é do tipo NVARCHAR(transformando efetivamente o CASTnão-op).

Paul White 9
fonte
Uma observação rápida para qualquer pessoa que encontre essa pergunta: NTEXT (e TEXT e IMAGE) foram oficialmente descontinuados e devem ser removidos em alguma versão futura do SQL Server (embora o IIRC ainda funcione no SQL1014), portanto, você deve usar o NVARCHR (MAX) (ou VARCHAR (MAX) ou VARBINARY (MAX)). Substituir a coluna NTEXT por uma NVARCHAR (MAX), neste caso, eliminaria a necessidade de conversão, pois a comparação pode ser feita diretamente com esse tipo e existem outros ganhos de eficiência em potencial aqui e em outros lugares também. Infelizmente, você não pode indexar uma coluna * (MAX), mas também não pode indexar uma coluna TEXT / NTEXT.
David Spillett

Respostas:

12

Se o elenco da coluna tem exatamente o mesmo tipo de dados e comprimento e o predicado de busca é literal, ele parece desconsiderá-lo ou tratá-lo como não operacional e um índice procura a igualdade.

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

Se a conversão da coluna for do mesmo tipo de dados, mas com maior comprimento e o predicado de busca for uma string literal, isso causará uma varredura de índice. Obviamente, isso deve ser evitado.

Se a conversão da coluna tiver o mesmo tipo de dados e o mesmo comprimento ou maior e o predicado de busca for uma variável local, ele adicionará um operador escalar de computação ao plano de execução. Isso chama GetRangeThroughConverte gera um intervalo.

Esse intervalo é usado para fazer uma busca de índice e parece bastante eficiente

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

Código de teste

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
fonte
6

Em geral, o CASTdesempenho prejudicará porque invalida qualquer uso de pesquisas de índice, como mostra o último exemplo de Martin Smith. Converter para nvarchar(max)ou para um comprimento diferente significa um tipo de dados diferente: o fato é que tudo isso nvarcharé irrelevante.

Além disso, o tipo de dados do lado direito da comparação também é importante. Se for uma variável local ou parâmetro de comprimento diferente, um lado estará implicitamente CASTno mais amplo dos 2 tipos de dados (consulte a precedência do tipo de dados ).

Basicamente, se você tem um general CASTpara nvarchar(max)ele vai bollix as coisas. Eu consideraria corrigir o uso de ntextantes de adicionar CASTtudo.

A conversão pode não aparecer no plano de consulta. Veja o artigo do blog de Paul White

gbn
fonte
2

Apenas uma observação: transmitir como este onde Datecreated é datetime

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

Não interrompe a capacidade do SQL de usar índices, se existirem, e se não existirem, pode resultar no registro de um índice ausente.

Da mesma forma, ao converter de intpara tinyintou bigintpara intetc, a função de conversão não impede o SQL de usar índices SE o otimizador sabe que a operação de conversão não altera a ordem de classificação dos dois tipos de dados comparáveis.

Aqui estão alguns testes que você pode executar e visualizar o plano real usando o 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
fonte
1
a conversão como data pode fazer uma busca no índice, mas ainda tem problemas em comparação com a expressão como uma busca no intervalo sem uma conversão.
Martin Smith