J'essaie de régler une requête dans laquelle la même fonction table (TVF) est appelée sur 20 colonnes.
La première chose que j'ai faite a été de convertir la fonction scalaire en une fonction de valeur de table en ligne.
La méthode CROSS APPLY
la plus performante consiste-t-elle à exécuter la même fonction sur plusieurs colonnes dans une requête?
Un exemple simpliste:
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
Existe-t-il de meilleures alternatives?
La même fonction peut être appelée dans plusieurs requêtes sur un nombre X de colonnes.
Voici la fonction:
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
Voici la version de la fonction scalaire dont j'ai hérité, si quelqu'un est intéressé:
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
Exemples de données de test:
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).Je vais commencer par jeter quelques données de test dans une table. Je n'ai aucune idée de l'apparence de vos données réelles, j'ai donc utilisé des entiers séquentiels:
La sélection de toutes les lignes dont les jeux de résultats sont désactivés fournit une ligne de base:
Si une requête similaire avec l'appel de fonction prend plus de temps, nous avons une estimation approximative de la surcharge de la fonction. Voici ce que j'obtiens en appelant votre TVF tel quel:
La fonction a donc besoin d'environ 40 secondes de temps processeur pour 6,5 millions de lignes. Multipliez cela par 20 et c'est 800 secondes de temps CPU. J'ai remarqué deux choses dans votre code de fonction:
Utilisation inutile de
OUTER APPLY
.CROSS APPLY
vous donnera les mêmes résultats, et pour cette requête, cela évitera un tas de jointures inutiles. Cela peut vous faire gagner un peu de temps. Cela dépend principalement si la requête complète va en parallèle. Je ne sais rien de vos données ou de votre requête, je teste donc simplement avecMAXDOP 1
. Dans ce cas, je suis mieux avecCROSS APPLY
.Il y a beaucoup d'
CHARINDEX
appels lorsque vous recherchez simplement un caractère par rapport à une petite liste de valeurs correspondantes. Vous pouvez utiliser laASCII()
fonction et un peu de maths pour éviter toutes les comparaisons de chaînes.Voici une manière différente d'écrire la fonction:
Sur ma machine, la nouvelle fonction est nettement plus rapide:
Il y a probablement aussi des optimisations supplémentaires disponibles, mais mon instinct dit qu'elles ne seront pas très importantes. Sur la base de ce que fait votre code, je ne vois pas comment vous verriez une amélioration supplémentaire en appelant votre fonction d'une manière différente. C'est juste un tas d'opérations de chaîne. L'appel de la fonction 20 fois par ligne sera plus lent qu'une seule fois, mais la définition est déjà intégrée.
la source
Essayez d'utiliser ce qui suit
au lieu
Une variante avec l'utilisation d'une table auxiliaire
Une requête de test
En variante, vous pouvez également essayer d'utiliser une table auxiliaire temporaire
#LastCharLink
ou une table variable@LastCharLink
(mais elle peut être plus lente qu'une table réelle ou temporaire)Et utilisez-le comme
ou
Ensuite, vous pouvez également créer une fonction en ligne simple et y mettre toutes les conversions
Et puis utilisez cette fonction comme
la source
Prefix
au lieu deDivider
.Alternativement, vous pouvez créer une table permanente, c'est une création unique.
Puis TVF
De l'exemple @Joe,
- Il faut 30 s
Si cela est possible, le montant peut également être formaté au niveau de l'interface utilisateur. C'est la meilleure solution. Sinon, vous pouvez également partager votre requête d'origine. OU si possible, conservez également la valeur formatée dans le tableau.
la source