Função com valor de tabela de múltiplas instruções vs Função com valor de tabela em linha


198

Alguns exemplos para mostrar, apenas no caso:

Tabela embutida avaliada

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

Tabela com várias instruções avaliada

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

Existe uma vantagem em usar um tipo (instrução em linha ou multi) sobre o outro? Existem certos cenários em que um é melhor que o outro ou as diferenças são puramente sintáticas? Sei que as duas consultas de exemplo estão fazendo coisas diferentes, mas existe uma razão para escrevê-las dessa maneira?

Lendo sobre eles e as vantagens / diferenças não foram realmente explicadas.


Também um dos grandes benefícios da função embutida é que você pode selecionar colunas ROWID (TIMESTAMP), enquanto não é possível inserir dados TIMESTAMP na tabela de retorno na função de várias instruções!
Artru #

3
Obrigado por uma excelente discussão. Eu aprendi muito. No entanto, lembre-se de que, ao ALTERAR uma função que era ITV para MSTV, o criador de perfil pensa que você está alterando um ITV. Não importa o que você faça para obter a sintaxe correta do ponto de vista da MSTV, a recompilação sempre falha, geralmente em torno da primeira instrução após BEGIN. A única maneira de contornar isso era DROP a função antiga e CRIAR a nova como MSTV.
Fandango68

Respostas:


141

Ao pesquisar o comentário de Matt, revi minha declaração original. Ele está correto, haverá uma diferença no desempenho entre uma função com valor de tabela em linha (ITVF) e uma função com valor de tabela com várias instruções (MSTVF), mesmo que ambos simplesmente executem uma instrução SELECT. O SQL Server tratará um ITVF como umVIEWna medida em que calculará um plano de execução usando as estatísticas mais recentes nas tabelas em questão. Um MSTVF é equivalente a colocar todo o conteúdo da sua instrução SELECT em uma variável de tabela e depois ingressar nela. Portanto, o compilador não pode usar nenhuma estatística de tabela nas tabelas no MSTVF. Sendo assim, todas as coisas iguais (o que raramente são), o ITVF terá um desempenho melhor que o MSTVF. Nos meus testes, a diferença de desempenho no tempo de conclusão era insignificante, no entanto, do ponto de vista estatístico, era perceptível.

No seu caso, as duas funções não são funcionalmente equivalentes. A função MSTV faz uma consulta extra toda vez que é chamada e, mais importante, filtra a identificação do cliente. Em uma consulta grande, o otimizador não poderia tirar proveito de outros tipos de junções, pois precisaria chamar a função para cada customerId passado. No entanto, se você reescreveu sua função MSTV da seguinte maneira:

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

Em uma consulta, o otimizador poderia chamar essa função uma vez e criar um melhor plano de execução, mas ainda assim não seria melhor que um ITVS ou a equivalente, não parametrizado VIEW.

Os ITVFs devem ter preferência sobre os MSTVFs, quando possível, porque os tipos de dados, anulabilidade e intercalação das colunas na tabela, enquanto você declara essas propriedades em uma função com valor de tabela com várias instruções e, principalmente, obterá melhores planos de execução do ITVF. Na minha experiência, não encontrei muitas circunstâncias em que uma ITVF era uma opção melhor que uma VIEW, mas a milhagem pode variar.

Graças a Matt.

Adição

Desde que vi isso surgir recentemente, aqui está uma excelente análise feita por Wayne Sheffield, comparando a diferença de desempenho entre as funções de valor de tabela em linha e as funções de múltiplas declarações.

Sua postagem original no blog.

Copiar no SQL Server Central


40
Isso simplesmente não é verdade - as funções de múltiplas instruções costumam ter um enorme impacto no desempenho porque impedem o otimizador de consulta de usar estatísticas. Se eu tivesse $ 1 para cada vez que vi o uso de funções de múltiplas instruções causar uma escolha muito ruim do plano de execução (principalmente porque geralmente estima a contagem de linhas retornada como 1), eu teria o suficiente para comprar um carro pequeno.
Matt Whitfield

A melhor explicação que eu já encontrei está na primeira resposta e no post relacionado: stackoverflow.com/questions/4109152/… Não perca o documento relacionado, você pode lê-lo rapidamente e é extremamente interessante.
JotaBe 19/05

1
Haverá uma atualização para esta resposta para o SQL Server 2017 ?: youtube.com/watch?time_continue=2&v=szTmo6rTUjM
Ralph

29

Internamente, o SQL Server trata uma função com valor de tabela embutida da mesma maneira que faria em uma exibição e trata uma função com valor de tabela com várias instruções semelhante à maneira como trataria um procedimento armazenado.

Quando uma função com valor de tabela embutida é usada como parte de uma consulta externa, o processador de consultas expande a definição de UDF e gera um plano de execução que acessa os objetos subjacentes, usando os índices nesses objetos.

Para uma função com valor de tabela com várias instruções, um plano de execução é criado para a própria função e armazenado no cache do plano de execução (depois que a função é executada pela primeira vez). Se funções com valor de tabela com várias instruções são usadas como parte de consultas maiores, o otimizador não sabe o que a função retorna e, portanto, faz algumas suposições padrão - na verdade, assume que a função retornará uma única linha e que os retornos de a função será acessada usando uma varredura de tabela em uma tabela com uma única linha.

