【问题标题】:User defined function replacing WHERE col IN(...)用户定义的函数替换 WHERE col IN(...)
【发布时间】:2009-04-16 18:34:33
【问题描述】:

我已经创建了一个用户定义的函数,以通过包含 'WHERE col IN (...)' 的查询来获得性能,例如这种情况:

SELECT myCol1, myCol2
FROM myTable
WHERE myCol3 IN (100, 200, 300, ..., 4900, 5000);

查询是从 Web 应用程序生成的,并且在某些情况下要复杂得多。 函数定义如下:

CREATE FUNCTION [dbo].[udf_CSVtoIntTable]
(
  @CSV VARCHAR(MAX),
  @Delimiter CHAR(1) = ','
)
RETURNS 
@Result TABLE 
(
    [Value] INT
)
AS
BEGIN

  DECLARE @CurrStartPos SMALLINT;
  SET @CurrStartPos = 1;
  DECLARE @CurrEndPos SMALLINT;
  SET @CurrEndPos = 1;
  DECLARE @TotalLength SMALLINT;

  -- Remove space, tab, linefeed, carrier return
  SET @CSV = REPLACE(@CSV, ' ', '');
  SET @CSV = REPLACE(@CSV, CHAR(9), '');
  SET @CSV = REPLACE(@CSV, CHAR(10), '');
  SET @CSV = REPLACE(@CSV, CHAR(13), '');

  -- Add extra delimiter if needed
  IF NOT RIGHT(@CSV, 1) = @Delimiter
    SET @CSV = @CSV + @Delimiter;

  -- Get total string length 
  SET @TotalLength = LEN(@CSV);

  WHILE @CurrStartPos < @TotalLength
  BEGIN

    SET @CurrEndPos = CHARINDEX(@Delimiter, @CSV, @CurrStartPos);

    INSERT INTO @Result
    VALUES (CAST(SUBSTRING(@CSV, @CurrStartPos, @CurrEndPos - @CurrStartPos) AS INT));

    SET @CurrStartPos = @CurrEndPos + 1;

  END

    RETURN 

END

该函数旨在像这样使用(或作为 INNER JOIN):

