Estou tentando ajustar uma consulta em que a mesma função com valor de tabela (TVF) é chamada em 20 colunas.
A primeira coisa que fiz foi converter a função escalar em uma função com valor de tabela embutido.
Está usando CROSS APPLY
a maneira com melhor desempenho para executar a mesma função em várias colunas em uma consulta?
Um exemplo 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
Existem alternativas melhores?
A mesma função pode ser chamada em várias consultas no número X de colunas.
Aqui está a função:
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
Aqui está a versão da função escalar que eu herdei, se alguém estiver interessado:
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
Dados de teste de amostra:
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
).Vou começar jogando alguns dados de teste em uma tabela. Não tenho ideia de como são seus dados reais, então usei números inteiros sequenciais:
A seleção de todas as linhas com os conjuntos de resultados desativados fornece uma linha de base:
Se uma consulta semelhante com a chamada da função levar mais tempo, teremos uma estimativa aproximada da sobrecarga da função. Aqui está o que eu recebo ao chamar seu TVF como é:
Portanto, a função precisa de cerca de 40 segundos de tempo da CPU para 6,5 milhões de linhas. Multiplique isso por 20 e são 800 segundos de tempo de CPU. Notei duas coisas no seu código de função:
Uso desnecessário de
OUTER APPLY
.CROSS APPLY
fornecerá os mesmos resultados e, para essa consulta, evitará muitas junções desnecessárias. Isso pode economizar um pouco de tempo. Depende principalmente se a consulta completa for paralela. Eu não sei nada sobre seus dados ou consulta, então estou apenas testandoMAXDOP 1
. Nesse caso, estou melhor comCROSS APPLY
.Existem muitas
CHARINDEX
chamadas quando você está apenas procurando um caractere em uma pequena lista de valores correspondentes. Você pode usar aASCII()
função e um pouco de matemática para evitar todas as comparações de string.Aqui está uma maneira diferente de escrever a função:
Na minha máquina, a nova função é significativamente mais rápida:
Provavelmente, também existem algumas otimizações adicionais disponíveis, mas meu intestino diz que não serão muito. Com base no que seu código está fazendo, não consigo ver como você veria melhorias ao chamar sua função de uma maneira diferente. É apenas um monte de operações de string. Chamar a função 20 vezes por linha será mais lento do que apenas uma vez, mas a definição já é incorporada.
fonte
Tente usar o seguinte
em vez de
Uma variante com o uso de uma tabela auxiliar
Uma consulta de teste
Como variante, você também pode tentar usar uma tabela auxiliar temporária
#LastCharLink
ou uma tabela variável@LastCharLink
(mas pode ser mais lenta que uma tabela real ou temporária)E use-o como
ou
Depois, você também pode criar uma função embutida simples e colocar todas as conversões
E então use esta função como
fonte
Prefix
vez deDivider
.Como alternativa, você pode criar uma tabela permanente. Essa é uma criação única.
Então TVF
Do exemplo @Joe,
- Demora 30 s
Se possível, o Valor também pode ser formatado no nível da interface do usuário. Essa é a melhor opção. Caso contrário, você também poderá compartilhar sua consulta original. OU, se possível, mantenha o valor formatado na tabela também.
fonte