从SQL Server中的VARCHAR中删除非数字字符的最快方法

时间:2008-09-19 22:42:42

标签: sql sql-server performance optimization

我正在编写一个导入实用程序,它使用电话号码作为导入中的唯一键。

我需要检查我的数据库中是否存在电话号码。问题是数据库中的电话号码可能包含破折号和括号以及其他可能的内容。我写了一个函数来删除这些东西,问题是它是并且我的数据库中有数千条记录和一次导入数千条记录,这个过程可能会慢得令人无法接受。我已经将电话号码列作为索引。

我尝试使用这篇文章中的脚本:
T-SQL trim &nbsp (and other non-alphanumeric characters)

但这并没有加快速度。

是否有更快的方法来删除非数字字符?当需要比较10,000到100,000条记录时,可以表现良好的东西。

无论做什么都需要执行快速

更新
考虑到人们的回应,我想在运行导入实用程序之前我必须清理字段。

要回答我正在编写导入实用程序的问题,它是一个C#应用程序。我现在正在将BIGINT与BIGINT进行比较,不需要改变数据库数据,而且我仍然使用一小组数据(大约2000条记录)来获得性能。

比较BIGINT和BIGINT会减慢速度吗?

我尽可能地优化了我的应用程序的代码端(删除了正则表达式,删除了不必要的数据库调用)。虽然我不能再将SQL作为问题的根源来隔离,但我仍然觉得它是。

15 个答案:

答案 0 :(得分:103)

我用T-SQL代码和PATINDEX看到了这个解决方案。我喜欢它: - )

CREATE Function [fnRemoveNonNumericCharacters](@strText VARCHAR(1000))
RETURNS VARCHAR(1000)
AS
BEGIN
    WHILE PATINDEX('%[^0-9]%', @strText) > 0
    BEGIN
        SET @strText = STUFF(@strText, PATINDEX('%[^0-9]%', @strText), 1, '')
    END
    RETURN @strText
END

答案 1 :(得分:36)

replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(string,'a',''),'b',''),'c',''),'d',''),'e',''),'f',''),'g',''),'h',''),'i',''),'j',''),'k',''),'l',''),'m',''),'n',''),'o',''),'p',''),'q',''),'r',''),'s',''),'t',''),'u',''),'v',''),'w',''),'x',''),'y',''),'z',''),'A',''),'B',''),'C',''),'D',''),'E',''),'F',''),'G',''),'H',''),'I',''),'J',''),'K',''),'L',''),'M',''),'N',''),'O',''),'P',''),'Q',''),'R',''),'S',''),'T',''),'U',''),'V',''),'W',''),'X',''),'Y',''),'Z','')*1 AS string

:)

答案 2 :(得分:16)

我可能会误解,但是您有两组数据可以从一个数据库中删除数据库中的当前数据,然后在导入时删除一个新数据集。

为了更新现有记录,我只使用SQL,只需要发生一次。

但是,SQL并未针对此类操作进行优化,因为您说您正在编写导入实用程序,我会在导入实用程序本身的上下文中执行这些更新,而不是在SQL中执行。这将是更好的性能。你在写这个实用程序是什么?

另外,我可能完全误解了这个过程,所以如果偏离基础,我会道歉。

编辑:
对于初始更新,如果您使用的是SQL Server 2005,则可以尝试CLR功能。这是一个使用正则表达式的快速方法。不确定性能如何比较,我自己从未使用过这个,除了现在的快速测试。

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

public partial class UserDefinedFunctions  
{  
    [Microsoft.SqlServer.Server.SqlFunction]  
    public static SqlString StripNonNumeric(SqlString input)  
    {  
        Regex regEx = new Regex(@"\D");  
        return regEx.Replace(input.Value, "");  
    }  
};  

部署完成后,要更新,您可以使用:

UPDATE table SET phoneNumber = dbo.StripNonNumeric(phoneNumber)

答案 3 :(得分:16)

