【问题标题】:Scalar function with WHILE loop to inline function带有 WHILE 循环到内联函数的标量函数
【发布时间】:2018-12-06 18:14:32
【问题描述】:

您好,我有一个视图用于我的应用程序中的大量搜索查询。 问题是使用此视图的应用程序查询运行速度非常慢。我正在对此进行调查,我发现视图定义中的某个特定部分使其运行缓慢。 创建视图Demoview AS

Select 
    p.Id as Id,
    ----------,
    STUFF((SELECT ',' + [dbo].[OnlyAlphaNum](colDesc)
        FROM dbo.ContactInfoDetails cd
        WHERE pp.FormId = f.Id AND ppc.PageId = pp.Id 
        FOR XML PATH('')), 1, 1, '') AS PhoneNumber,
    p.FirstName as Fname,
From
---

这是视图中的列之一。 标量函数[OnlyAlphaNum] 使它变慢,因为它停止了查询的并行执行。

功能如下;

CREATE FUNCTION [dbo].[OnlyAlphaNum]
(

@String VARCHAR(MAX)

)

RETURNS VARCHAR(MAX)
WITH SCHEMABINDING
AS
BEGIN

   WHILE PATINDEX('%[^A-Z0-9]%', @String) > 0

        SET @String = STUFF(@String, PATINDEX('%[^A-Z0-9]%', @String), 1, '')

RETURN @String

END

如何将其转换为内联函数。? 我尝试使用 CASE,但没有成功。我读过 CTE 是一个不错的选择。 知道如何解决这个问题吗?

【问题讨论】:

  • 如何将预先计算的值存储在另一列中并直接使用该列?如果您的读写比率很高,那么值得在插入/更新时计算值并将其存储在冗余列中。
  • 这也需要在应用程序方面进行更改,对吗?我不允许对应用程序代码进行任何更改。另外,对于修复单个查询,应用程序团队不会批准。您的建议是有效的如果早点实施,那就容易多了。
  • 你只能在数据库端使用触发器来解决这个问题。

标签: sql-server tsql while-loop common-table-expression


【解决方案1】:

当然还有改进的余地。测试一下。

   ;WITH CTE AS (
    SELECT (CASE WHEN PATINDEX('%[^A-Z0-9]%', D.Name) > 0
           THEN STUFF(D.Name, PATINDEX('%[^A-Z0-9]%', D.Name), 1, '')
           ELSE D.NAME
           END ) NameString
    FROM @dept D
    UNION ALL
    SELECT STUFF(C.NameString, PATINDEX('%[^A-Z0-9]%', C.NameString), 1, '')
    FROM CTE C
    WHERE PATINDEX('%[^A-Z0-9]%', C.NameString) > 0
    )

Select STUFF((SELECT ',' + E.NameString from CTE E
WHERE PATINDEX('%[^A-Z0-9]%', E.NameString) = 0
FOR XML PATH('')), 1, 1, '') AS NAME

【讨论】:

  • 我尝试使用 CASE,但它没有正确格式化数据。使用功能时,我得到的电话号码是 5472154896。但是使用案例时,我会得到 547)215 4896。我想使用 WHILE 它遍历字符串并做这些事情,但使用 CASE 则不是。这就是我的想法。
  • 它是视图内的一列,我不能在其中使用 CTE。如果我可以通过添加内联表值函数来更改视图,它将满足我的要求。
【解决方案2】:

我已经这样做了;你可以阅读更多关于它的信息here。

功能:

