Альтернатива размещению скалярной функции в предложении Where

#sql-server #tsql #optimization #indexing #user-defined-functions

#sql-сервер #tsql #оптимизация #индексирование #определяемые пользователем функции

Вопрос:

У меня есть хранимая процедура, которая принимает строку и сравнивает ее с несколькими полями. Однако одно из полей содержит дополнительные символы, которые необходимо удалить, прежде чем сравнивать его со строкой поиска.

например

 Create Or Alter Proc FindPosts3
(
     @criteria AS nvarchar(25)
)
AS
Begin

     Select P.OwnerUserId,P.CreationDate,p.Score,p.CommentCount,p.Body1
     From Posts1 p Inner Join
     (
         Select p.OwnerUserId,max(p.CreationDate) as CreationDate
         From Posts1 p
         Group by p.OwnerUserId

     ) Sub On P.OwnerUserId = Sub.OwnerUserId AND P.CreationDate = Sub.CreationDate
     Where p.Score = @criteria OR p.CommentCount = @criteria Or udfstripHtmlTags(p.body1) = @criteria   

End
 

Примерные значения полей:

 OwernerUserID---CreationDate----Score----CommentCount----Body
1               Aug 20, 2010     18      6               <p>null<p>
2               Dec 15, 2008      7      3               <b>variable<b>
3               Mar 07, 2011     15      20              <i>Arrays<i>
 

При поиске в поле body <p>, <b> and <i> теги удаляются с помощью функции udfStripHtmlTag . Это приводит к тому, что поле Body в предложении where больше не является SARG аргументом поиска » или», что замедляет выполнение запроса.

В этом примере я удаляю HTML-теги из поля body, используя функцию с именем ‘udfStripHtmlTag’, прежде чем сравнивать ее с критериями. Как и ожидалось, это приводит к тому, что вместо поиска по индексу выполняется сканирование индекса с индексом, созданным для поля body . Фактически, это также приводит к тому, что индексы для оценки и commentcount полей больше не используются в этой версии запроса. В другой версии, где я использую объединение вместо «или», индексы для оценки и commentcount полей по-прежнему используются. Однако запрос по-прежнему выполняется так же медленно из-за сканирования индекса для основного поля.

Удаление символов из поля является обязательным требованием. Есть ли альтернативный метод, который я могу использовать вместо скалярной функции в предложении where?

примечание. Это не мой исходный код проблемы. Скорее это код, основанный на базе данных StackOverflow. Мой исходный код проблемы основан на большом количестве конфиденциальных данных / полей, поэтому мне было проще использовать альтернативную базу данных. Аналогично, поле body в базе данных StackOverflow было изменено на nvarchar(30) , чтобы я мог создать для него индекс. Наконец, запрос выполняется менее чем за секунду без скалярной функции.

Комментарии:

1. Функции с табличным значением, как правило, более производительны, но примеры данных и желаемых результатов были бы полезны.

2. Удаление символов из поля является обязательным требованием. Это источник вашей проблемы. если вам нужно выполнить поиск и вам нужно удалить из него html для поиска, то сделайте это ОДИН РАЗ при вставке или обновлении этого столбца.

3. Это не мой исходный код проблемы , тогда любые предложения, которые вы можете получить, могут быть полностью неосновными.

4. @SMor, хотя это не моя первоначальная проблема, я смог воспроизвести те же проблемы с производительностью. Проблема заключается в функции в предложении where . Если я смогу решить это здесь, это также решит мою первоначальную проблему. Кроме того, ваше предложение правильно делать это при обновлении или вставке. Однако я не могу изменить исходную базу данных. Я пишу запросы только для извлечения данных.

5. @JohnCappelletti Я добавил таблицу, я не знаю, полезно ли это. Тем не менее, результаты, которые я получаю, в порядке. Просто функция приводит к тому, что индекс не используется, что приводит к тому, что выполнение запроса занимает около полутора минут.

Ответ №1:

Не уверен, что это более эффективно, чем ваша скалярная функция

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

Пример

 Declare @YourTable Table ([OwernerUserID] int,[CreationDate] varchar(50),[Score] int,[CommentCount] int,[Body] varchar(150))  
Insert Into @YourTable Values 
 (1,'Aug 20, 2010',18,6,'<p>null</p>')
,(2,'Dec 15, 2008',7,3,'<b>variable</b>')
,(3,'Mar 07, 2011',15,20,'<i>Arrays</i>')
 
Select A.* 
 From @YourTable A
 Cross Apply [dbo].[tvf-Str-Extract](Body,'>','</') B
 Where B.RetVal like 'variable%'
 

ВОЗВРАТ

 OwernerUserID   CreationDate    Score   CommentCount    Body
2               Dec 15, 2008    7       3               <b>variable</b>
 

Табличная функция, если она заинтересована

 CREATE FUNCTION [dbo].[tvf-Str-Extract] (@String varchar(max),@Delim1 varchar(100),@Delim2 varchar(100))
Returns Table 
As
Return (  

    Select RetSeq = row_number() over (order by RetSeq)
          ,RetVal = left(RetVal,charindex(@Delim2,RetVal)-1)
    From  (
            Select RetSeq = row_number() over (order by 1/0)
                  ,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
            From  ( values (convert(xml,'<x>'   replace((Select replace(@String,@Delim1,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>') '</x>').query('.'))) as A(XMLData)
            Cross Apply XMLData.nodes('x') AS B(i)
          ) C1
    Where charindex(@Delim2,RetVal)>1

)
/*
Max Length of String 1MM characters

Declare @String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[tvf-Str-Extract] (@String,'[[',']]')
*/
 

Комментарии:

1. Большое вам спасибо. Это именно то, что мне нужно. Теперь мой запрос выполняется менее чем за секунду при поиске более 4 миллионов записей. Странно, что многие онлайн-ресурсы советуют не использовать скалярные функции в предложениях where, но не предлагают жизнеспособной альтернативы. Для меня это решение должно работать в самых разных сценариях. Еще раз спасибо.

2. Чтобы было ясно, логика в вашей табличной функции не имела значения. Я создал tvf со своей собственной логикой, а затем использовал перекрестное применение. Затем я сравнил возвращаемое значение из cross apply с моими критериями так же, как и в примере.