如何在SQL中替换多个字符?

时间:2009-10-16 19:38:23

标签: sql sql-server sql-server-2005 sql-function

这是基于类似的问题How to Replace Multiple Characters in Access SQL?

我写了这个,因为sql server 2005似乎对where子句中的19个替换的replace()函数有限制。

我有以下任务:需要对列执行匹配,并使用replace()函数提高匹配剥离多个不需要的字符的可能性

DECLARE @es NVarChar(1) SET @es = ''
DECLARE @p0 NVarChar(1) SET @p0 = '!'
DECLARE @p1 NVarChar(1) SET @p1 = '@'
---etc...

SELECT *
FROM t1,t2 
WHERE  REPLACE(REPLACE(t1.stringkey,@p0, @es), @p1, @es) 
     = REPLACE(REPLACE(t2.stringkey,@p0, @es), @p1, @es)    
---etc 

如果where子句中有> 19 REPLACE(),则它不起作用。所以我想出的解决方案是在这个例子中创建一个名为 trimChars 的sql函数(原谅他们从@ 22开始

CREATE FUNCTION [trimChars] (
   @string varchar(max)
) 

RETURNS varchar(max) 
AS
BEGIN

DECLARE @es NVarChar(1) SET @es = ''
DECLARE @p22 NVarChar(1) SET @p22 = '^'
DECLARE @p23 NVarChar(1) SET @p23 = '&'
DECLARE @p24 NVarChar(1) SET @p24 = '*'
DECLARE @p25 NVarChar(1) SET @p25 = '('
DECLARE @p26 NVarChar(1) SET @p26 = '_'
DECLARE @p27 NVarChar(1) SET @p27 = ')'
DECLARE @p28 NVarChar(1) SET @p28 = '`'
DECLARE @p29 NVarChar(1) SET @p29 = '~'
DECLARE @p30 NVarChar(1) SET @p30 = '{'

DECLARE @p31 NVarChar(1) SET @p31 = '}'
DECLARE @p32 NVarChar(1) SET @p32 = ' '
DECLARE @p33 NVarChar(1) SET @p33 = '['
DECLARE @p34 NVarChar(1) SET @p34 = '?'
DECLARE @p35 NVarChar(1) SET @p35 = ']'
DECLARE @p36 NVarChar(1) SET @p36 = '\'
DECLARE @p37 NVarChar(1) SET @p37 = '|'
DECLARE @p38 NVarChar(1) SET @p38 = '<'
DECLARE @p39 NVarChar(1) SET @p39 = '>'
DECLARE @p40 NVarChar(1) SET @p40 = '@'
DECLARE @p41 NVarChar(1) SET @p41 = '-'

return   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       @string, @p22, @es), @p23, @es), @p24, @es), @p25, @es), @p26, @es), @p27, @es), @p28, @es), @p29, @es), @p30, @es), @p31, @es), @p32, @es), @p33, @es), @p34, @es), @p35, @es), @p36, @es), @p37, @es), @p38, @es), @p39, @es), @p40, @es), @p41, @es)
END 

除了其他替换字符串

之外,还可以使用它
SELECT *
FROM t1,t2 
WHERE  trimChars(REPLACE(REPLACE(t1.stringkey,@p0, @es), @p1, @es) 
         = REPLACE(REPLACE(t2.stringkey,@p0, @es), @p1, @es))   

