Función de valor de tabla de varias instrucciones frente a función de valor de tabla en línea

198

Algunos ejemplos para mostrar, por si acaso:

Tabla en línea valorada

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

Tabla de declaración múltiple valorada

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

¿Hay alguna ventaja de usar un tipo (en línea o declaración múltiple) sobre el otro? ¿Hay ciertos escenarios cuando uno es mejor que el otro o las diferencias son puramente sintácticas? Me doy cuenta de que las dos consultas de ejemplo están haciendo cosas diferentes, pero ¿hay alguna razón por la que las escribiría de esa manera?

Leer sobre ellos y las ventajas / diferencias realmente no se han explicado.

AndrewC
fuente
¡También uno de los grandes beneficios de la función en línea es que puede seleccionar columnas ROWID (TIMESTAMP), mientras que no puede insertar datos TIMESTAMP en la tabla de retorno en la función de varias etapas!
Artru
3
Gracias por un excelente hilo. He aprendido mucho Sin embargo, una cosa a tener en cuenta es que ALTERAR una función que era ITV a MSTV, el generador de perfiles cree que está alterando un ITV. No importa lo que haga para obtener la sintaxis correcta desde un punto de vista MSTV, la recompilación siempre falla, generalmente alrededor de la primera instrucción después de BEGIN. La única forma de evitar esto fue DROP la función anterior y CREAR la nueva como un MSTV.
Fandango68

Respuestas:

141

Al investigar el comentario de Matt, he revisado mi declaración original. Tiene razón, habrá una diferencia en el rendimiento entre una función con valor de tabla en línea (ITVF) y una función con valor de tabla de varias instrucciones (MSTVF) incluso si ambas simplemente ejecutan una instrucción SELECT. SQL Server tratará una ITVF como algo asíVIEWya que calculará un plan de ejecución utilizando las últimas estadísticas en las tablas en cuestión. Un MSTVF es equivalente a rellenar todo el contenido de su instrucción SELECT en una variable de tabla y luego unirse a eso. Por lo tanto, el compilador no puede usar ninguna estadística de tabla en las tablas en el MSTVF. Entonces, en igualdad de condiciones (lo cual rara vez lo son), el ITVF funcionará mejor que el MSTVF. En mis pruebas, la diferencia de rendimiento en el tiempo de finalización fue insignificante, sin embargo, desde el punto de vista estadístico, fue notable.

En su caso, las dos funciones no son funcionalmente equivalentes. La función MSTV realiza una consulta adicional cada vez que se llama y, lo más importante, filtra la identificación del cliente. En una consulta grande, el optimizador no podría aprovechar otros tipos de combinaciones, ya que necesitaría llamar a la función para cada ID de cliente aprobada. Sin embargo, si reescribió su función MSTV así:

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

En una consulta, el optimizador podría llamar a esa función una vez y construir un mejor plan de ejecución, pero aún así no sería mejor que un ITVS o un a equivalente no parametrizado VIEW.

Las ITVF deben preferirse a las MSTVF cuando sea factible debido a los tipos de datos, la nulabilidad y la clasificación de las columnas de la tabla, mientras que declara esas propiedades en una función con valores de tabla de múltiples declaraciones y, lo que es más importante, obtendrá mejores planes de ejecución de la ITVF. En mi experiencia, no he encontrado muchas circunstancias en las que un ITVF fuera una mejor opción que un VIEW, pero el kilometraje puede variar.

Gracias a Matt

Adición

Desde que vi esto surgir recientemente, aquí hay un excelente análisis realizado por Wayne Sheffield que compara la diferencia de rendimiento entre las funciones Inline Table Valued y las funciones Multi-Statement.

Su publicación original del blog.

Copiar en SQL Server Central

Thomas
fuente
40
Esto simplemente no es cierto: las funciones de múltiples declaraciones son muy a menudo un gran impacto en el rendimiento porque impiden que el optimizador de consultas use estadísticas. Si tuviera $ 1 por cada vez que he visto el uso de la función de múltiples declaraciones causa una elección muy pobre del plan de ejecución (principalmente porque generalmente estima el recuento de filas devuelto como 1), tendría suficiente para comprar un automóvil pequeño.
Matt Whitfield
La mejor explicación que he encontrado está en la primera respuesta, y la publicación relacionada: stackoverflow.com/questions/4109152/… No te pierdas el documento relacionado, puedes leerlo rápidamente, y es extremadamente interesante.
JotaBe
1
¿Habrá una actualización de esta respuesta para SQL Server 2017 ?: youtube.com/watch?time_continue=2&v=szTmo6rTUjM
Ralph
29

Internamente, SQL Server trata una función con valor de tabla en línea de forma muy similar a como lo haría con una vista y trata una función con valor de tabla de varias instrucciones de forma similar a como sería un procedimiento almacenado.

Cuando se utiliza una función en línea con valores de tabla como parte de una consulta externa, el procesador de consultas expande la definición UDF y genera un plan de ejecución que accede a los objetos subyacentes, utilizando los índices de estos objetos.

Para una función con valores de tabla de varias instrucciones, se crea un plan de ejecución para la función misma y se almacena en la caché del plan de ejecución (una vez que la función se ha ejecutado por primera vez). Si se utilizan funciones con valores de tabla de varias instrucciones como parte de consultas más grandes, entonces el optimizador no sabe qué devuelve la función y, por lo tanto, hace algunas suposiciones estándar; en efecto, supone que la función devolverá una sola fila y que los retornos de Se accederá a la función utilizando un escaneo de tabla contra una tabla con una sola fila.

