【问题标题】:SQL Server replace string based on another tableSQL Server 基于另一个表替换字符串
【发布时间】:2020-05-04 08:50:39
【问题描述】:

我有一个字符串,我想通过引用查找表来替换其中的一些单词

create table LookupTab 
(
     oldvalue varchar(100),
     newvalue varchar(100)
);

insert into LookupTab 
values ('Run', 'Run Go'), ('Hide', 'Hide Mask'), ('Go', 'Go Run'), ('Mask', 'Mask Hide')

预期输出

string ='i have to go'     
result ='i have to Go Run'   <-- it should not again replace the word Run

string ='i have to go and go again'     
result ='i have to Go Run and Go Run again'

我尝试过的

CREATE FUNCTION [dbo].[TranslateString]
    (@Str nvarchar(max))
RETURNS nvarchar(max)
AS
BEGIN
    DECLARE @Result nvarchar(max) = @Str;

    SELECT @Result = REPLACE(@Result, Oldvalue, NewValue) 
    FROM LookupTab; 

    RETURN @Result;
END

但它再次替换了替换的单词

【问题讨论】:

  • 在问题中包含您尝试过的内容。
  • insertvalues() 部分中的字符串必须用单引号括起来! insert into LookupTab(oldvalue, newvalue) values ('Run', 'Run Go'), .....
  • 您的匹配项区分大小写 goGo, Go Run。这很重要吗?
  • @Zhorov 它并不重要.. 如果我们输入“go”,它应该被替换为 Go Run

标签: sql sql-server replace sql-server-2016


【解决方案1】:

Demo on dbfiddle

两步:

  1. 找出所有要替换的词。
  2. 将每个 @OldValue 替换为 @NewValue 相应的单词。
CREATE FUNCTION [dbo].[TranslateString]
(
 @Str nvarchar(max)
)RETURNS nvarchar(max)
AS
BEGIN
  DECLARE @OldValue nvarchar(100);
  DECLARE @NewValue nvarchar(100);
  DECLARE @CHARINDEX INT = 0;
  DECLARE @Result nvarchar(100) = @Str;
  DECLARE @TempTable AS TABLE(OldValue varchar(100), NewValue varchar(100), isApply BIT) 

  --1. Region: Find all the words to replace
   WHILE (@CHARINDEX < LEN(@Str))
   BEGIN
     SELECT TOP 1 @OldValue = OldValue, @NewValue = newvalue, @CHARINDEX = CHARINDEX(oldvalue, @Str) 
      FROM LookupTab
        WHERE CHARINDEX(oldvalue, @Str) > @CHARINDEX
        ORDER BY CHARINDEX(oldvalue, @Str)

     IF(ISNULL(@OldValue, '') != '' AND NOT EXISTS(SELECT TOP 1 1 FROM @TempTable WHERE OldValue = @OldValue))   
         INSERT INTO @TempTable(OldValue, NewValue)
         VALUES(@OldValue, @NewValue)

     SET @CHARINDEX = @CHARINDEX + LEN(@OldValue);
   END
 --1. End-Region: Find all the words to replace

  --2. Region: Replace with each @OldValue to @NewValue word accordingly
  WHILE(EXISTS(SELECT OldValue FROM @TempTable WHERE ISNULL(isApply, 0) = 0))
  BEGIN
       SELECT @OldValue = OldValue, @NewValue = NewValue FROM @TempTable WHERE ISNULL(isApply, 0) = 0

       SET @Result = replace(@Result,@Oldvalue,@NewValue);
       UPDATE @TempTable SET isApply = 1 WHERE OldValue = @OldValue
  END
  --2. End-Region: Replace with each @OldValue to @NewValue word accordingly

  RETURN @Result;
END

输出

2020 年 1 月 20 日更新

解决一些特殊情况的新解决方案。 Demo in db<>fiddle

  • 创建一个strSplit 函数,以便能够将每个单词拆分成一个表
  • 将每个单词替换为ISNULL(l.newvalue, s.val)
  • 在替换成@Result之后加入所有单词然后返回。
    CREATE FUNCTION [dbo].[TranslateString]
    (
     @Str nvarchar(max)
    )RETURNS nvarchar(max)
    AS
    BEGIN
        DECLARE @Result NVARCHAR(MAX)
        ;WITH cte_TempTable AS(
           select ISNULL(l.newvalue, s.val) AS Value 
           from strSplit(@Str, ' ') s
           left join LookupTab l on s.val = l.oldvalue
        )
        SELECT @Result = (SELECT Value + ' ' FROM cte_TempTable FOR XML PATH(''))

      RETURN @Result;
    END

输出

【讨论】:

  • 其实不会。如果我输入“我必须戴面具”,它不会取代工作面具
  • 老实说,根据您的要求,它只是替换“Go”这个词并保留“mask”
  • 我没找到你。实际上我应该得到像“我必须去运行面具隐藏”这样的结果
  • 我明白了。我只是更新第一个单词而不是全部。让我更新我的答案。
  • 我刚刚更新了我的答案,请看一下。 @Surensiveaya
【解决方案2】:

这是一个具有挑战性的问题。递归 CTE 可用于替换字符串。但是,您不想替换已替换的字符串。哎哟。

要解决这个问题,您可以使用两轮替换。第一个为旧值放置一个占位符。第二个输入新值。

这看起来像:

with cte as (
      select convert(varchar(max), v.str) as str, 1 as lev, str as orig_str
      from (values ('i have to go'), ('i have to Go Run')) v(str)
      union all
      select replace(cte.str, lt.oldvalue, concat('[', lt.ord, ']')), 1 + cte.lev, cte.orig_str
      from cte join
           lookuptab lt
           on lt.ord = cte.lev
     ),
     cte2 as (
      select cte.str, 1 as lev, cte.orig_str
      from (select cte.*, row_number() over (partition by cte.orig_str order by lev desc) as seqnum
            from cte
           ) cte
      where seqnum = 1
      union all
      select replace(cte2.str, concat('[', lt.ord, ']'), lt.newvalue), 1 + cte2.lev, cte2.orig_str
      from cte2 join
           lookuptab lt
           on lt.ord = cte2.lev
     )
select top (1) with ties str, orig_str
from cte2
order by row_number() over (partition by orig_str order by lev desc);

Here 是一个 dbfiddle。

您可以在 CTE 中使用 row_number() 添加 ord 列,但我建议将其添加到表中。

如果您愿意,此逻辑也很容易合并到 UDF 中。为此,我不推荐使用 UDF。

【讨论】:

  • 能否将其设为接受一个 varchar 并根据要求替换它的函数
  • OP 没有Ord 列。
  • @Surensiveaya 。 . .问题中不需要函数。我也不推荐一个。
猜你喜欢
  • 1970-01-01
  • 2023-01-25
  • 2013-12-03
  • 2014-07-27
  • 2019-05-14
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 2017-01-28
相关资源
最近更新 更多