SQL Server:我们如何使用不同的排序规则调用相同的UDF

时间:2014-11-11 16:42:32

标签: sql-server-2008-r2 user-defined-functions collation

我正在SQL Server 2008 R2中编写用户定义函数(UDF)。我想让UDF在不同的调用中使用不同的排序规则。这可能吗?是否可以根据用户输入动态更改排序规则?

从Intellisense,我在网上找不到什么,似乎必须在实际的T-SQL代码中指定排序规则,并且不能将其应用为变量或输入参数。有没有解决方法,特别是可以在UDF中使用的方法?

编辑:根据要求,这是我正在使用的UDF。我的目的是在有和没有" xxxx敏感"用于按照"相关性"

对查询结果进行排序的设置
/*
    T-SQL Implementation of Levenshtein Edit Distance.
*/
CREATE FUNCTION [dbo].[LevenshteinDistanceSQL] (
    @textA varchar(max)
    , @textB varchar(max)
)
RETURNS INT
AS
Begin

    DECLARE
        @aLength int
        , @bLength int
        , @prevSize int
        , @currentSize int
        , @previousDistances varbinary(max)
        , @currentDistances varbinary(max)
        , @result int

    SET @aLength = LEN(@textA)
    SET @bLength = LEN(@textB)
    SET @result = NULL

    -- Degenerate Cases

    -- want calling routine to specify the collation of this comparison
    If (@textA = @textB) SET @result = 0;

    If (@aLength = 0) SET @result = @bLength;
    If (@bLength = 0) SET @result = @aLength;

    If (@result IS NULL)
    Begin
        -- Set sizes of tables to length of largest input text, plus 1
        SET @prevSize = CASE WHEN @aLength > @bLength THEN @aLength ELSE @bLength END + 1;
        SET @currentSize = CASE WHEN @aLength > @bLength THEN @aLength ELSE @bLength END + 1;

        -- Initialize Previous distances
        DECLARE
            @i int
            , @j int
            , @cost int
            , @curDistLastCost int
            , @prevDistCurCost int
            , @prevDistLastCost int
            , @charA char(1)
            , @charB char(1)

        SET @i = 0
        SET @previousDistances = 0x -- empty varbinary
        While (@i < @prevSize)
        Begin
            SET @previousDistances = @previousDistances + CONVERT(binary(4), @i)
            SET @i = @i + 1;
        End

        -- Process @textA
        SET @i = 0
        While (@i < @aLength)
        Begin
            SET @currentDistances = CONVERT(binary(4), @i + 1);

            -- Process @textB
            SET @j = 0
            While (@j < @bLength)
            Begin
                SET @charA = SUBSTRING(@textA, @i + 1, 1)
                SET @charB = SUBSTRING(@textB, @j + 1, 1)

               -- want calling routine to specify the collation of this comparison
                SET @cost = CASE WHEN @charA = @charB THEN 0 ELSE 1 END;

                SET @curDistLastCost = CONVERT(int, SUBSTRING(@currentDistances, @j * 4 + 1, 4))
                SET @prevDistCurCost = CONVERT(int, SUBSTRING(@previousDistances, (@j + 1) * 4 + 1, 4))
                SET @prevDistLastCost = CONVERT(int, SUBSTRING(@previousDistances, @j * 4 + 1, 4))

                SET @currentDistances = @currentDistances + CONVERT(binary(4), CASE
                    WHEN @curDistLastCost < @prevDistCurCost AND @curDistLastCost < @prevDistLastCost THEN @curDistLastCost + 1
                    WHEN @prevDistCurCost < @curDistLastCost AND @prevDistCurCost < @prevDistLastCost THEN @prevDistCurCost + 1
                    ELSE @prevDistLastCost + @cost
                    END)

                SET @j = @j + 1
            End

            -- copy current distances to previous distances for next iteration
            SET @previousDistances = @currentDistances

            SET @i = @i + 1;
        End

        SET @result = CONVERT(int, SUBSTRING(@currentDistances, @bLength * 4 + 1, 4))
    End

    RETURN @result
End

这样称呼:

declare @text varchar(50)

set @text = 'c'

select
    *
from
    Skills as s
where
    s.Name like '%' + @text + '%'
order by
    dbo.LevenshteinDistance(@text, s.Name) -- COLLATE Latin1_General_100_CI_AI
    , dbo.LevenshteinDistance(@text, s.Name) -- COLLATE Latin1_General_100_CS_AS_KS_WS
    , s.Name

能够指定排序规则将允许我将最短的编辑距离(不敏感)排序到结果的顶部;在不敏感的编辑距离内,大小写,重音等匹配将位于顶部。

1 个答案:

答案 0 :(得分:1)

有两种方法可以实现这一点,并且都需要SQLCLR。如您所见,T-SQL不允许动态指定COLLATION,但在.Net中您可以:)。事实上,为此目的使用SQLCLR还有两个好处:

  1. 您可以更精细地控制进行比较的选项。查看CompareOptions枚举。例如,它有一个IgnoreSymbols选项:

      

    表示字符串比较必须忽略符号,例如空格字符,标点符号,货币符号,百分号,数学符号,符号等。

  2. SQLCLR UDF可以允许并行执行计划,而T-SQL UDF将始终禁止并行计划。为了允许并行计划,SQLCLR UDF需要是确定性的标记为IsDeterministic=true,并且不进行任何数据访问(不需要明确标记,因为它是默认值)。

  3. 那就是说,这里有一些补充说明:

    • 我见过的Levenshtein距离算法的所有实现都假定有序比较(即任何*_BIN归类)。但我想,你想要实现的目标可能会很有趣:)

    • 考虑到您要完成的任务,在采用SQLCLR方法时,您实际上并不需要传递任何整理信息。原因是每种特定语言(即校对)都控制了相等和排序的比较选项。但Levenshtein距离算法不涉及排序。这减少了仅需两种变化的需求:

      • 完全不敏感(至少对于IgnoreCaseIgnoreNonSpace,以及对IgnoreKanaTypeIgnoreSymbolsIgnoreWidth

        的选择
      • 完全敏感(即Ordinal

    所以,你的两个选择是:

    1. 在.Net中完成此操作:

      在C#(或VB.Net)中完全实现算法并使用CompareInfo.Compare (String, String, CompareOptions)方法。在这种情况下,您需要两个功能,一个是&#34;敏感&#34;和#34;不敏感&#34;或两者的单个函数,它为@SensitiveComparison接受输入参数(可能是SqlBoolean)。但是要比较的字符串的特定语言是无关紧要的,所以你在技术上支持第1天的所有语言:)。

    2. 将算法保留在T-SQL中。在这种情况下,您将使用SqlCommand的连接字符串执行"Context Connection = true;"。您的函数将采用SqlString的校对名称,并使用它来连接到进行比较的两个点中的算法。 [对于完全C#选项,我没有看到这个选项有多大好处,事实上它应该稍微慢一些,因为.Net代码对于这类事情应该更快。] < / p>

    3. 在任何一种情况下,都应该注意,这可以在标有PERMISSION_SET = SAFE的程序集中完成。这意味着,在实现此功能时存在绝对没有安全风险(如此处所述)。并且,对于仍然存在(错误)SQLCLR /启用CLR集成本身存在安全风险的人,我写了一篇文章(SQLCLR系列文章的一部分),从不同角度和大量的角度介绍了这个安全主题。自己测试的例子:Stairway to SQLCLR Level 3: Security (General and SAFE Assemblies)(需要免费注册)。