如果你不想创建一个函数,或者你只需​​要在T-SQL中进行一次内联调用,你可以尝试:

set @Phone = REPLACE(REPLACE(REPLACE(REPLACE(@Phone,'(',''),' ',''),'-',''),')','')

当然这特定于删除电话号码格式,而不是通用删除字符串函数中的所有特殊字符。

答案 4 :(得分:9)

简单功能:

CREATE FUNCTION [dbo].[RemoveAlphaCharacters](@InputString VARCHAR(1000))
RETURNS VARCHAR(1000)
AS
BEGIN
  WHILE PATINDEX('%[^0-9]%',@InputString)>0
        SET @InputString = STUFF(@InputString,PATINDEX('%[^0-9]%',@InputString),1,'')     
  RETURN @InputString
END

GO

答案 5 :(得分:6)

create function dbo.RemoveNonNumericChar(@str varchar(500))  
returns varchar(500)  
begin  
declare @startingIndex int  
set @startingIndex=0  
while 1=1  
begin  
    set @startingIndex= patindex('%[^0-9]%',@str)  
    if @startingIndex <> 0  
    begin  
        set @str = replace(@str,substring(@str,@startingIndex,1),'')  
    end  
    else    break;   
end  
return @str  
end

go  

select dbo.RemoveNonNumericChar('aisdfhoiqwei352345234@#$%^$@345345%^@#$^')  

答案 6 :(得分:1)

你可以在夜间进程中删除它们,将它们存储在一个单独的字段中,然后在运行该进程之前对更改的记录进行更新吗?

或者在插入/更新时,存储“数字”格式,以便稍后参考。触发器是一种简单的方法。

答案 7 :(得分:1)

与使用数字相比,使用varchars基本上是缓慢且低效的,原因显而易见。您在原始帖子中链接的功能确实会非常慢,因为它们遍历字符串中的每个字符以确定它是否为数字。为成千上万的记录做到这一点,这个过程肯定会很慢。这是正则表达式的完美工作,但它们在SQL Server中不是本机支持的。你可以使用CLR函数添加支持,但很难说如果没有尝试它会有多慢,我肯定期望它比循环遍历每个电话号码的每个字符要快得多!

一旦你在数据库中格式化了电话号码,使它们只是数字,你就可以在SQL中切换到一种数字类型,这样可以快速地与其他数字类型进行比较。您可能会发现,根据您的新数据进入的速度,在数据库端进行修剪和转换为数字的速度足够快,一旦您比较的格式正确,但如果可能,您会更好以.NET语言编写导入实用程序,在访问数据库之前处理这些格式问题。

无论哪种方式,你都会遇到关于可选格式化的大问题。即使你的号码保证只是北美的号码,有些人会把1号放在完全符合区号的电话号码前面,而其他人则不会,这将导致同一电话号码的多次录音。此外,根据您的数据代表什么,有些人将使用他们的家庭电话号码,可能有几个人住在那里,因此对其的唯一约束只允许每个家庭一个数据库成员。有些人会使用他们的工作号码并遇到同样的问题,有些人会或者不会包括会再次造成人为唯一性的扩展。

根据您的特定数据和用法,所有这些可能会影响您,也可能不会影响您,但请务必记住这一点!

答案 8 :(得分:1)

我首先尝试使用Scott的CLR函数,但添加WHERE子句以减少更新的记录数。

UPDATE table SET phoneNumber = dbo.StripNonNumeric(phoneNumber) 
WHERE phonenumber like '%[^0-9]%'

如果您知道绝大多数记录都有非数字字符,那么它可能无济于事。

答案 9 :(得分:1)

我知道游戏已经晚了,但这是我为T-SQL创建的一个快速删除非数字字符的函数。值得注意的是,我有一个模式“String”,我将字符串的实用函数放入...

CREATE FUNCTION String.ComparablePhone( @string nvarchar(32) ) RETURNS bigint AS
BEGIN
    DECLARE @out bigint;