Onde as funções com valor de tabela de várias instruções podem ter um desempenho ruim é quando elas retornam um grande número de linhas e são unidas em consultas externas. Os problemas de desempenho devem-se principalmente ao fato de o otimizador produzir um plano assumindo que uma única linha seja retornada, o que não será necessariamente o plano mais apropriado.

Como regra geral, descobrimos que, sempre que possível, funções com valor de tabela em linha devem ser usadas preferencialmente a funções com várias instruções (quando o UDF será usado como parte de uma consulta externa) devido a esses possíveis problemas de desempenho.


2
Embora possa tratar funções com valor de tabela com várias instruções semelhantes a um procedimento armazenado, um procedimento armazenado funcionalmente idêntico é muito mais rápido que uma função com valor de tabela para conjuntos de dados grandes. Estou aderindo aos procs armazenados sobre as funções com valor da tabela de várias instruções.
Kekoa

6
A menos que você precise associar esses resultados em outra consulta.
Guillermo Gutiérrez

por que não usar os dois? Um processo armazenado que retorna o resultado de uma função com valor de tabela com várias instruções. Melhor dos dois mundos.
Robino

13

Há outra diferença. Uma função embutida com valor de tabela pode ser inserida, atualizada e excluída de - assim como uma exibição. Restrições semelhantes se aplicam - não é possível atualizar funções usando agregados, não é possível atualizar colunas calculadas e assim por diante.


3

Acho que seus exemplos respondem muito bem à pergunta. A primeira função pode ser executada como uma única seleção e é um bom motivo para usar o estilo embutido. O segundo provavelmente poderia ser feito como uma única instrução (usando uma subconsulta para obter a data máxima), mas alguns codificadores podem achar mais fácil ler ou mais natural fazê-lo em várias instruções, como você fez. Algumas funções simplesmente não podem ser executadas em uma instrução e, portanto, requerem a versão de múltiplas instruções.

Eu sugiro usar o mais simples (em linha) sempre que possível, e usar várias instruções quando necessário (obviamente) ou quando a preferência / legibilidade pessoal fizer com que a digitação extra seja necessária.


Obrigado pela resposta. Então, basicamente, a declaração múltipla é realmente apenas para ser usada quando a função é mais complicada do que é viável em uma função embutida, por uma questão de legibilidade? Existem benefícios de desempenho para a declaração múltipla?
AndrewC

Não sei, mas acho que não. Provavelmente, é melhor deixar o sql server descobrir as otimizações que você pode tentar fazer manualmente (usando variáveis, tabelas temporárias ou o que for). Embora você certamente possa fazer alguns testes de desempenho para provar / refutar isso em casos específicos.
Raio

Muito obrigado novamente. Eu posso olhar mais para isso quando tiver mais tempo! :)
AndrewC 31/03


0

Não testei isso, mas uma função de instrução múltipla armazena em cache o conjunto de resultados. Pode haver casos em que há muita coisa acontecendo para o otimizador alinhar a função. Por exemplo, suponha que você tenha uma função que retorne um resultado de bancos de dados diferentes, dependendo do que você passa como "Número da Empresa". Normalmente, você poderia criar uma visualização com um sindicato e depois filtrar por número da empresa, mas descobri que algumas vezes o servidor sql retira o sindicato inteiro e não é inteligente o suficiente para chamar o seleto. Uma função de tabela pode ter lógica para escolher a fonte.


0

Outro caso para usar uma função de linha múltipla seria evitar que o servidor sql pressione a cláusula where.

Por exemplo, eu tenho uma tabela com nomes de tabela e alguns nomes de tabela são formatados como C05_2019 e C12_2018 e todas as tabelas formatadas dessa maneira têm o mesmo esquema. Eu queria mesclar todos esses dados em uma tabela e analisar 05 e 12 em uma coluna CompNo e 2018,2019 em uma coluna de ano. No entanto, existem outras tabelas como ACA_StupidTable que não consigo extrair CompNo e CompYr e receberiam um erro de conversão se tentasse. Portanto, minha consulta foi dividida em duas partes, uma consulta interna que retornou apenas tabelas formatadas como 'C_______' e, em seguida, a consulta externa fez uma sub-string e uma conversão int. ie Cast (Substring (2, 2) como int) como CompNo. Tudo parece bom, exceto que o servidor sql decidiu colocar minha função Cast antes que os resultados fossem filtrados e, portanto, recebo um erro de conversão. Uma função de tabela de múltiplas instruções pode impedir que isso aconteça,


0

Talvez de uma maneira muito condensada. ITVF (TVF em linha): mais se você é uma pessoa DB, é tipo de exibição parametrizada, faça um único SELECT st

MTVF (TVF de múltiplas instruções): desenvolvedor, cria e carrega uma variável de tabela.


-2

se você for fazer uma consulta, poderá ingressar na função Valor da Tabela Inline, como:

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

incorrerá pouco em cima e funcionará bem.

se você tentar usar a tabela de instruções múltiplas avaliada em uma consulta semelhante, terá problemas de desempenho:

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

porque você executará a função 1 vez para cada linha retornada, à medida que o conjunto de resultados for maior, ele será executado cada vez mais devagar.


Ah, então você diria que o inline é muito melhor em termos de desempenho?
AndrewC

1
Não, os dois retornam uma tabela, o que torna seu segundo SQL inválido enquanto você tenta colocar uma tabela em uma coluna.
cjk

1
@ck, atualizei a consulta que você comentou. os parâmetros da função usada na segunda função emprestam-na para ser usada como uma subconsulta, o que resultará em pior desempenho.
KM.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.