我创建了一些更多的函数来做类似的替换,如 trimChars(trimMoreChars(

SELECT *
FROM t1,t2 
WHERE  trimChars(trimMoreChars(REPLACE(REPLACE(t1.stringkey,@p0, @es), @p1, @es) 
         = REPLACE(REPLACE(t2.stringkey,@p0, @es), @p1, @es)))

有人可以在性能方面为我提供更好的解决方案,也可以更清洁实施吗?

11 个答案:

答案 0 :(得分:47)

SQL中一个有用的技巧是能够使用@var = function(...)来分配值。如果您的记录集中有多个记录,则var会多次分配副作用:

declare @badStrings table (item varchar(50))

INSERT INTO @badStrings(item)
SELECT '>' UNION ALL
SELECT '<' UNION ALL
SELECT '(' UNION ALL
SELECT ')' UNION ALL
SELECT '!' UNION ALL
SELECT '?' UNION ALL
SELECT '@'

declare @testString varchar(100), @newString varchar(100)

set @teststring = 'Juliet ro><0zs my s0x()rz!!?!one!@!@!@!'
set @newString = @testString

SELECT @newString = Replace(@newString, item, '') FROM @badStrings

select @newString -- returns 'Juliet ro0zs my s0xrzone'

答案 1 :(得分:22)

我会认真考虑making a CLR UDF instead并使用正则表达式(字符串和模式都可以作为参数传入)来完成搜索并替换一系列字符。它应该轻松胜过这个SQL UDF。

答案 2 :(得分:17)

我非常喜欢@ Juliett的解决方案!我只想使用CTE来获取所有无效字符:

DECLARE @badStrings VARCHAR(100)
DECLARE @teststring VARCHAR(100)

SET @badStrings = '><()!?@'
SET @teststring = 'Juliet ro><0zs my s0x()rz!!?!one!@!@!@!'

;WITH CTE AS
(
  SELECT SUBSTRING(@badStrings, 1, 1) AS [String], 1 AS [Start], 1 AS [Counter]
  UNION ALL
  SELECT SUBSTRING(@badStrings, [Start] + 1, 1) AS [String], [Start] + 1, [Counter] + 1 
  FROM CTE 
  WHERE [Counter] < LEN(@badStrings)
)

SELECT @teststring = REPLACE(@teststring, CTE.[String], '') FROM CTE

SELECT @teststring
  

Juliet ro0zs my s0xrzone

答案 3 :(得分:3)

我建议你创建一个标量用户定义函数。这是一个例子(提前抱歉,因为变量名是西班牙语):

CREATE FUNCTION [dbo].[Udf_ReplaceChars] (
  @cadena VARCHAR(500),  -- String to manipulate
  @caracteresElim VARCHAR(100),  -- String of characters to be replaced
  @caracteresReem VARCHAR(100)   -- String of characters for replacement
) 
RETURNS VARCHAR(500)
AS
BEGIN
  DECLARE @cadenaFinal VARCHAR(500), @longCad INT, @pos INT, @caracter CHAR(1), @posCarER INT;
  SELECT
    @cadenaFinal = '',
    @longCad = LEN(@cadena),
    @pos = 1;

  IF LEN(@caracteresElim)<>LEN(@caracteresReem)
    BEGIN
      RETURN NULL;
    END

  WHILE @pos <= @longCad
    BEGIN
      SELECT
        @caracter = SUBSTRING(@cadena,@pos,1),
        @pos = @pos + 1,
        @posCarER = CHARINDEX(@caracter,@caracteresElim);

      IF @posCarER <= 0
        BEGIN
          SET @cadenaFinal = @cadenaFinal + @caracter;
        END
      ELSE
        BEGIN
          SET @cadenaFinal = @cadenaFinal + SUBSTRING(@caracteresReem,@posCarER,1)
        END
    END

  RETURN @cadenaFinal;
END

以下是使用此功能的示例:

SELECT dbo.Udf_ReplaceChars('This is a test.','sat','Z47');

结果是: 7hiZ iZ 4 7eZ7。

如您所见,@caracteresElim参数的每个字符都替换为@caracteresReem参数中相同位置的字符。

答案 4 :(得分:1)

declare @testVal varchar(20)

set @testVal = '?t/es?ti/n*g 1*2?3*'

select @testVal = REPLACE(@testVal, item, '') from (select '?' item union select '*' union select '/') list

select @testVal;

答案 5 :(得分:1)

我有一个一次性的数据迁移问题,即源数据无法正确输出一些异常/技术字符以及CSV中普遍存在的多余逗号。

我们认为,对于每个此类字符,源提取应使用源系统和正在加载它们的SQL Server都可以识别的东西替换它们,否则将不在数据中。

但这确实意味着在各个表的各个列中将出现这些替换字符,而我将不得不替换它们。嵌套多个REPLACE函数使导入代码看起来很吓人,并且在错误判断括号的位置和数量时容易出错,因此我编写了以下函数。我知道它可以在不到一秒钟的时间内处理3,000行表中的一列,尽管我不确定它可以多快地扩展到数百万行表。

create function [dbo].[udf_ReplaceMultipleChars]
(
    @OriginalString nvarchar(4000)
  , @ReplaceTheseChars nvarchar(100)
  , @LengthOfReplacement int = 1
)
returns nvarchar(4000)
begin

    declare @RevisedString nvarchar(4000) = N'';
    declare @lengthofinput int =
            (
            select len(@OriginalString)
            );

with AllNumbers
as (select 1 as  Number
    union all
    select Number + 1
    from AllNumbers
    where Number < @lengthofinput)
select @RevisedString += case
                             when (charindex(substring(@OriginalString, Number, 1), @ReplaceTheseChars, 1) - 1) % 2
    = 0 then
                                 substring(
                                              @ReplaceTheseChars
                                            , charindex(
                                                           substring(@OriginalString, Number, 1)
                                                         , @ReplaceTheseChars
                                                         , 1
                                                       ) + 1
                                            , @LengthOfReplacement
                                          )
                             else
                                 substring(@OriginalString, Number, 1)
                         end
    from AllNumbers
    option (maxrecursion 4000);
    return (@RevisedString);
end;

通过提交要评估的字符串和要替换的字符(@OriginalString)以及成对字符字符串来工作,其中第一个字符将由第二个,第三个由第四个,第五个由第一个替换第六,依此类推(@ReplaceTheseChars)。

这是我需要替换的一串字符及其替换... [']“〜,{Ø}°$$ || ¼¦¼ª½¬½ ^¾#✓

即方括号的开头表示撇号,而方括号表示双引号。您会看到其中存在粗俗的分数以及度数和直径符号。

如果有人需要替换更长的字符串,则默认包含一个@LengthOfReplacement作为起点。我在项目中试用了该方法,但是替换单个char是主要功能。

案例陈述的条件很重要。它确保仅当在@ReplaceTheseChars变量中找到该字符时才替换该字符,并且必须在一个奇数编号的位置找到该字符(charindex结果的减号1确保未找到的任何内容都返回负模数值)。也就是说,如果在位置5找到代字号(〜),它将用逗号替换它;但是,如果在随后的运行中发现位置6是逗号,则不会用大括号({)替换它。

这可以用一个例子来最好地说明...

declare @ProductDescription nvarchar(20) = N'abc~def[¦][123';
select @ProductDescription
= dbo.udf_ReplaceMultipleChars(
                                  @ProductDescription
/* NB the doubling up of the apostrophe is necessary in the string but resolves to a single apostrophe when passed to the function */
                                ,'['']"~,{Ø}°$±|¼¦¼ª½¬½^¾#✓' 
                                , default
                              );
select @ProductDescription
 , dbo.udf_ReplaceMultipleChars(
                                   @ProductDescription
                                 ,'['']"~,{Ø}°$±|¼¦¼ª½¬½^¾#✓'
/* if you didn't know how to type those peculiar chars in then you can build a string like  this... '[' + nchar(0x0027) + ']"~,{' + nchar(0x00D8) + '}' + nchar(0x00B0) etc */
                                ,
                                 default
                               );

这将在第一次通过函数后和第二次返回时都返回值,如下所示...      abc,def'¼“'123 abc,def'¼”'123

表更新将只是

update a
set a.Col1 = udf.ReplaceMultipleChars(a.Col1,'~,]"',1)
from TestTable a

最后(我听到你说的是!),尽管我还没有访问翻译功能,但我相信这个功能可以很轻松地处理文档中显示的示例。 TRANSLATE功能演示是

SELECT TRANSLATE('2*[3+4]/{7-2}', '[]{}', '()()');

它返回2 *(3 + 4)/(7-2),尽管我知道它可能不适用于2 * [3 + 4] / [7-2]!

我的函数将按以下方式进行处理:列出要替换的每个字符,然后替换[[->(,{->(等。

select dbo.udf_ReplaceMultipleChars('2*[3+4]/{7-2}', '[({(])})', 1);

也将适用于

select dbo.udf_ReplaceMultipleChars('2*[3+4]/[7-2]', '[({(])})', 1);

我希望有人觉得它有用,如果您要对照较大的表测试其性能,请告诉我们一种或另一种方式!

答案 6 :(得分:0)

一种选择是使用数字/计数表通过基于伪集的查询来驱动迭代过程。

可以使用简单的字符映射表方法来证明char替换的一般概念:

create table charMap (srcChar char(1), replaceChar char(1))
insert charMap values ('a', 'z')
insert charMap values ('b', 'y')


create table testChar(srcChar char(1))
insert testChar values ('1')
insert testChar values ('a')
insert testChar values ('2')
insert testChar values ('b')

select 
coalesce(charMap.replaceChar, testChar.srcChar) as charData
from testChar left join charMap on testChar.srcChar = charMap.srcChar

然后你可以引入计数表方法来对字符串中的每个字符位置进行查找。

create table tally (i int)
declare @i int
set @i = 1
while @i <= 256 begin
    insert tally values (@i)
    set @i = @i + 1
end

create table testData (testString char(10))
insert testData values ('123a456')
insert testData values ('123ab456')
insert testData values ('123b456')

select
    i,
    SUBSTRING(testString, i, 1) as srcChar,
    coalesce(charMap.replaceChar, SUBSTRING(testString, i, 1)) as charData
from testData cross join tally
    left join charMap on SUBSTRING(testString, i, 1) = charMap.srcChar
where i <= LEN(testString)

答案 7 :(得分:0)

我不知道为什么Charles Bretana删除了他的答案,所以我将它作为CW答案添加回来,但是持久的计算列是一种非常好的方法来处理这些需要清理或转换数据的情况所有的时间,但需要保留原始垃圾。无论您决定如何清理数据,他的建议都是相关且适当的。

具体来说,在我当前的项目中,我有一个持久的计算列,它修剪了所有前导零(幸运的是,它在直接的T-SQL中可以很容易地处理)来自与前导零不一致存储的某些特定数字标识符。这存储在表中的持久计算列中,这些列需要它并编制索引,因为符合的标识符通常用在连接中。

答案 8 :(得分:0)

以下是步骤

  1. 创建CLR功能
  2. 请参阅以下代码:

    public partial class UserDefinedFunctions 
    {
    
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString Replace2(SqlString inputtext, SqlString filter,SqlString      replacewith)
    {
    
        string str = inputtext.ToString();
        try
        {
            string pattern = (string)filter;
            string replacement = (string)replacewith;
            Regex rgx = new Regex(pattern);
            string result = rgx.Replace(str, replacement);
            return (SqlString)result;
    
        }
        catch (Exception s)
        {
            return (SqlString)s.Message;
        }
    }
    }
    
    1. 部署您的CLR功能

    2. 现在测试

    3. 请参阅以下代码:

      create table dbo.test(dummydata varchar(255))
      Go
      INSERT INTO dbo.test values('P@ssw1rd'),('This 12is @test')
      Go
      Update dbo.test
      set dummydata=dbo.Replace2(dummydata,'[0-9@]','')
      
      select * from dbo.test
      dummydata, Psswrd, This is test booom!!!!!!!!!!!!!
      

答案 9 :(得分:0)

虽然有人询问有关SQL Server 2005的问题,但值得注意的是,从Sql Server 2017开始,可以使用新的TRANSLATE函数完成请求。

https://docs.microsoft.com/en-us/sql/t-sql/functions/translate-transact-sql

我希望这些信息有助于将来访问此页面的用户。

答案 10 :(得分:0)

这是一个使用 STRING_SPLIT 的现代解决方案,非常简洁。缺点是您至少需要以兼容级别 130 运行的 SQL Server 2016 版本。

Declare @strOriginal varchar(100) = 'Juliet ro><0zs my s0x()rz!!?!one!@!@!@!'
Declare @strModified varchar(100) = @strOriginal
Declare @disallowed  varchar(100) = '> < ( ) ! ? @'

Select 
   @strModified = Replace(@strModified, value, '') 
From 
   String_Split(@disallowed,' ')

Select @strModified

它返回:

Juliet ro0zs my s0xrzone