SELECT myCol1, myCol2
FROM myTable
WHERE myCol3 IN (
    SELECT [Value] 
    FROM dbo.udf_CSVtoIntTable('100, 200, 300, ..., 4900, 5000', ',');

在我的情况下,是否有人对我的功能或其他提高性能的方法有一些优化想法? 有没有我遗漏的缺点?

我正在使用 MS SQL Server 2005 Std 和 .NET 2.0 框架。

【问题讨论】:

    标签: sql-server tsql optimization


    【解决方案1】:

    我不确定性能是否会提高,但我会将其用作内部连接并远离内部选择语句。

    【讨论】:

      【解决方案2】:

      在 WHERE 子句或(更糟糕的)子查询中使用 UDF 是自找麻烦。优化器有时会做对,但经常会出错,并为查询中的每一行评估一次函数,这是您不想要的。

      如果您的参数是静态的(它们看起来是静态的)并且您可以发出多语句批处理,我会将您的 UDF 的结果加载到表变量中,然后对表变量使用连接来进行过滤。这应该更可靠地工作。

      【讨论】:

        【解决方案3】:

        那个循环会影响性能!

        创建一个这样的表:

        CREATE TABLE Numbers
        (
            Number  int   not null primary key
        )
        

        具有包含值 1 到 8000 左右的行并使用此函数:

        CREATE FUNCTION [dbo].[FN_ListAllToNumberTable]
        (
             @SplitOn  char(1)       --REQUIRED, the character to split the @List string on
            ,@List     varchar(8000) --REQUIRED, the list to split apart
        )
        RETURNS
        @ParsedList table
        (
            RowNumber int
           ,ListValue varchar(500)
        )
        AS
        BEGIN
        
        /*
        DESCRIPTION: Takes the given @List string and splits it apart based on the given @SplitOn character.
                     A table is returned, one row per split item, with a columns named "RowNumber" and "ListValue".
                     This function workes for fixed or variable lenght items.
                     Empty and null items will be included in the results set.
        
        PARAMETERS:
            @List      varchar(8000) --REQUIRED, the list to split apart
            @SplitOn   char(1)       --OPTIONAL, the character to split the @List string on, defaults to a comma ","
        
        
        RETURN VALUES:
          a table, one row per item in the list, with a column name "ListValue"
        
        TEST WITH:
        ----------
        SELECT * FROM dbo.FN_ListAllToNumTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')
        
        DECLARE @InputList  varchar(200)
        SET @InputList='17;184;75;495'
        SELECT
            'well formed list',LEFT(@InputList,40) AS InputList,h.Name
            FROM Employee  h
                INNER JOIN dbo.FN_ListAllToNumTable(';',@InputList) dt ON h.EmployeeID=dt.ListValue
            WHERE dt.ListValue IS NOT NULL
        
        SET @InputList='17;;;184;75;495;;;'
        SELECT
            'poorly formed list join',LEFT(@InputList,40) AS InputList,h.Name
            FROM Employee  h
                INNER JOIN dbo.FN_ListAllToNumTable(';',@InputList) dt ON h.EmployeeID=dt.ListValue
        
        SELECT
            'poorly formed list',LEFT(@InputList,40) AS InputList, ListValue
            FROM dbo.FN_ListAllToNumTable(';',@InputList)
        
        **/
        
        
        
        /*this will return empty rows, and row numbers*/
        INSERT INTO @ParsedList
                (RowNumber,ListValue)
            SELECT
                ROW_NUMBER() OVER(ORDER BY number) AS RowNumber
                    ,LTRIM(RTRIM(SUBSTRING(ListValue, number+1, CHARINDEX(@SplitOn, ListValue, number+1)-number - 1))) AS ListValue
                FROM (
                         SELECT @SplitOn + @List + @SplitOn AS ListValue
                     ) AS InnerQuery
                    INNER JOIN Numbers n ON n.Number < LEN(InnerQuery.ListValue)
                WHERE SUBSTRING(ListValue, number, 1) = @SplitOn
        
        RETURN
        
        END /*Function FN_ListAllToNumTable*/
        

        我有其他版本不返回空行或空行,只返回项目而不是行号等。查看标题注释以了解如何将其用作 JOIN 的一部分,这很多比 where 子句更快。

        【讨论】:

        • +1 提出了很好的建议,但我会使用同时处理 BIGINT 的版本,然后数字表会增长很多!
        • 数字表只需要与最长的字符串一样大。如果您计划在拆分字符串中包含超过 8000 个字符,只需添加更多行。 Number 表中的 int Number 可以达到 2,147,483,647,我几乎不怀疑你会有这么长的字符串。
        【解决方案4】:

        CLR 解决方案没有给我很好的性能,所以我将使用递归查询。所以这里是我将使用的 SP 的定义(主要基于 Erland Sommarskogs 的例子):

        CREATE FUNCTION [dbo].[priudf_CSVtoIntTable]
        (
          @CSV VARCHAR(MAX),
          @Delimiter CHAR(1) = ','
        )
        RETURNS 
        @Result TABLE 
        (
            [Value] INT
        )
        AS
        BEGIN
        
          -- Remove space, tab, linefeed, carrier return
          SET @CSV = REPLACE(@CSV, ' ', '');
          SET @CSV = REPLACE(@CSV, CHAR(9), '');
          SET @CSV = REPLACE(@CSV, CHAR(10), '');
          SET @CSV = REPLACE(@CSV, CHAR(13), '');
        
          WITH csvtbl(start, stop) AS 
          (
            SELECT  start = CONVERT(BIGINT, 1),
                    stop = CHARINDEX(@Delimiter, @CSV + @Delimiter)
            UNION ALL
            SELECT  start = stop + 1,
                    stop = CHARINDEX(@Delimiter, @CSV + @Delimiter, stop + 1)
            FROM csvtbl
            WHERE stop > 0
          )
          INSERT INTO @Result
          SELECT CAST(SUBSTRING(@CSV, start, CASE WHEN stop > 0 THEN stop - start ELSE 0 END) AS INT) AS [Value]
          FROM   csvtbl
          WHERE  stop > 0
          OPTION (MAXRECURSION 1000)
        
          RETURN 
        END
        

        【讨论】:

          【解决方案5】:

          感谢您的意见,我不得不承认我在开始工作之前做了一些糟糕的研究。我发现Erland Sommarskog在他的网页上写了很多这个问题,在你的回复和阅读他的页面后,我决定我会尝试做一个CLR来解决这个问题。

          我尝试了递归查询,结果很好,但我还是会尝试 CLR 函数。

          【讨论】:

          • 来自上面关于 CLR 性能的网页: ...当他们执行多进程测试时,CLR 方法的扩展性比其他方法差很多。事实上,它的扩展性非常糟糕,以至于他们放弃了它,转而采用其他方法。
          • 我没有尝试过 CLR 拆分,但我敢打赌它会比我回答的函数慢很多。
          猜你喜欢
          • 2018-10-27
          • 1970-01-01
          • 2010-11-07
          • 2011-06-14
          • 1970-01-01
          • 2011-01-10
          • 2021-03-23
          • 1970-01-01
          相关资源
          最近更新 更多