Estoy tratando de ajustar una consulta donde se llama a la misma función con valores de tabla (TVF) en 20 columnas.
Lo primero que hice fue convertir la función escalar en una función en línea con valores de tabla.
¿Está utilizando CROSS APPLY
la mejor forma de ejecutar la misma función en varias columnas en una consulta?
Un ejemplo simplista:
SELECT Col1 = A.val
,Col2 = B.val
,Col3 = C.val
--do the same for other 17 columns
,Col21
,Col22
,Col23
FROM t
CROSS APPLY
dbo.function1(Col1) A
CROSS APPLY
dbo.function1(Col2) B
CROSS APPLY
dbo.function1(Col3) C
--do the same for other 17 columns
¿Hay mejores alternativas?
Se puede invocar la misma función en múltiples consultas contra X número de columnas.
Aquí está la función:
CREATE FUNCTION dbo.ConvertAmountVerified_TVF
(
@amt VARCHAR(60)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH cteLastChar
AS(
SELECT LastChar = RIGHT(RTRIM(@amt), 1)
)
SELECT
AmountVerified = CAST(RET.Y AS NUMERIC(18,2))
FROM (SELECT 1 t) t
OUTER APPLY (
SELECT N =
CAST(
CASE
WHEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0) >0
THEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0)-1
WHEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, 'JKLMNOPQR', 0) >0
THEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, 'JKLMNOPQR', 0)-1
WHEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, 'pqrstuvwxy', 0) >0
THEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, 'pqrstuvwxy', 0)-1
ELSE
NULL
END
AS VARCHAR(1))
FROM
cteLastChar L
) NUM
OUTER APPLY (
SELECT N =
CASE
WHEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0) >0
THEN 0
WHEN CHARINDEX(L.LastChar COLLATE Latin1_General_CS_AS, 'JKLMNOPQRpqrstuvwxy', 0) >0
THEN 1
ELSE 0
END
FROM cteLastChar L
) NEG
OUTER APPLY(
SELECT Amt= CASE
WHEN NUM.N IS NULL
THEN @amt
ELSE
SUBSTRING(RTRIM(@amt),1, LEN(@amt) - 1) + Num.N
END
) TP
OUTER APPLY(
SELECT Y = CASE
WHEN NEG.N = 0
THEN (CAST(TP.Amt AS NUMERIC) / 100)
WHEN NEG.N = 1
THEN (CAST (TP.Amt AS NUMERIC) /100) * -1
END
) RET
) ;
GO
Aquí está la versión de la función escalar que heredé, si alguien está interesado:
CREATE FUNCTION dbo.ConvertAmountVerified
(
@amt VARCHAR(50)
)
RETURNS NUMERIC (18,3)
AS
BEGIN
-- Declare the return variable here
DECLARE @Amount NUMERIC(18, 3);
DECLARE @TempAmount VARCHAR (50);
DECLARE @Num VARCHAR(1);
DECLARE @LastChar VARCHAR(1);
DECLARE @Negative BIT ;
-- Get Last Character
SELECT @LastChar = RIGHT(RTRIM(@amt), 1) ;
SELECT @Num = CASE @LastChar collate latin1_general_cs_as
WHEN '{' THEN '0'
WHEN 'A' THEN '1'
WHEN 'B' THEN '2'
WHEN 'C' THEN '3'
WHEN 'D' THEN '4'
WHEN 'E' THEN '5'
WHEN 'F' THEN '6'
WHEN 'G' THEN '7'
WHEN 'H' THEN '8'
WHEN 'I' THEN '9'
WHEN '}' THEN '0'
WHEN 'J' THEN '1'
WHEN 'K' THEN '2'
WHEN 'L' THEN '3'
WHEN 'M' THEN '4'
WHEN 'N' THEN '5'
WHEN 'O' THEN '6'
WHEN 'P' THEN '7'
WHEN 'Q' THEN '8'
WHEN 'R' THEN '9'
---ASCII
WHEN 'p' Then '0'
WHEN 'q' Then '1'
WHEN 'r' Then '2'
WHEN 's' Then '3'
WHEN 't' Then '4'
WHEN 'u' Then '5'
WHEN 'v' Then '6'
WHEN 'w' Then '7'
WHEN 'x' Then '8'
WHEN 'y' Then '9'
ELSE ''
END
SELECT @Negative = CASE @LastChar collate latin1_general_cs_as
WHEN '{' THEN 0
WHEN 'A' THEN 0
WHEN 'B' THEN 0
WHEN 'C' THEN 0
WHEN 'D' THEN 0
WHEN 'E' THEN 0
WHEN 'F' THEN 0
WHEN 'G' THEN 0
WHEN 'H' THEN 0
WHEN 'I' THEN 0
WHEN '}' THEN 1
WHEN 'J' THEN 1
WHEN 'K' THEN 1
WHEN 'L' THEN 1
WHEN 'M' THEN 1
WHEN 'N' THEN 1
WHEN 'O' THEN 1
WHEN 'P' THEN 1
WHEN 'Q' THEN 1
WHEN 'R' THEN 1
---ASCII
WHEN 'p' Then '1'
WHEN 'q' Then '1'
WHEN 'r' Then '1'
WHEN 's' Then '1'
WHEN 't' Then '1'
WHEN 'u' Then '1'
WHEN 'v' Then '1'
WHEN 'w' Then '1'
WHEN 'x' Then '1'
WHEN 'y' Then '1'
ELSE 0
END
-- Add the T-SQL statements to compute the return value here
if (@Num ='')
begin
SELECT @TempAmount=@amt;
end
else
begin
SELECT @TempAmount = SUBSTRING(RTRIM(@amt),1, LEN(@amt) - 1) + @Num;
end
SELECT @Amount = CASE @Negative
WHEN 0 THEN (CAST(@TempAmount AS NUMERIC) / 100)
WHEN 1 THEN (CAST (@TempAmount AS NUMERIC) /100) * -1
END ;
-- Return the result of the function
RETURN @Amount
END
Datos de prueba de muestra:
SELECT dbo.ConvertAmountVerified('00064170') -- 641.700
SELECT * FROM dbo.ConvertAmountVerified_TVF('00064170') -- 641.700
SELECT dbo.ConvertAmountVerified('00057600A') -- 5760.010
SELECT * FROM dbo.ConvertAmountVerified_TVF('00057600A') -- 5760.010
SELECT dbo.ConvertAmountVerified('00059224y') -- -5922.490
SELECT * FROM dbo.ConvertAmountVerified_TVF('00059224y') -- -5922.490
CROSS APPLY
s).Comenzaré arrojando algunos datos de prueba en una tabla. No tengo idea de cómo se ven sus datos reales, así que solo utilicé enteros secuenciales:
Seleccionar todas las filas con conjuntos de resultados desactivados proporciona una línea base:
Si una consulta similar con la llamada a la función lleva más tiempo, entonces tenemos una estimación aproximada de la sobrecarga de la función. Esto es lo que obtengo al llamar a su TVF tal como está:
Por lo tanto, la función necesita aproximadamente 40 segundos de tiempo de CPU para 6.5 millones de filas. Multiplique eso por 20 y son 800 segundos de tiempo de CPU. Noté dos cosas en su código de función:
Uso innecesario de
OUTER APPLY
.CROSS APPLY
le dará los mismos resultados, y para esta consulta evitará un montón de uniones innecesarias. Eso puede ahorrar un poco de tiempo. Depende principalmente de si la consulta completa va paralela. No sé nada sobre sus datos o consulta, así que solo estoy probando conMAXDOP 1
. En ese caso estoy mejor conCROSS APPLY
.Hay muchas
CHARINDEX
llamadas cuando solo busca un carácter en una pequeña lista de valores coincidentes. Puede usar laASCII()
función y un poco de matemática para evitar todas las comparaciones de cadenas.Aquí hay una forma diferente de escribir la función:
En mi máquina, la nueva función es significativamente más rápida:
Probablemente también hay algunas optimizaciones adicionales disponibles, pero mi instinto dice que no serán demasiado. Según lo que está haciendo su código, no puedo ver cómo vería una mejora adicional al llamar de alguna manera a su función de una manera diferente. Es solo un montón de operaciones de cadena. Llamar a la función 20 veces por fila será más lento que solo una vez, pero la definición ya está en línea.
fuente
Intenta usar lo siguiente
en lugar
Una variante con el uso de una mesa auxiliar
Una consulta de prueba
Como variante, también puede intentar usar una tabla auxiliar temporal
#LastCharLink
o una tabla variable@LastCharLink
(pero puede ser más lenta que una tabla real o temporal)Y úsalo como
o
Entonces también puede crear una función en línea simple y poner todas las conversiones
Y luego use esta función como
fuente
Prefix
lugar deDivider
.Alternativamente, puede crear una tabla permanente. Esta es una creación única.
Entonces TVF
Del ejemplo de @Joe,
- Tarda 30 s
Si es posible, Amount también se puede formatear a nivel de IU. Esta es la mejor opcion. De lo contrario, también puede compartir su consulta original. O si es posible, mantenga el valor formateado en la tabla también.
fuente