Cuando las funciones con valores de tabla de varias instrucciones pueden funcionar mal es cuando devuelven un gran número de filas y se unen en consultas externas. Los problemas de rendimiento se deben principalmente al hecho de que el optimizador producirá un plan suponiendo que se devuelva una sola fila, que no será necesariamente el plan más apropiado.

Como regla general, hemos descubierto que, cuando sea posible, las funciones con valores de la tabla en línea deben usarse con preferencia a las de múltiples instrucciones (cuando el UDF se usará como parte de una consulta externa) debido a estos posibles problemas de rendimiento.

Paul McLoughlin
fuente
2
Aunque puede tratar funciones con valores de tabla de varias instrucciones similares a un procedimiento almacenado, un procedimiento almacenado funcionalmente idéntico es mucho más rápido que una función con valores de tabla para grandes conjuntos de datos. Me quedo con los procesos almacenados sobre las funciones con valores de tabla de varias instrucciones.
Kekoa
66
A menos que necesite unir esos resultados en otra consulta.
Guillermo Gutiérrez
¿Por qué no usar ambos? Un proceso almacenado que devuelve el resultado de una función con valores de tabla de varias instrucciones. Lo mejor de ambos mundos.
Robino
13

Hay otra diferencia Se puede insertar, actualizar y eliminar una función en línea con valores de tabla, como una vista. Se aplican restricciones similares: no se pueden actualizar funciones utilizando agregados, no se pueden actualizar columnas calculadas, etc.

Craig Beere
fuente
3

Sus ejemplos, creo, responden muy bien la pregunta. La primera función se puede hacer como una sola selección, y es una buena razón para usar el estilo en línea. El segundo probablemente podría hacerse como una sola declaración (usando una subconsulta para obtener la fecha máxima), pero algunos codificadores pueden encontrar que es más fácil de leer o más natural hacerlo en varias declaraciones como lo ha hecho. Algunas funciones simplemente no se pueden realizar en una sola declaración, por lo que requieren la versión de varias declaraciones.

Sugiero usar el más simple (en línea) siempre que sea posible, y usar declaraciones múltiples cuando sea necesario (obviamente) o cuando la preferencia / legibilidad personal lo haga cambiar el tipeo adicional.

Rayo
fuente
Gracias por la respuesta. Entonces, básicamente, la declaración múltiple solo se debe usar realmente cuando la función es más complicada de lo que es factible hacer en una función en línea, en aras de la legibilidad. ¿Hay algún beneficio de rendimiento para la declaración múltiple?
AndrewC
No lo sé, pero no lo creo. Probablemente sea mejor dejar que el servidor sql descubra las optimizaciones que podría intentar hacer manualmente (utilizando variables, tablas temporales o lo que sea). Aunque ciertamente podría hacer algunas pruebas de rendimiento para probar / refutar esto en casos específicos.
Ray
Muchas gracias de nuevo. ¡Puedo investigar más sobre esto cuando tenga más tiempo! :)
AndrewC
0

No he probado esto, pero una función de declaración múltiple almacena en caché el conjunto de resultados. Puede haber casos en los que el optimizador está pasando demasiado para alinear la función. Por ejemplo, suponga que tiene una función que devuelve un resultado de diferentes bases de datos dependiendo de lo que pase como un "Número de compañía". Normalmente, podría crear una vista con una unión y luego filtrar por número de compañía, pero descubrí que a veces el servidor sql retira toda la unión y no es lo suficientemente inteligente como para llamar a one select. Una función de tabla puede tener lógica para elegir la fuente.

William Egge
fuente
0

Otro caso para usar una función multilínea sería evitar que el servidor SQL empuje hacia abajo la cláusula where.

Por ejemplo, tengo una tabla con los nombres de una tabla y algunos nombres de tabla están formateados como C05_2019 y C12_2018 y todas las tablas formateadas de esa manera tienen el mismo esquema. Quería fusionar todos esos datos en una tabla y analizar 05 y 12 en una columna CompNo y 2018,2019 en una columna de año. Sin embargo, hay otras tablas como ACA_StupidTable que no puedo extraer CompNo y CompYr y obtendría un error de conversión si lo intentara. Entonces, mi consulta fue en dos partes, una consulta interna que devolvió solo tablas formateadas como 'C_______', luego la consulta externa realizó una conversión de subcadena e int. es decir, Cast (Subcadena (2, 2) como int) como CompNo. Todo se ve bien, excepto que el servidor sql decidió poner mi función Cast antes de que se filtraran los resultados, por lo que obtengo un error de conversión de codificación mental. Una función de tabla de múltiples instrucciones puede evitar que eso suceda,

William Egge
fuente
0

Quizás de una manera muy condensada. ITVF (TVF en línea): más si eres una persona DB, es una especie de vista parametrizada, toma una sola st SELECT

MTVF (TVF de múltiples declaraciones): Desarrollador, crea y carga una variable de tabla.

LinchenPal
fuente
-2

si va a hacer una consulta, puede unirse a su función Valor de tabla en línea como:

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

incurrirá en una pequeña sobrecarga y funcionará bien.

Si intenta utilizar su tabla de declaración múltiple valorada en una consulta similar, tendrá problemas de rendimiento:

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

porque ejecutará la función 1 vez por cada fila devuelta, ya que el conjunto de resultados aumenta, se ejecutará más y más lento.

KM.
fuente
Ah, ¿diría que la línea es mucho mejor en términos de rendimiento?
AndrewC
1
No, ambos devuelven una tabla, lo que invalida su segundo SQL al intentar colocar una tabla en una columna.
cjk
1
@ck, he actualizado la consulta que comenté. Los parámetros de la función utilizada en la segunda función permiten que se use como una subconsulta, lo que dará como resultado un peor rendimiento.
KM.