3

I have a string and i would like to replace some words in it by referring a lookup table

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')

Expected output

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'

What I have tried

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

but it replaces the replaced word again

4
  • Include what you have tried in your question. Commented Jan 17, 2020 at 12:56
  • 2
    The strings in your values() portion of the insert must be in single quotes! insert into LookupTab(oldvalue, newvalue) values ('Run', 'Run Go'), ..... Commented Jan 17, 2020 at 13:02
  • 1
    Your match is not case sensitive go and Go, Go Run. Is this important? Commented Jan 17, 2020 at 13:04
  • @Zhorov its not important.. if we type 'go' it should get replaced with Go Run Commented Jan 17, 2020 at 13:05

2 Answers 2

1

Demo on dbfiddle

2 steps:

  1. Find all the words to replace.
  2. Replace with each @OldValue to @NewValue word accordingly.
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

Output enter image description here

Updated 2020-01-20

A new solution to fix some exceptional cases. Demo in db<>fiddle

  • Create a strSplit function to be able to split each word into a table
  • Replace each word by ISNULL(l.newvalue, s.val)
  • Join all word after replacing into @Result then return.
    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

Output enter image description here

Sign up to request clarification or add additional context in comments.

12 Comments

Actually it wont. if i type 'i have to go mask' it wont replace the work mask
To be honest, based on your requirement, it just replace the word "Go" and keep "mask"
I didnt get you. Actually I should get result like 'i have to Go Run Mask Hide'
I got it. I just update first word instead all. let me update my answer.
I've just updated my answer, please take a look. @Surensiveaya
|
1

This is a challenging problem. Recursive CTEs can be used to replace the strings. However, you don't want to replace already replaced strings. Ouch.

To solve this, you can use two rounds of replacement. The first puts in a placeholder for the old values. The second puts in the new values.

This looks like:

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 is a db<>fiddle.

You can add the ord column using row_number() in a CTE, but I would recommend adding it to the table.

This logic is also easily incorporated into a UDF, if you want that. I don't recommend a UDF for this purpose.

3 Comments

Could you please make it as a function which accept one varchar and replace it as per the requirement
OP doesn't have Ord column.
@Surensiveaya . . . There is no requirement for a function in the question. And I don't recommend one either.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.