SQL Replace函数中的正则表达式模式?


78
SELECT REPLACE('<strong>100</strong><b>.00 GB', '%^(^-?\d*\.{0,1}\d+$)%', '');

我想用上面的正则表达式替换数字两部分之间的任何标记,但是似乎不起作用。我不确定是不是正则表达式语法错误,因为我尝试了一种更简单的方法(例如'%[^0-9]%'只是为了进行测试),但是它也不起作用。有人知道我该怎么做到吗?


3
您可能需要重新查看答案。
Mukus 2014年

1
您希望最终结果是什么?您期望100.00还是100.00 GB?还有其他一些格式化数字的示例,它们仅位于小数点左侧的部分附近,而不适合标记的样式吗?标记可以是整数100<i>.00</i> GB吗?右边总是有2个字符的货币代码吗?
所罗门·鲁兹基

@srutzky我想要带小数点的数字(如果有的话),不是所有值都有它们,而且由于生成了第三方html生成器,因此实际上没有任何模式。有时货币在数字的前面,有时在数字的后面,有时是符号-$,有时是代码-USD,并且没有空格-.etc等。简直是非常垃圾的数据
2014年

Answers:


61

您可以使用PATINDEX 查找模式(字符串)出现的第一个索引。然后使用STUFF将另一个字符串填充到匹配的模式(字符串)中。

遍历每一行。用您想要的替换每个非法字符。在您的情况下,将非数字替换为空白。内循环是当前循环中是否有多个非法字符。

DECLARE @counter int

SET @counter = 0

WHILE(@counter < (SELECT MAX(ID_COLUMN) FROM Table))
BEGIN  

    WHILE 1 = 1
    BEGIN
        DECLARE @RetVal varchar(50)

        SET @RetVal =  (SELECT Column = STUFF(Column, PATINDEX('%[^0-9.]%', Column),1, '')
        FROM Table
        WHERE ID_COLUMN = @counter)

        IF(@RetVal IS NOT NULL)       
          UPDATE Table SET
          Column = @RetVal
          WHERE ID_COLUMN = @counter
        ELSE
            break
    END

    SET @counter = @counter + 1
END

注意:这很慢!拥有varchar列可能会产生影响。因此,使用LTRIM RTRIM可能会有所帮助。无论如何,它很慢。

功劳归于这个StackOverFlow答案。

编辑信用也去@srutzky

编辑(通过@Tmdean)而不是一次执行一行,此答案可以适应于基于更多集合的解决方案。它仍然会迭代单行中非数字字符的最大数量,因此它不是理想的,但我认为在大多数情况下应该可以接受。

WHILE 1 = 1 BEGIN
    WITH q AS
        (SELECT ID_Column, PATINDEX('%[^0-9.]%', Column) AS n
        FROM Table)
    UPDATE Table
    SET Column = STUFF(Column, q.n, 1, '')
    FROM q
    WHERE Table.ID_Column = q.ID_Column AND q.n != 0;

    IF @@ROWCOUNT = 0 BREAK;
END;

如果您在表中保留一个指示该字段是否已清理的位列,还可以大大提高效率。(在我的示例中,NULL表示“未知”,并且应为默认列。)

DECLARE @done bit = 0;
WHILE @done = 0 BEGIN
    WITH q AS
        (SELECT ID_Column, PATINDEX('%[^0-9.]%', Column) AS n
        FROM Table
        WHERE COALESCE(Scrubbed_Column, 0) = 0)
    UPDATE Table
    SET Column = STUFF(Column, q.n, 1, ''),
        Scrubbed_Column = 0
    FROM q
    WHERE Table.ID_Column = q.ID_Column AND q.n != 0;

    IF @@ROWCOUNT = 0 SET @done = 1;

    -- if Scrubbed_Column is still NULL, then the PATINDEX
    -- must have given 0
    UPDATE table
    SET Scrubbed_Column = CASE
        WHEN Scrubbed_Column IS NULL THEN 1
        ELSE NULLIF(Scrubbed_Column, 0)
    END;
END;

如果您不想更改架构,可以轻松地将中间结果存储在表值变量中,该变量最终将应用于实际表。


