65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL Server 2005 中的电话号码:第二部分 – 格式化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.03/5 (9投票s)

2007年9月21日

CPOL

3分钟阅读

viewsIcon

94840

downloadIcon

1

在 SQL Server 中格式化电话号码。

引言

在我第一篇文章《SQL Server 2005 中的电话号码:第 1 部分 – 数据类型》中,我讨论了在 SQL Server 中存储简单电话号码的各种方法。直接显示 SQL Server 中存储的原始数据不适合人类阅读,因此必须进行某种形式的数据格式化。数据格式化通常在用户界面层进行,例如,通过 ASPX 页面或 WinForms 应用程序。通常,让 SQL Server 在视图中格式化数据以便传递到另一个文档(如可能难以正确格式化数字的报表)会很方便。在本文中,我将讨论几种可以在 SQL Server 上用于向用户呈现电话号码的格式化方法。

UDF

SQL Server 2000 引入了创建用户定义函数(UDF)的功能。使用自定义格式化函数来处理电话号码是 UDF 的一个绝佳示例。下面的代码可用于在 TSQL 中创建电话号码格式化函数。

-- =============================================
-- Author:    Bryan Kowalchuk
-- Create date:     Sept 14, 2007
-- Description:     Formats a telephone number 
--                  to North American Numbering Plan standard 
-- =============================================
CREATE FUNCTION [dbo].[FORMATPHONENUMBER] 
(
    @Number money
)
RETURNS varchar(25)
AS
BEGIN

    -- Declare the return variable here
    DECLARE @Formatted varchar(25)  -- Formatted number to return
    DECLARE @CharNum varchar(18)    -- Character type of phone number
    DECLARE @Extension int         -- Phone extesion
    DECLARE @Numerator bigint         -- Working number variable

    IF @Number IS NULL 
    BEGIN
        --Just return NULL if input string is NULL
        RETURN NULL
    END

    -- Just enough room, since max phone number
    -- digits is 14 + 4 for extension is 18

    -- Get rid of the decimal
    SET @Numerator = CAST(@Number * 10000 AS bigint)
    -- Cast to int to strip off leading zeros
    SET @Extension = CAST(RIGHT(@Numerator, 4) AS int)
    -- Strip off the extension
    SET @CharNum = CAST(LEFT(@Numerator , LEN(@Numerator) - 4) 
        AS varchar(18))

    IF LEN(@CharNum) = 10    -- Full phone number, return (905) 555-1212
      BEGIN
                
        SET @Formatted = '(' + LEFT(@CharNum, 3) + ') ' + 
            SUBSTRING(@CharNum,4,3) + '-' + RIGHT(@CharNum, 4)

        IF @Extension > 0    -- Add Extension
        BEGIN
            SET @Formatted = @Formatted +  ' ext '+ 
                             CAST(@Extension AS varchar(4))
        END

        RETURN @Formatted
      END

    IF LEN(@CharNum) = 7    -- No Area Code, return 555-1212
      BEGIN
        SET @Formatted = LEFT(@CharNum, 3) + '-' + RIGHT(@CharNum, 4)
        IF @Extension > 0    -- Add Extension
        BEGIN
            SET @Formatted = @Formatted +  ' ext '+ 
                             CAST(@Extension AS varchar(6))
        END

        RETURN @Formatted
      END

    IF LEN(@CharNum) = 11
    -- Full phone number with access code,
    -- return  1 (905) 555-1212  (19055551212)
      BEGIN
                
        SET @Formatted = LEFT(@CharNum, 1) + ' (' + SUBSTRING(@CharNum, 2, 3) + ') ' + 
                         SUBSTRING(@CharNum,4,3) + '-' + RIGHT(@CharNum, 4)

        IF @Extension > 0    -- Add Extension
        BEGIN
            SET @Formatted = @Formatted +  ' ext '+ CAST(@Extension AS varchar(4))
        END

        RETURN @Formatted
      END

    
    -- Last case, just return the number unformatted (unhandled format)
    SET @Formatted = @CharNum
    IF @Extension > 0    -- Just the Extension
      BEGIN
        SET @Formatted = @Formatted +  ' ext '+ CAST(@Extension AS varchar(4))
        RETURN 'ext '+ CAST(@Extension AS varchar(4))
        
      END

    RETURN @Formatted

END

CLR 版本

