38

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

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

我有以下任务:需要对列执行匹配,并提高匹配使用 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)))

有人可以在性能和更清洁的实现方面给我一个更好的解决方案吗?

4

12 回答 12

58

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'
于 2009-10-16T19:47:32.760 回答
24

我会认真考虑改为使用CLR UDF 并使用正则表达式(字符串和模式都可以作为参数传入)对一系列字符进行完整的搜索和替换。它应该很容易胜过这个 SQL UDF。

于 2009-10-16T19:45:57.550 回答
21

我真的很喜欢@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

朱丽叶 ro0zs 我 s0xrzone

于 2015-03-20T08:35:26.097 回答
5

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

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

于 2013-01-18T20:57:17.123 回答
2

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

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

我希望这些信息对将来访问此页面的人有所帮助。

于 2018-02-14T13:44:33.167 回答
2

我遇到了一次性数据迁移问题,其中源数据无法正确输出一些不寻常/技术字符以及 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)。

这是我需要替换的字符串及其替换... [']"~,{Ø}°$±|¼¦¼ª½¬½^¾#✓</p>

即,左方括号表示撇号,右方括号表示双引号。您可以看到那里有粗俗的分数以及度数和直径符号。

如果有人需要替换较长的字符串,则会包含一个默认的 @LengthOfReplacement 作为起点。我在我的项目中玩过这个,但单个字符替换是主要功能。

case 语句的条件很重要。它确保仅在您的 @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);

我希望有人发现这很有用,如果您要针对更大的表测试它的性能,请以一种或另一种方式告诉我们!

于 2019-03-28T12:35:44.190 回答
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;
于 2016-06-01T15:19:17.970 回答
0

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

字符替换的一般思想可以用一个简单的字符映射表方法来演示:

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)
于 2009-10-16T20:46:27.713 回答
0

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

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

于 2009-10-17T02:58:29.623 回答
0

以下是步骤

  1. 创建 CLR 函数

请参见以下代码:

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. 现在测试它

请参见以下代码:

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!!!!!!!!!!!!!
于 2013-09-27T22:44:59.107 回答
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
于 2021-04-06T14:44:32.223 回答
0
create function RemoveCharacters(@original nvarchar(max) , @badchars nvarchar(max))
returns nvarchar(max)
as 
begin 
    declare @len int = (select len(@badchars))
    return REPLACE(TRANSLATE(@original, @badchars, replicate('#' , @len )), '#', '')
end
go


select dbo.RemoveCharacters('Hello World!' , 'lo!' )
--returns He Wrd
于 2022-01-10T12:03:00.813 回答