Я пытаюсь настроить запрос, в котором одна и та же табличная функция (TVF) вызывается для 20 столбцов.
Первым делом я преобразовал скалярную функцию во встроенную табличную функцию.
Используется CROSS APPLY
ли наилучший способ выполнения одной и той же функции для нескольких столбцов в запросе?
Упрощенный пример:
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
Есть ли лучшие альтернативы?
Одна и та же функция может быть вызвана в нескольких запросах против числа столбцов.
Вот функция:
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
Вот версия скалярной функции, которую я унаследовал, если кому-то интересно:
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
Пример тестовых данных:
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).Я начну с того, что добавлю некоторые тестовые данные в таблицу. Я понятия не имею, как выглядят ваши реальные данные, поэтому я просто использовал последовательные целые числа:
Выбор всех строк с отключенными наборами результатов обеспечивает базовую строку:
Если аналогичный запрос с вызовом функции занимает больше времени, то мы имеем приблизительную оценку накладных расходов функции. Вот что я получаю, называя ваш TVF как есть:
Таким образом, функции требуется около 40 секунд процессорного времени для 6,5 миллионов строк. Умножьте это на 20, и это будет 800 секунд процессорного времени. Я заметил две вещи в вашем коде функции:
Ненужное использование
OUTER APPLY
.CROSS APPLY
даст вам те же результаты, и для этого запроса он избежит кучу ненужных объединений. Это может сэкономить немного времени. Это в основном зависит от того, идет ли полный запрос параллельно. Я ничего не знаю о ваших данных или запросах, поэтому я просто проверяюMAXDOP 1
. В этом случае мне лучшеCROSS APPLY
.Есть много
CHARINDEX
вызовов, когда вы просто ищете один символ по небольшому списку совпадающих значений. Вы можете использоватьASCII()
функцию и немного математики, чтобы избежать всех сравнений строк.Вот другой способ написать функцию:
На моей машине новая функция значительно быстрее:
Возможно, есть также некоторые дополнительные оптимизации, но моя интуиция говорит, что они не будут значительными. Исходя из того, что делает ваш код, я не могу понять, как вы бы увидели дальнейшее улучшение, каким-либо образом вызывая вашу функцию. Это просто набор строковых операций. Вызов функции 20 раз в строке будет медленнее, чем один раз, но определение уже встроено.
источник
Попробуйте использовать следующее
вместо
Один вариант с использованием вспомогательной таблицы
Тестовый запрос
В качестве варианта вы также можете попытаться использовать временную вспомогательную таблицу
#LastCharLink
или таблицу переменных@LastCharLink
(но она может быть медленнее, чем реальная или временная таблица)И использовать его как
или
Тогда вы также можете создать простую встроенную функцию и вставить в нее все преобразования
А затем использовать эту функцию как
источник
Prefix
вместоDivider
.В качестве альтернативы вы можете создать одну постоянную таблицу. Это однократное создание.
Тогда твф
Из примера @Joe,
- это займет 30 с
Если это возможно, сумма также может быть отформатирована на уровне пользовательского интерфейса. Это лучший вариант. В противном случае вы также можете поделиться своим оригинальным запросом. ИЛИ, если возможно, сохраняйте форматированное значение в таблице.
источник