SQL Server 2005 允许使用您选择的 .NET 编程语言编写自己的 DLL,因此我也用 C# 编写了等效的函数。要在 Visual Studio 2005 中执行此操作,请启动一个新的 SQL Server 项目,并添加一个新的用户定义函数。将此代码粘贴到其中。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction(
               IsDeterministic=true, IsPrecise=true)]
    public static SqlString FormatNAPhoneNumber(SqlMoney Number)
    {
        // Return Null if number is null
        if (Number.IsNull)
            return SqlString.Null;

        string phoneNumber = Number.ToString();
        string [] phone = phoneNumber.Split(new Char [] {'.'});
        string charnum = phoneNumber;   //Default to unformatted number
  
        int extension = int.Parse(phone[1]);
        
        switch (phone[0].Length)
        {
            case 10:
            //Format standard North American phone number 
            //'(416) 555-1212' or '(415) 555-1212 ext1234'
                {

                    if (extension > 0)
                    {
                        charnum = string.Format("({0}) {1}-{2} ext{3}",
                                phone[0].Substring(0, 3),
                                phone[0].Substring(3, 3),
                                phone[0].Substring(6),
                                extension.ToString());

        //     charnum = Regex.Replace(phone[0], @"(\d{3})(\d{3})(\d{4})", 
        //               "($1) $2-$3");    //Test Regex performance
        //     charnum = "(" + phone[0].Substring(0, 3) + ") " + 
        //               phone[0].Substring(3, 3) +  //Test standard string concat
        //     phone[0].Substring(6) + " ext" + extension.ToString();

                        return new SqlString(charnum);
                    }
                    else
                    {
                        charnum = string.Format("({0}) {1}-{2}",
                                phone[0].Substring(0, 3),
                                phone[0].Substring(3, 3),
                                phone[0].Substring(6));

        //    charnum = Regex.Replace(phone[0], @"(\d{3})(\d{3})(\d{4})", 
        //              "($1) $2-$3");
        //    charnum = "(" + phone[0].Substring(0, 3) + ") " + 
        //              phone[0].Substring(3, 3) + phone[0].Substring(6);
                        return new SqlString(charnum);
                        
                    }
                    break;
                }
            case 7:  // Format without the area code '555-1212' or '555-1212 ext1234'
                { 
                    if (extension > 0)
                    {
                        charnum = string.Format("{0}-{1} ext{2}",
                                phone[0].Substring(0, 3),
                                phone[0].Substring(3),
                                extension.ToString());
                        return new SqlString(charnum);
                        break;
                    }
                    else
                    {
                        charnum = string.Format("{0}-{1}",
                                phone[0].Substring(0, 3),
                                phone[0].Substring(3));
                        return new SqlString(charnum);
                        break;
                    }
                }
            case 11:
            // Format with dialing prefix '1 (416) 555-1212' or '1 (416) 555-1212 ext1234'
                {
                    //      return new SqlString(phone[0] + " and " + phone[1]);       
                    if (extension > 0)
                    {
                        charnum = string.Format("{0} ({1}) {2}-{3} ext{4}",
                            phone[0].Substring(0, 1),
                            phone[0].Substring(1, 3),
                                phone[0].Substring(4, 3),
                                phone[0].Substring(7),
                                extension.ToString());
                        return new SqlString(charnum);
                    }
                    else
                    {
                        charnum = string.Format("{0} ({1}) {2}-{3}",
                            phone[0].Substring(0, 1),
                            phone[0].Substring(1, 3),
                                phone[0].Substring(4, 3),
                                phone[0].Substring(7));
                        return new SqlString(charnum);
                    }
                }
            default:    //Just return the number as a string, no formatting
                {
                    return new SqlString(charnum); 
                    break;
                }
            }
        }
}

我对一个包含电话号码数据类型的小型表进行了快速基准测试,以查看其对性能的影响。该表包含 580 行数据,并执行了 1000 次 SQL SELECT 命令,总共调用了 580,000 次函数。执行 SELECT 命令时未将数据集返回到客户端,以最大程度地减少网络时间。执行时间以毫秒为单位。您的结果可能会有所不同,但请将这些数字作为不同方法的相对比较。

方法

时间

函数开销

不进行电话号码格式化

1950 毫秒

TSQL FORMATPHONENUMBER

7450 毫秒

5500 毫秒

使用 String.Format 的 C# 函数

4750 毫秒

2800 毫秒

使用 RegEx 的 C# 函数

7187 毫秒

5237 毫秒

令人惊讶的结果!

令我惊讶的是,.NET 函数的运行速度是原生 TSQL 函数的两倍!我最初的猜测是,原生 TSQL 函数肯定会运行得更快,因为它们消除了 .NET 引擎和接口的开销。看来微软在 .NET 集成方面做得功课很足。这是另一个很好的例子,说明测试结果常常与您的预期相反。

C# 版本还注释掉了各种行,如果您选择使用 RegEx 库或简单的字符串连接运算符来格式化电话号码。处理国际电话号码的一种方法是将 RegEx 模式和求值器字符串存储在按国家/地区键入的一个表中。然后,可以将此表与电话号码关联,并将相应的格式化代码传递给函数。我没有在设计中深入研究这一点,但我尝试使用 RegEx 方法进行格式化,以测试其性能与使用 String.Format 方法相比。正如您所见,它的性能与 TSQL 版本差不多,因此 RegEx 对象的开销相当可观。

结论

使用 TSQL 编写用户定义函数可能会很麻烦,特别是当它需要大量的字符串操作或复杂的逻辑时。使用 .NET CLR 集成,您可以轻松地利用 .NET 库的全部功能,并且性能可能比 TSQL 更好。

在我第一篇文章中提到,细节决定成败。通过关注正确的ю设计并进行一些测试,我们将存储电话号码的数据占用空间减少了一半,并将格式化号码的 CPU 需求也减少了一半。像这样的微小改变,对于您的应用程序的可扩展性来说,可能产生巨大的影响。

© . All rights reserved.