-- 1. table of unique characters to be kept
    DECLARE @keepers table ( chr nchar(1) not null primary key );
    INSERT INTO @keepers ( chr ) VALUES (N'0'),(N'1'),(N'2'),(N'3'),(N'4'),(N'5'),(N'6'),(N'7'),(N'8'),(N'9');

-- 2. Identify the characters in the string to remove
    WITH found ( id, position ) AS
    (
        SELECT 
            ROW_NUMBER() OVER (ORDER BY (n1+n10) DESC), -- since we are using stuff, for the position to continue to be accurate, start from the greatest position and work towards the smallest
            (n1+n10)
        FROM 
            (SELECT 0 AS n1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS d1,
            (SELECT 0 AS n10 UNION SELECT 10 UNION SELECT 20 UNION SELECT 30) AS d10
        WHERE
            (n1+n10) BETWEEN 1 AND len(@string)
            AND substring(@string, (n1+n10), 1) NOT IN (SELECT chr FROM @keepers)
    )
-- 3. Use stuff to snuff out the identified characters
    SELECT 
        @string = stuff( @string, position, 1, '' )
    FROM 
        found
    ORDER BY
        id ASC; -- important to process the removals in order, see ROW_NUMBER() above

-- 4. Try and convert the results to a bigint   
    IF len(@string) = 0
        RETURN NULL; -- an empty string converts to 0

    RETURN convert(bigint,@string); 
END

然后用它来比较插入,就像这样;

INSERT INTO Contacts ( phone, first_name, last_name )
SELECT i.phone, i.first_name, i.last_name
FROM Imported AS i
LEFT JOIN Contacts AS c ON String.ComparablePhone(c.phone) = String.ComparablePhone(i.phone)
WHERE c.phone IS NULL -- Exclude those that already exist

答案 10 :(得分:0)

“虽然我不能再将SQL视为问题的根源,但我仍然觉得它是。”

启动SQL事件探查器并查看。获取生成的查询并检查其执行计划以确保正在使用索引。

答案 11 :(得分:0)

成千上万条记录中的数千条记录通常不是问题。我已经使用SSIS导入数百万条带有重复数据删除的记录。

我会清理数据库,首先删除非数字字符并将其保留。

答案 12 :(得分:0)

寻找一个超级简单的解决方案:

SUBSTRING([Phone], CHARINDEX('(', [Phone], 1)+1, 3)
       + SUBSTRING([Phone], CHARINDEX(')', [Phone], 1)+1, 3)
       + SUBSTRING([Phone], CHARINDEX('-', [Phone], 1)+1, 4) AS Phone

答案 13 :(得分:0)

我从性能角度使用内联函数,如下所示: 请注意像&#39; +&#39;,&#39; - &#39;等将不会被删除

CREATE FUNCTION [dbo].[UDF_RemoveNumericStringsFromString]
 (
 @str varchar(100)
 )
 RETURNS TABLE AS RETURN
 WITH Tally (n) as 
  (
  -- 100 rows
   SELECT TOP (Len(@Str)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
   FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
  )

  SELECT OutStr =  STUFF(
       (SELECT SUBSTRING(@Str, n,1) st
        FROM Tally
        WHERE ISNUMERIC(SUBSTRING(@Str, n,1)) = 1
        FOR XML PATH(''),type).value('.', 'varchar(100)'),1,0,'')
  GO

  /*Use it*/
  SELECT OutStr
  FROM dbo.UDF_RemoveNumericStringsFromString('fjkfhk759734977fwe9794t23')
  /*Result set
   759734977979423 */

您可以使用超过100个字符来定义它......

答案 14 :(得分:-1)

我建议对数据库中的电话号码强制执行严格的格式。我使用以下格式。 (假设美国电话号码)

数据库:5555555555x555

显示:(555)555-5555转555

输入:任何字符串中嵌入10位或更多位数。 (正则表达式替换将删除所有非数字字符)