Ich versuche, eine Abfrage zu optimieren, bei der dieselbe Tabellenwertfunktion (TVF) für 20 Spalten aufgerufen wird.
Als erstes habe ich die Skalarfunktion in eine Inline-Tabellenwertfunktion konvertiert.
Verwenden Sie CROSS APPLY
die leistungsstärkste Methode, um dieselbe Funktion für mehrere Spalten in einer Abfrage auszuführen?
Ein vereinfachtes Beispiel:
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
Gibt es bessere Alternativen?
Dieselbe Funktion kann in mehreren Abfragen für die X-Anzahl von Spalten aufgerufen werden.
Hier ist die Funktion:
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
Hier ist die Skalarfunktionsversion, die ich geerbt habe, wenn jemand interessiert ist:
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
Beispieltestdaten:
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 nicht benötigt ).Ich werde zunächst einige Testdaten in eine Tabelle werfen. Ich habe keine Ahnung, wie Ihre realen Daten aussehen, also habe ich nur sequentielle Ganzzahlen verwendet:
Wenn Sie alle Zeilen mit deaktivierten Ergebnismengen auswählen, erhalten Sie eine Basislinie:
Wenn eine ähnliche Abfrage mit dem Funktionsaufruf länger dauert, haben wir eine grobe Schätzung des Overheads der Funktion. Folgendes bekomme ich, wenn ich Ihre TVF so anrufe, wie sie ist:
Die Funktion benötigt also ungefähr 40 Sekunden CPU-Zeit für 6,5 Millionen Zeilen. Multiplizieren Sie dies mit 20 und es sind 800 Sekunden CPU-Zeit. Ich habe zwei Dinge in Ihrem Funktionscode bemerkt:
Unnötige Verwendung von
OUTER APPLY
.CROSS APPLY
Sie erhalten die gleichen Ergebnisse und vermeiden bei dieser Abfrage eine Reihe unnötiger Verknüpfungen. Das kann ein bisschen Zeit sparen. Es hängt hauptsächlich davon ab, ob die vollständige Abfrage parallel verläuft. Ich weiß nichts über Ihre Daten oder Abfragen, also teste ich nur mitMAXDOP 1
. In diesem Fall bin ich besser dranCROSS APPLY
.Es gibt viele
CHARINDEX
Aufrufe, wenn Sie nur nach einem Zeichen anhand einer kleinen Liste übereinstimmender Werte suchen. Sie können dieASCII()
Funktion und ein wenig Mathematik verwenden, um alle Zeichenfolgenvergleiche zu vermeiden.Hier ist eine andere Art, die Funktion zu schreiben:
Auf meinem Computer ist die neue Funktion deutlich schneller:
Es gibt wahrscheinlich auch einige zusätzliche Optimierungen, aber mein Bauch sagt, dass sie nicht viel ausmachen werden. Aufgrund dessen, was Ihr Code tut, kann ich nicht sehen, wie Sie weitere Verbesserungen sehen würden, wenn Sie Ihre Funktion auf eine andere Weise aufrufen würden. Es ist nur eine Reihe von String-Operationen. Das 20-malige Aufrufen der Funktion pro Zeile ist langsamer als nur einmal, aber die Definition wird bereits eingefügt.
quelle
Versuchen Sie Folgendes zu verwenden
stattdessen
Eine Variante mit Verwendung einer Hilfstabelle
Eine Testabfrage
Als Variante können Sie auch versuchen, eine temporäre Hilfstabelle
#LastCharLink
oder eine Variablentabelle zu verwenden@LastCharLink
(diese kann jedoch langsamer sein als eine reale oder temporäre Tabelle).Und benutze es als
oder
Dann können Sie auch eine einfache Inline-Funktion erstellen und alle Konvertierungen einfügen
Und dann nutzen Sie diese Funktion als
quelle
Prefix
anstelle vonDivider
.Alternativ können Sie eine permanente Tabelle erstellen. Dies ist eine einmalige Erstellung.
Dann TVF
Aus @ Joe Beispiel,
- Es dauert 30 s
Wenn es möglich ist, kann der Betrag auch auf UI-Ebene formatiert werden. Dies ist die beste Option. Andernfalls können Sie auch Ihre ursprüngliche Abfrage freigeben. ODER wenn möglich, formatieren Sie den formatierten Wert auch in der Tabelle.
quelle