2
为了使该解决方案有效,至少您需要在PATINDEX模式中添加一个句点;应该是:[^0-9.]。如果不是,则将小数点去掉,然后将其100.00变成10000
Solomon Rutzky 2014年

@srutzky ok添加了'。
Mukus 2014年

+1是很费力的,但是(正如您还指出的那样)这会使报告运行太长,它们的运行速度很慢……但是对于较小的数据,这是一个很好的解决方案!
2014年

1
我只是从事与此类似的工作,所以我将使用更快的解决方案来更新答案。它仍然不是理想的,但是在大多数情况下性能应该可以接受。
Tmdean 2015年

@Tmdean:感谢您对此做出的贡献,下次遇到类似问题时,请尝试一下。
JanT

23

一般而言,SQL Server不支持正则表达式,因此您不能在本机T-SQL代码中使用它们。

您可以编写CLR函数来做到这一点。例如,请参见此处


1
好吧,那似乎是唯一的选择...谢谢
2014年

21

与其通过唯一位置剥离找到的角色,不如使用它Replace(Column, BadFoundCharacter, '')可能会更快。另外,这不仅替换了在每一列中找到的一个坏字符,还将替换所有找到的坏字符。

WHILE 1 = 1 BEGIN
    UPDATE dbo.YourTable
    SET Column = Replace(Column, Substring(Column, PatIndex('%[^0-9.-]%', Column), 1), '')
    WHERE Column LIKE '%[^0-9.-]%'
    If @@RowCount = 0 BREAK;
END;

我坚信这将比接受的答案更好,即使仅仅是因为它执行较少的操作。还有其他方法可能也更快,但是我现在没有时间探索这些方法。


看起来很有趣,我现在没有时间尝试,但有空就可以尝试。干杯
JanT '16

4
这帮助我解决了一个不相关的问题。我用你Replace(Column, Substring(Column, PatIndex('%[^0-9.-]%', Column), 1), '')的选择查询。那谢谢啦!
jyoseph

1
@jyoseph太好了!请注意,这只会删除特定坏字符的所有实例,并且如果坏字符集大于一个,则必须重复运行...
ErikE 2016年

