UDF T-SQL против времени выполнения полного выражения - PullRequest
2 голосов
/ 10 апреля 2019

Я пытаюсь сделать мой запрос читабельным, используя UDF в SQL SERVER, но время выполнения резко увеличивается при использовании функции.

Следующая функция, которую я использую:

create function DL.trim_all(@input varchar(max)) 
returns varchar(max)
as begin 
    set @input=replace(replace(replace(@input,' ',''),')',''),'(','')
    return @input
end

Вместо записи:

SELECT
CASE WHEN replace(replace(replace([FULL_NAME_1],' ',''),')',''),'(','')=replace(replace(replace([FULL_NAME_2],' ',''),')',''),'(','') THEN 1 ELSE 0 END AS [name_match],
CASE WHEN replace(replace(replace([ADDRESS_1],' ',''),')',''),'(','')=replace(replace(replace([ADDRESS_2],' ',''),')',''),'(','') THEN 1 ELSE 0 END AS [adrs_match]
.
.
.
FROM
TABLE_1

для 20 различных полей.

При использовании функции я получаю время выполнения 12,5 минут, а время работы 45 секунд, когда я не использую функцию.

Есть идеи?

Ответы [ 4 ]

2 голосов
/ 10 апреля 2019

Идя дальше к идее Джона, преобразовав скалярную функцию во встроенную табличную функцию и используя функцию перекрестного применения, чтобы активировать ее для каждой пары столбцов - вы можете получить еще лучшую производительность по цене более громоздкого запроса:

CREATE function DL.DoesItMatch(@s1 varchar(500),@s2 varchar(500)) 
returns table -- returns a table with a single row and a single column
as return 
  SELECT 
    CASE WHEN replace(replace(replace(@s1,' ',''),')',''),'(','') = 
              replace(replace(replace(@s2,' ',''),')',''),'(','') THEN 1 ELSE 0 END As IsMatch;    

и запрос:

SELECT NameMatch.IsMatch AS [name_match],
       AddressMatch.IsMatch AS adrs_match
.
.
.
FROM TABLE_1
CROSS APPLY DL.DoesItMatch(FULL_NAME_1, FULL_NAME_2) As NameMatch
CROSS APPLY DL.DoesItMatch(ADDRESS_1, ADDRESS_2) As AddressMatch
1 голос
/ 10 апреля 2019

Инлайнинг - это всегда путь.Период.Даже без учета аспектов, препятствующих параллелизму, скалярных пользовательских функций T-SQL - функции ITVF работают быстрее, требуют меньших ресурсов (ЦП, памяти и ввода-вывода), их легче поддерживать и легче диагностировать, анализировать, профилировать и отслеживать.Для забавы я собрал тест производительности, сравнивая ITVF Zohar со скалярным UDF Джона.Я создал 250K строк, проверил базовый выбор против обоих, затем еще один тест с ORDER BY против кучи для принудительной сортировки.

Пример данных:

-- Sample Data
BEGIN
  SET NOCOUNT ON;
  IF OBJECT_ID('tempdb..#tmp','U') IS NOT NULL DROP TABLE #tmp;
  SELECT TOP (250000) col1 = '('+LEFT(NEWID(),10)+')', col2 = '('+LEFT(NEWID(),10)+')'
  INTO    #tmp
  FROM   sys.all_columns a, sys.all_columns;

  UPDATE #tmp SET col1 = col2 WHERE LEFT(col1,2) = LEFT(col2,2) 
END

Тест производительности:

PRINT 'scalar, no sort'+CHAR(10)+REPLICATE('-',60);
GO
DECLARE @st DATETIME = GETDATE(), @isMatch BIT;
  SELECT @isMatch = DL.DoesItMatch(t.col1,t.col2)
  FROM   #tmp AS t;
PRINT DATEDIFF(MS,@st,GETDATE())
GO 3

