Джордж Мастрос писал:
Я бы предложил UDF сделать это.Поскольку UDF, который я собираюсь предложить, не касается таблиц, производительность должна быть довольно хорошей.
Я согласен, что Scalar UDF "только для памяти" довольно быстрые.Фактически, я использовал один из скалярных UDF Джорджа, который решил проблему «начальных заглавных букв», чтобы продемонстрировать, что иногда код «на основе набора» ISN'T всегда лучший путь.
Однако Мартин Смит (еще один автор этой темы) определенно был на правильном пути.В этом случае «Set Based» - это еще путь.Конечно, любой может сделать необоснованное заявление о производительности, поэтому давайте подведем итоги с демонстрацией производительности.
Для демонстрации нам сначала понадобятся некоторые тестовые данные.Множество тестовых данных, потому что обе функции, которые мы собираемся протестировать, выполняются очень быстро.Вот код для построения тестовой таблицы с миллионами строк.
--===== Conditionally drop the test table
-- to make reruns in SSMS easier
IF OBJECT_ID('tempdb..#MyHead','U') IS NOT NULL
DROP TABLE #MyHead
GO
--===== Create and populate the test table on-the-fly.
-- This builds a bunch of GUIDs and removes the dashes from them to
-- increase the chances of duplicating adjacent characters.
-- Not to worry. This takes less than 7 seconds to run because of
-- the "Pseudo Cursor" created by the CROSS JOIN.
SELECT TOP 1000000
RowNum = IDENTITY(INT,1,1),
SomeString = REPLACE(CAST(NEWID() AS VARCHAR(36)),'-','')
INTO #MyHead
FROM sys.all_columns ac1
CROSS JOIN sys.all_columns ac2
;
GO
Нет необходимости репостить здесь тонкую функцию Джорджа, но мне нужно опубликовать мою.Следующая функция дает тот же результат, что и у Джорджа.Он выглядит как «iTVF» (функция с оценкой встроенной таблицы) и возвращает только одно значение.Вот почему Microsoft называет их «встроенными скалярными функциями» (для краткости я называю их «iSF»).
CREATE FUNCTION dbo.CleanDuplicatesJBM
(@Data VARCHAR(8000), @DuplicateChar VARCHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT Item = STUFF(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
@DuplicateChar+@Data COLLATE LATIN1_GENERAL_BIN,
REPLICATE(@DuplicateChar,33),@DuplicateChar),
REPLICATE(@DuplicateChar,17),@DuplicateChar),
REPLICATE(@DuplicateChar, 9),@DuplicateChar),
REPLICATE(@DuplicateChar, 5),@DuplicateChar),
REPLICATE(@DuplicateChar, 3),@DuplicateChar),
REPLICATE(@DuplicateChar, 2),@DuplicateChar),
REPLICATE(@DuplicateChar, 2),@DuplicateChar)
,1,1,'')
;
GO
Сначала давайте проверим скалярный UDF Джорджа.Пожалуйста, прочитайте комментарии о том, почему мы не используем SET STATISTICS TIME ON здесь.
/******************************************************************************
Test George's code.
Since Scalar Functions don't work well with SET STATISTICS TIME ON, we measure
duration a different way. We'll also throw away the result in a "Bit Bucket"
variable because we're trying to measure the performance of the function
rather than how long it takes to display or store results.
******************************************************************************/
--===== Declare some obviously named variables
DECLARE @StartTime DATETIME,
@BitBucket VARCHAR(36)
;
--===== Start the "Timer"
SELECT @StartTime = GETDATE()
;
--===== Run the test on the function
SELECT @BitBucket = [dbo].[CleanDuplicates](SomeString,'A')
FROM #MyHead
;
--===== Display the duration in milliseconds
PRINT DATEDIFF(ms,@StartTime,GETDATE())
;
--===== Run the test a total of 5 times
GO 5
Вот результаты этого «пятикратного» запуска ...
Beginning execution loop
15750
15516
15543
15480
15510
Batch execution completed 5 times.
(Average is 15,559 on my 10 year old, single 1.8Ghz CPU)
Теперь,мы запустим версию "iSF" ...
/******************************************************************************
Test Jeff's code.
Even though this uses an "iSF" (Inline Scalar Function), we'll test exactly
the same way that we tested George's code so we're comparing apples-to-apples.
This includes throwing away the result in a "Bit Bucket" variable because
we're trying to measure the performance of the function rather than how long
it takes to display or store results.
******************************************************************************/
--===== Declare some obviously named variables
DECLARE @StartTime DATETIME,
@BitBucket VARCHAR(36)
;
--===== Start the "Timer"
SELECT @StartTime = GETDATE()
;
--===== Run the test on the function
SELECT @BitBucket = cleaned.ITEM
FROM #MyHead
CROSS APPLY [dbo].[CleanDuplicatesJBM](SomeString,'A') cleaned
;
--===== Display the duration in milliseconds
PRINT DATEDIFF(ms,@StartTime,GETDATE())
;
--===== Run the test a total of 5 times
GO 5
Вот результаты этого прогона.
Beginning execution loop
6856
6810
7020
7350
6996
Batch execution completed 5 times.
(Average is 7,006 {more than twice as fast} on my 10 year old, single 1.8Ghz CPU)
Я не считаю, что код Джорджа плохой.Не за что.На самом деле, я использую Scalar UDF, когда нет решения «одного запроса».Я также констатирую и поддерживаю Джорджа, говоря, что не все решения с «одним запросом» всегда являются лучшими.
Просто не переставайте искать их, когда дело доходит до UDF.; -)