CREATE FUNCTION dbo.alphaNumericOnly8K(@pString varchar(8000)) 
RETURNS TABLE WITH SCHEMABINDING AS RETURN
/****************************************************************************************
Purpose:
 Given a varchar(8000) string or smaller, this function strips all but the alphanumeric 
 characters that exist in @pString.

Compatibility: 
 SQL Server 2008+, Azure SQL Database, Azure SQL Data Warehouse & Parallel Data Warehouse

Parameters:
 @pString = varchar(8000); Input string to be cleaned

Returns:
 AlphaNumericOnly - varchar(8000) 

Syntax:
--===== Autonomous
 SELECT ca.AlphaNumericOnly
 FROM dbo.AlphaNumericOnly(@pString) ca;

--===== CROSS APPLY example
 SELECT ca.AlphaNumericOnly
 FROM dbo.SomeTable st
 CROSS APPLY dbo.AlphaNumericOnly(st.SomeVarcharCol) ca;

Programmer's Notes:
 1. Based on Jeff Moden/Eirikur Eiriksson's DigitsOnlyEE function. For more details see:
    http://www.sqlservercentral.com/Forums/Topic1585850-391-2.aspx#bm1629360

 2. This is an iTVF (Inline Table Valued Function) that performs the same task as a 
    scalar user defined function (UDF) accept that it requires the APPLY table operator. 
    Note the usage examples below and see this article for more details: 
    http://www.sqlservercentral.com/articles/T-SQL/91724/ 

    The function will be slightly more complicated to use than a scalar UDF but will yeild
    much better performance. For example - unlike a scalar UDF, this function does not 
    restrict the query optimizer's ability generate a parallel query plan. Initial testing
    showed that the function generally gets a 

 3. AlphaNumericOnly runs 2-4 times faster when using make_parallel() (provided that you 
    have two or more logical CPU's and MAXDOP is not set to 1 on your SQL Instance).

 4. This is an iTVF (Inline Table Valued Function) that will be used as an iSF (Inline 
    Scalar Function) in that it returns a single value in the returned table and should 
    normally be used in the FROM clause as with any other iTVF.

 5. CHECKSUM returns an INT and will return the exact number given if given an INT to 
    begin with. It's also faster than a CAST or CONVERT and is used as a performance 
    enhancer by changing the bigint of ROW_NUMBER() to a more appropriately sized INT.

 6. Another performance enhancement is using a WHERE clause calculation to prevent 
    the relatively expensive XML PATH concatentation of empty strings normally 
    determined by a CASE statement in the XML "loop".

 7. Note that AlphaNumericOnly returns an nvarchar(max) value. If you are returning small 
    numbers consider casting or converting yout values to a numeric data type if you are 
    inserting the return value into a new table or using it for joins or comparison 
    purposes.

 8. AlphaNumericOnly is deterministic; for more about deterministic and nondeterministic
    functions see https://msdn.microsoft.com/en-us/library/ms178091.aspx

Usage Examples:
--===== 1. Basic use against a literal

 SELECT ao.AlphaNumericOnly 
 FROM samd.alphaNumericOnly8K('xxx123abc999!!!') ao;

--===== 2. Against a table 
 DECLARE @sampleTxt TABLE (txtID int identity, txt varchar(100));
 INSERT @sampleTxt(txt) VALUES ('!!!A555A!!!'),(NULL),('AAA.999');

 SELECT txtID, OldTxt = txt, AlphaNumericOnly
 FROM @sampleTxt st
 CROSS APPLY samd.alphaNumericOnly8K(st.txt);

---------------------------------------------------------------------------------------
Revision History:
 Rev 00 - 20150526 - Inital Creation - Alan Burstein
 Rev 00 - 20150526 - 3rd line in WHERE clause to correct something that was missed
                   - Eirikur Eiriksson
 Rev 01 - 20180624 - ADDED ORDER BY N; now performing CHECKSUM conversion to INT inside
                     the final cte (digitsonly) so that ORDER BY N does not get sorted.
****************************************************************************************/ 
WITH 
E1(N) AS 
(
  SELECT N 
  FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))x(N)
), 
iTally(N) AS 
( 
  SELECT TOP (LEN(ISNULL(@pString,CHAR(32)))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
  FROM E1 a CROSS JOIN E1 b CROSS JOIN E1 c CROSS JOIN E1 d 
) 
SELECT AlphaNumericOnly = 
( 
  SELECT SUBSTRING(@pString,CHECKSUM(N),1) 
  FROM iTally 
  WHERE 
     ((ASCII(SUBSTRING(@pString,CHECKSUM(N),1)) - 48) & 0x7FFF) < 10 
  OR ((ASCII(SUBSTRING(@pString,CHECKSUM(N),1)) - 65) & 0x7FFF) < 26 
  OR ((ASCII(SUBSTRING(@pString,CHECKSUM(N),1)) - 97) & 0x7FFF) < 26 
  ORDER BY N
  FOR XML PATH('') 
);

注意代码cmets中的例子:

--===== 1. Basic use against a literal    
 SELECT ao.AlphaNumericOnly 
 FROM samd.alphaNumericOnly8K('xxx123abc999!!!') ao;

--===== 2. Against a table 
 DECLARE @sampleTxt TABLE (txtID int identity, txt varchar(100));
 INSERT @sampleTxt(txt) VALUES ('!!!A555A!!!'),(NULL),('AAA.999');

 SELECT txtID, OldTxt = txt, AlphaNumericOnly
 FROM @sampleTxt st
 CROSS APPLY samd.alphaNumericOnly8K(st.txt);

返回:

AlphaNumericOnly
-------------------
xxx123abc999

txtID       OldTxt        AlphaNumericOnly 
----------- ------------- -----------------
1           !!!A555A!!!   A555A
2           NULL          NULL
3           AAA.999       AAA999

这是同类中最快的。它使用并行执行计划运行得特别快。要强制执行并行执行计划,请获取make_parallel by Adam Machanic 的副本。然后你会像这样运行它:

--===== 1. Basic use against a literal    
 SELECT ao.AlphaNumericOnly 
 FROM dbo.alphaNumericOnly8K('xxx123abc999!!!') ao
 CROSS APPLY dbo.make_parallel();

--===== 2. Against a table 
 DECLARE @sampleTxt TABLE (txtID int identity, txt varchar(100));
 INSERT @sampleTxt(txt) VALUES ('!!!A555A!!!'),(NULL),('AAA.999');

 SELECT txtID, OldTxt = txt, AlphaNumericOnly
 FROM @sampleTxt st
 CROSS APPLY dbo.alphaNumericOnly8K(st.txt)
 CROSS APPLY dbo.make_parallel();

【讨论】:

    猜你喜欢
    • 2012-01-12
    • 2016-09-19
    • 2014-02-13
    • 1970-01-01
    • 2012-12-21
    • 2021-02-27
    • 1970-01-01
    • 2020-06-16
    • 2021-12-03
    相关资源
    最近更新 更多