PRINT CHAR(10)+'ITVF, no sort'+CHAR(10)+REPLICATE('-',60);
GO
DECLARE @st DATETIME = GETDATE(), @isMatch BIT;
  SELECT      @isMatch = f.isMatch
  FROM        #tmp AS t
  CROSS APPLY DL.DoesItMatch_ITVF(t.col1,t.col2) AS f;
PRINT DATEDIFF(MS,@st,GETDATE())
GO 3    

PRINT CHAR(10)+'scalar, sorted set'+CHAR(10)+REPLICATE('-',60);
GO
DECLARE @st DATETIME = GETDATE(), @isMatch BIT;
  SELECT @isMatch = DL.DoesItMatch(t.col1,t.col2)
  FROM   #tmp AS t
  ORDER BY DL.DoesItMatch(t.col1,t.col2);
PRINT DATEDIFF(MS,@st,GETDATE())
GO 3

PRINT CHAR(10)+'ITVF, sorted set'+CHAR(10)+REPLICATE('-',60);
GO
DECLARE @st DATETIME = GETDATE(), @isMatch BIT;
  SELECT      @isMatch = f.isMatch
  FROM        #tmp AS t
  CROSS APPLY DL.DoesItMatch_ITVF(t.col1,t.col2) AS f
  ORDER BY    f.isMatch;
PRINT DATEDIFF(MS,@st,GETDATE())
GO 3

Результаты теста:

scalar, no sort
------------------------------------------------------------
Beginning execution loop
844
843
840
Batch execution completed 3 times.

ITVF, no sort
------------------------------------------------------------
Beginning execution loop
270
270
270
Batch execution completed 3 times.

scalar, sorted set
------------------------------------------------------------
Beginning execution loop
937
930
936
Batch execution completed 3 times.

ITVF, sorted set
------------------------------------------------------------
Beginning execution loop
196
190
190
Batch execution completed 3 times.

Таким образом, когда параллельный план не требуется, ITVF работает в 3 раза быстрее, а если требуется параллельный план, он в 5 раз быстрее.Вот несколько других ссылок, где я тестировал ITVF против (UDFs со скалярными значениями и таблицами с несколькими состояниями).

План на основе набора работает медленнее, чем функция со скалярным значением со многими условиями

Пользовательская функция SQL Server для расчета возрастной категории

Функция медленная, но запрос выполняется быстро

Почему SQL Server сообщает эту функциюявляется недетерминированным?

Группировка по проценту совпадений

Пользовательская функция SQL Server 2008 для добавления пробелов между каждой цифрой Sql таблицы, разделенные запятыми, содержат значения любых переменных, проверяющих

Обработка SQL-строки, поиск всех перестановок

1 голос
/ 10 апреля 2019

Не могу представить огромный импульс, но как насчет альтернативного подхода

create function DL.DoesItMatch(@s1 varchar(500),@s2 varchar(500)) 
returns bit
as begin 
    return CASE WHEN replace(replace(replace(@s1,' ',''),')',''),'(','')=replace(replace(replace(@s2,' ',''),')',''),'(','') THEN 1 ELSE 0 END
end

Затем вызовите функцию как:

SELECT 
      DL.DoesItMatch([FULL_NAME_1],[FULL_NAME_2])  AS [name_match],
      ...
FROM
TABLE_1
0 голосов
/ 10 апреля 2019

Вы можете использовать встраивание Scalar UDF в SQL Server 2019. Таким образом, вы сможете сохранить тот же UDF, который вы написали, и автоматически получить производительность, идентичную запросу, без UDF.

Указанный вами UDF соответствует критериям встроенности, поэтому вы в хорошей форме.Документация о функции вставки UDF приведена здесь: https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/scalar-udf-inlining?view=azuresqldb-current

Совет для профессионала: я бы посоветовал вам внести незначительные изменения в вашу UDF перед использованием Scalar UDF inlining.Сделайте это в едином операторе скалярной UDF, избегая локальной переменной.При этом вам будет лучше, чем при использовании встроенного TVF с перекрестным применением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...