@ErikE感谢您的注意!我用它来查询具有电话号码的列(将模式略微修改为%[^ 0-9]%),以便去除任何非数字的内容。因此,用户可以查询333-1234,并且它与输入为3331234的电话号码匹配。如果我正确理解,您是说在电话号码为(333)-333-1234的情况下,它只会删除第一个?“(”我必须测试多一点。
jyoseph

正确。您可以安装CLR模块。或者理想情况下,只需在程序代码中执行即可。
ErikE

4

我偶然发现了这篇文章,寻找了其他东西,但以为我会提到我使用的解决方案效率更高-与基于集合的查询一起使用时,实际上应该是任何函数的默认实现-使用交叉应用表功能。似乎该主题仍在进行中,因此希望对某人有用。

到目前为止,基于运行递归基于查询集或标量函数的一些答案的示例运行时示例,基于1m行测试集从随机newid中删除字符,WHILE循环示例的范围为34s至2m05s,范围为1m3s至{功能示例。

使用具有交叉应用功能的表功能可以在10秒钟内达到相同的目标。您可能需要对其进行调整以适应您的需要,例如处理的最大长度。

功能:

CREATE FUNCTION [dbo].[RemoveChars](@InputUnit VARCHAR(40))
RETURNS TABLE
AS
RETURN
    (
        WITH Numbers_prep(Number) 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
            )
        ,Numbers(Number) AS
            (
                SELECT TOP (ISNULL(LEN(@InputUnit),0))
                    row_number() OVER (ORDER BY (SELECT NULL))
                FROM Numbers_prep a
                    CROSS JOIN Numbers_prep b
            )
        SELECT
            OutputUnit
        FROM
            (
                SELECT
                    substring(@InputUnit,Number,1)
                FROM  Numbers
                WHERE substring(@InputUnit,Number,1) like '%[0-9]%'
                ORDER BY Number
                FOR XML PATH('')
            ) Sub(OutputUnit)
    )

用法:

UPDATE t
SET column = o.OutputUnit
FROM ##t t
CROSS APPLY [dbo].[RemoveChars](t.column) o

4

这是我根据先前的答案编写的用于完成此功能的函数。

CREATE FUNCTION dbo.RepetitiveReplace
(
    @P_String VARCHAR(MAX),
    @P_Pattern VARCHAR(MAX),
    @P_ReplaceString VARCHAR(MAX),
    @P_ReplaceLength INT = 1
)
RETURNS VARCHAR(MAX)
BEGIN
    DECLARE @Index INT;

    -- Get starting point of pattern
    SET @Index = PATINDEX(@P_Pattern, @P_String);

    while @Index > 0
    begin
        --replace matching charactger at index
        SET @P_String = STUFF(@P_String, PATINDEX(@P_Pattern, @P_String), @P_ReplaceLength, @P_ReplaceString);
        SET @Index = PATINDEX(@P_Pattern, @P_String);
    end

    RETURN @P_String;
END;

要旨

编辑:

最初,我在这里有一个递归函数,该函数在sql server中不能很好地发挥作用,因为它具有32个嵌套级别限制,每次尝试用该函数进行32次以上替换时,都会导致类似以下的错误。与其尝试进行服务器级别的更改以允许更多的嵌套(这可能是危险的,如永不结束循环),倒不如切换为while循环更有意义。

超出最大存储过程,函数,触发器或视图嵌套级别(限制32)。


2

如果要重用,将解决方案包装在SQL函数中可能会很有用。我什至在单元级别上执行此操作,因此将其作为不同的答案:

CREATE FUNCTION [dbo].[fnReplaceInvalidChars] (@string VARCHAR(300))
RETURNS VARCHAR(300)
BEGIN
    DECLARE @str VARCHAR(300) = @string;
    DECLARE @Pattern VARCHAR (20) = '%[^a-zA-Z0-9]%';
    DECLARE @Len INT;
    SELECT @Len = LEN(@String); 
    WHILE @Len > 0 
    BEGIN
        SET @Len = @Len - 1;
        IF (PATINDEX(@Pattern,@str) > 0)
            BEGIN
                SELECT @str = STUFF(@str, PATINDEX(@Pattern,@str),1,'');    
            END
        ELSE
        BEGIN
            BREAK;
        END
    END     
    RETURN @str
END

2

我创建了此函数来清理在时间字段中包含非数字字符的字符串。他们未添加分钟数时,时间中包含问号,类似于20:??。函数循环遍历每个字符并替换?0:

 CREATE FUNCTION [dbo].[CleanTime]
(
    -- Add the parameters for the function here
    @intime nvarchar(10) 
)
RETURNS nvarchar(5)
AS
BEGIN
    -- Declare the return variable here
    DECLARE @ResultVar nvarchar(5)
    DECLARE @char char(1)
    -- Add the T-SQL statements to compute the return value here
    DECLARE @i int = 1
    WHILE @i <= LEN(@intime)
    BEGIN
    SELECT @char =  CASE WHEN substring(@intime,@i,1) like '%[0-9:]%' THEN substring(@intime,@i,1) ELSE '0' END
    SELECT @ResultVar = concat(@ResultVar,@char)   
    set @i  = @i + 1       
    END;
    -- Return the result of the function
    RETURN @ResultVar

END

1

如果仅针对进入存储过程的参数执行此操作,则可以使用以下命令:

declare @badIndex int
set @badIndex = PatIndex('%[^0-9]%', @Param)
while @badIndex > 0
    set @Param = Replace(@Param, Substring(@Param, @badIndex, 1), '')
    set @badIndex = PatIndex('%[^0-9]%', @Param)

0

我认为一种更简单,更快速的方法是对字母的每个字符进行迭代:

DECLARE @i int
SET @i = 0

WHILE(@i < 256)
BEGIN  

    IF char(@i) NOT IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.')      

      UPDATE Table SET Column = replace(Column, char(@i), '')

    SET @i = @i + 1

END

1
请不要在生产中使用类似这样的东西。您要执行245个没有where子句的更新。它有效,但远非有效的方法。一个更好的主意是遍历我们要删除的字符,而不是字母上所有可用的字符。但是,即使那样也可以改进为更好的东西。
安德森·席尔瓦
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.