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

时间:2020-03-06 14:28:08  来源:igfitidea点击:

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

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

我尝试使用这篇文章中的脚本:
T-SQL修剪&nbsp(和其他非字母数字字符)

但这并没有加快速度。

是否有删除非数字字符的更快方法?当必须比较10,000到100,000条记录时,某些性能会很好。

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

更新
鉴于人们的回应,我认为在运行导入实用程序之前,我必须清理字段。

为了回答我在其中编写导入实用程序的问题,它是一个Capp。现在,我正在将BIGINT与BIGINT进行比较,而无需更改数据库数据,而我仍然会因为很少的一组数据(大约2000条记录)而对性能造成冲击。

将BIGINT与BIGINT进行比较会降低速度吗?

我已尽我所能优化了应用程序的代码侧(删除了正则表达式,删除了不必要的数据库调用)。尽管我不能再将SQL隔离为问题的根源,但我仍然感觉是这样。

解决方案

我们可以在每晚的过程中将它们删除,将它们存储在一个单独的字段中,然后在运行该过程之前立即对已更改的记录进行更新吗?

或者在插入/更新中,存储"数字"格式,以供以后参考。触发器将是一种简单的方法。

我可能会误会,但是我们有两套数据要从数据库中当前数据的一组字符串中删除字符串,然后在每次导入时从一组新字符串中删除字符串。

对于更新现有记录,我只使用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)

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

数据库:5555555555x555

显示:(555)555-5555转555

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

出于明显的原因,与使用数字变量相比,使用varchars从根本上来说是缓慢且效率低下的。我们在原始文章中链接到的函数的确会非常慢,因为它们会遍历字符串中的每个字符以确定它是否为数字。对成千上万的记录执行此操作,过程一定很慢。对于正则表达式而言,这是一项完美的工作,但SQL Server本身不支持它们。我们可以使用CLR功能添加支持,但是如果不尝试它,很难说这将有多慢,但是我绝对希望它比遍历每个电话号码的每个字符快得多!

一旦将电话号码格式化为数据库中的电话号码(仅作为数字),就可以切换到SQL中的数字类型,这将产生与其他数字类型的快速比较。我们可能会发现,根据新数据的传入速度,一旦将要比较的内容正确格式化,就可以在数据库端进行修剪和转换为数字足够快,但是如果可能,我们会更好无需使用.NET语言编写导入实用程序,该实用程序可以在访问数据库之前解决这些格式问题。

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

所有这些可能会或者可能不会影响我们,具体取决于特定数据和使用情况,但请务必记住!

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

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

如果我们知道绝大多数记录都包含非数​​字字符,则可能没有帮助。

"尽管我不能再将SQL隔离为问题的根源,但我仍然感觉是这样。"

启动SQL事件探查器,看看。接受结果查询并检查其执行计划,以确保正在使用索引。

通常,成千上万的记录与成千上万的记录不成问题。我已经使用SSIS导入了数百万条具有重复数据删除功能的记录。

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

我知道游戏已经晚了,但是这是我为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

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%^@#$^')