【问题标题】:Apply a Mask to Format a String in SQL Server Query/View在 SQL Server 查询/视图中应用掩码来格式化字符串
【发布时间】:2011-04-02 13:33:11
【问题描述】:

有没有一种巧妙的方法可以将掩码应用于 SQL Server 查询中的字符串?

我有两张表,一张将电话号码存储为没有文字 0155567890 的 varchar 和一个电话类型,它具有该电话号码类型的掩码:(##) #### ####

返回字符串(对于合并文档)以便查询返回完全格式化的电话号码的最佳方法是什么:

(01) 5556 7890

【问题讨论】:

    标签: sql sql-server


    【解决方案1】:

    如评论中所述,如果在大量行中使用,我下面的原始答案将导致糟糕的性能。如果考虑性能,则首选i-one's answer

    我也需要这个,感谢 Sjuul 的伪代码,我能够创建一个函数来执行此操作。

    CREATE FUNCTION [dbo].[fx_FormatUsingMask] 
    (
        -- Add the parameters for the function here
        @input nvarchar(1000),
        @mask nvarchar(1000)
    )
    RETURNS nvarchar(1000)
    AS
    BEGIN
        -- Declare the return variable here
        DECLARE @result nvarchar(1000) = ''
        DECLARE @inputPos int = 1
        DECLARE @maskPos int = 1
        DECLARE @maskSign char(1) = ''
    
        WHILE @maskPos <= Len(@mask)
        BEGIN
            set @maskSign = substring(@mask, @maskPos, 1)
    
            IF @maskSign = '#'
            BEGIN
                set @result = @result + substring(@input, @inputPos, 1)
                set @inputPos += 1
                set @maskPos += 1
            END
            ELSE
            BEGIN
                set @result = @result + @maskSign
                set @maskPos += 1
            END
        END
        -- Return the result of the function
        RETURN @result
    
    END
    

    【讨论】:

    • 如果您将其应用于大量行,则在带有循环的过程代码中执行此操作将导致糟糕的性能。如果考虑性能,我建议使用具有基于集合的方法的表值函数,如i-one's answer
    【解决方案2】:

    以防万一有人需要表值函数。

    方法 1(参见 #2 以获得更快的版本)

    create function ftMaskPhone
    (
        @phone varchar(30),
        @mask varchar(50)
    )
    returns table as
    return
        with ci(n, c, nn) as (
            select
                1,
                case
                    when substring(@mask, 1, 1) = '#' then substring(@phone, 1, 1)
                    else substring(@mask, 1, 1)
                end,
                case when substring(@mask, 1, 1) = '#' then 1 else 0 end
            union all
            select
                n + 1,
                case
                    when substring(@mask, n + 1, 1) = '#' then substring(@phone, nn + 1, 1)
                    else substring(@mask, n + 1, 1)
                end,
                case when substring(@mask, n + 1, 1) = '#' then nn + 1 else nn end
            from ci where n < len(@mask))
        select (select c + '' from ci for xml path(''), type).value('text()[1]', 'varchar(50)') PhoneMasked
    GO
    

    然后将其应用为

    declare @mask varchar(50)
    set @mask = '(##) #### ####'
    
    select pm.PhoneMasked
    from Phones p
        outer apply ftMaskPhone(p.PhoneNum, @mask) pm
    

    方法 2

    出于历史目的,我将保留上述版本。但是,这个性能更好。

    CREATE FUNCTION dbo.ftMaskPhone
    (
        @phone varchar(30),
        @mask varchar(50)
    )
    RETURNS TABLE 
    WITH SCHEMABINDING
    AS
    RETURN
    (
        WITH v1(N) AS (
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
            UNION ALL
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
        ),
        v2(N) AS (SELECT 1 FROM v1 a, v1 b),
        v3(N) AS (SELECT TOP (ISNULL(LEN(@mask), 0)) ROW_NUMBER() OVER (ORDER BY @@SPID) FROM v2),
        v4(N, C) AS (
            SELECT N, ISNULL(SUBSTRING(@phone, CASE WHEN c.m = 1 THEN ROW_NUMBER() OVER (PARTITION BY c.m ORDER BY N) END, 1), SUBSTRING(@mask, v3.N, 1))
            FROM v3
                CROSS APPLY (SELECT CASE WHEN SUBSTRING(@mask, v3.N, 1) = '#' THEN 1 END m) c
        )
        SELECT MaskedValue = (
            SELECT c + ''
            FROM v4
            ORDER BY N
            FOR XML PATH(''), TYPE
        ).value('text()[1]', 'varchar(50)')
    );
    GO
    

    模式绑定,结合这是一个单语句表值函数,使这个版本有资格被查询优化器内联。如上例所示,使用CROSS APPLY 实现该函数,或者对于单个值,如下所示:

    SELECT *
    FROM dbo.ftMaskPhone('0012345678910', '### (###) ###-####')
    

    结果如下:

    MaskedValue
    001 (234) 567-8910

    【讨论】:

      【解决方案3】:

      正如@Sean 所说,SQL Server 2012 及更高版本支持FORMAT 函数,几乎可以满足您的需求,但需要注意以下几点:

      • 格式化需要一个数字,而不是VARCHAR。这可以通过使用CAST 来解决。

      • 提供的掩码 ((##) #### ####) 加上 CAST 将删除前导零,留下 (1) 5556 7890。您可以将掩码更新为(0#) #### ####。在你代表Australian phone number的肢体上,似乎前导0总是在那里:

        在澳大利亚境内,要访问来电者所在“地区”以外的固定电话“号码”(包括使用“移动”电话的来电者),首先需要拨打澳大利亚“中继接入代码”为 0 加上“区域”代码,后跟“本地”号码。因此,“完整的国家号码”(FNN)有十位数字:0x xxxx xxxx。

      但归根结底,我认为 SQL Server 不是处理数据表示/格式化的最佳位置(与日期一样,电话号码也是如此)。我建议使用 Google 的 libphonenumber 之类的东西来做这个客户端。将电话号码输入数据库后,您可以存储电话号码本身及其所属的国家/地区,然后您可以在显示电话号码时使用它们(或执行诸如拨打电话或检查有效性之类的操作)。

      【讨论】:

      • 同意,用户界面/表示层是执行表示层功能的地方。
      【解决方案4】:

      有内置的FORMAT 功能,几乎可以工作。不幸的是,它需要一个 int 作为第一个参数,所以它去掉了前导零:

      select format(0155567890 ,'(##) #### ####')
      
      (1) 5556 7890
      

      【讨论】:

        【解决方案5】:

        我想隐藏一些信息,所以我使用了RIGHT 函数。它仅显示右侧的前 4 个字符。

        CONCAT('xxx-xx-', RIGHT('03466045896', 4))
        

        上面的代码会显示“xxx-xx-5896

        【讨论】:

          【解决方案6】:

          如果您需要“屏蔽”,而不是用另一个隐藏实际值,然后“取消屏蔽”一个字符串,您可以尝试此功能,或扩展它。 :)

          https://stackoverflow.com/a/22023329/2175524

          【讨论】:

            【解决方案7】:

            这正是我想到的。我不知道这是否是最好的解决方案,但我认为它应该是可行的。

            创建一个名为 applyMask (orso) 的函数

            伪代码:

            WHILE currentPosition < Length(PhoneNr) AND safetyCounter < Length(Mask)
                IF currentSign = "#"
                    result += Mid(PhoneNr, currentPosition, 1)
                    currentPosition++
                ELSE
                    result += currentSign
                    safetyCounter++
                END
            END
            Return result
            

            【讨论】:

            • 如果您将其应用于大量行,则在带有循环的过程代码中执行此操作将导致糟糕的性能。如果考虑性能,我建议使用具有基于集合的方法的表值函数,如i-one's answer
            猜你喜欢
            • 2022-08-19
            • 2020-07-08
            • 2011-07-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-02-23
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多