Replace the alternate occurances of a substring

Viewed 114

My input strings are like:

  1. A or B OR C or D OR E or F
  2. A OR B OR C OR D OR E OR F

Expected Output: 'A or B' OR 'C or D' OR 'E or F'

outputString = '''' + REPLACE(@inputValue COLLATE Latin1_General_CS_AS, ' OR ' COLLATE Latin1_General_CS_AS, ''' OR ''') + ''''

I tried using SQL Replace function and the above statement works properly for the first string and I get the desired output but for the second string since we have all the ORs in the uppercase it fails and returns 'A' OR 'B' OR 'C' OR 'D' OR 'E' OR 'F'

I'm using SSMS 15.0.

How can I solve this problem? Any help will be appreciated.

3 Answers

Here's a solution that uses a UDF.

The function splits a string on a pattern as a resultset.
(similar as the STRING_SPLIT function, but with a pattern)

The FOR XML trick is then used to construct a string from the splitted parts, and to add the quotes.

DECLARE @vchNewValue VARCHAR(100), @result VARCHAR(100);
SET @vchNewValue = 'A OR B or C OR D or E OR F';

SET @result = LTRIM(RTRIM((
       SELECT
         CASE WHEN match = 1
         THEN ' '+quotename(ltrim(rtrim(replace(value,' OR ',' or ') )),'''')+' ' 
         ELSE UPPER(value)
         END
       FROM dbo.fnPattern_Split(' '+@vchNewValue+' ', ' % OR % ') AS spl
       ORDER BY ordinal
       FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
    ));

SELECT @result AS result;
result
'A or B' OR 'C or D' OR 'E or F'

Test db<>fiddle here

The UDF

Uses PATINDEX to find each next start position of the given pattern in the string.

Then finds the nearest end position where the pattern is still valid.
So it's kinda like a lazy search in regex.

The positions are then used to insert the parts into the returned table.

CREATE FUNCTION dbo.fnPattern_Split
(
  @str     VARCHAR(MAX),
  @pattern VARCHAR(100)
)
RETURNS @tbl TABLE (
 ordinal INT,
 value VARCHAR(MAX),
 match BIT
)
WITH SCHEMABINDING
AS
BEGIN
  DECLARE @value NVARCHAR(MAX)
        , @splitvalue NVARCHAR(MAX)
        , @startpos INT = 0
        , @endpos INT = 0
        , @ordinal INT = 0
        , @foundend BIT = 0
        , @patminlen INT = ISNULL(NULLIF(LEN(REPLACE(@pattern,'%','')),0),1);
  WHILE (LEN(@str) > 0)
  BEGIN
    
    SET @startpos = ISNULL(NULLIF(PATINDEX('%'+@pattern+'%', @str),0), LEN(@str)+1);
    
    IF @startpos < LEN(@str)
    BEGIN
        SET @foundend = 0;
        SET @endpos = @startpos+@patminlen-1;
        
        WHILE @endpos < LEN(@str) AND @foundend = 0
        BEGIN
          IF SUBSTRING(@str, @startpos, 1+@endpos-@startpos) LIKE @pattern
            SET @foundend = 1;
          ELSE
            SET @endpos += 1;
        END
    END
    ELSE SET @endpos = LEN(@str);
    
    IF @startpos > 1
    BEGIN
      SET @ordinal += 1;
      SET @value = LEFT(@str, @startpos-1);
      INSERT INTO @tbl (ordinal, value, match)
                VALUES (@ordinal, @value, 0);
    END
    
    IF @endpos >= @startpos
    BEGIN
      SET @ordinal += 1;
      SET @splitvalue = SUBSTRING(@str, @startpos, 1+@endpos-@startpos);
      INSERT INTO @tbl (ordinal, value, match)
                VALUES (@ordinal, @splitvalue, 1);
    END
    
    SET @str = SUBSTRING(@str, @endpos+1, LEN(@str));
  END;
  RETURN;
END;

A recursive solution that stuffs the quotes.

The recursive CTE loops through the string while finding the start positions of the ' or ' patterns.

Since ' or ' has 4 characters, having a start position means you also have the end position.

TheSTUFF function can insert characters in a string on positions.

So the positions are used to stuff the quotes where needed.
Which is every even occurence (modulus 2 of lvl is 0).

declare @input varchar(100)
      , @result varchar(100);
set @input = 'A OR B or C OR D or E OR F';
set @result = @input;

with rcte as (
  select 1 as lvl
  , charindex(' or ', @input) as pos
  , len(@input) as max_pos
  union all
  select lvl+1
  , isnull(nullif(charindex(' or ', @input, pos+4), 0), max_pos) 
  , max_pos
  from rcte
  where pos < max_pos
) 
select @result = stuff(stuff(@result,pos+4,0,''''),pos,0,'''')
from rcte
where lvl%2 = 0 and pos+4 < max_pos
order by lvl desc;

SET @result = ''''+@result+'''';
SET @result = REPLACE(REPLACE(@result,' OR ',' or '),''' or ''',''' OR ''');

select @result as result;
result
'A or B' OR 'C or D' OR 'E or F'

Test on db<>fiddle here

This solution comes with a high cost on server load since while is used.

declare @input varchar(100) 
set @input = 'A or B or C or D or E or F or G or'

declare @inc int = 1, @end int = 1
,@final varchar(100) = '', @part varchar(100)
,@nextposition varchar(100), @or varchar(10)= ''
,@last varchar(10), @ifendsOR varchar(10)

select @nextposition = case when @input like '%or' then substring(@input,1,len(@input)-2) else @input end
select @ifendsOR = case when @input  like '%or' then ' or' else '' end
select @last = ltrim(rtrim(right(@nextposition,2)))

while  @end <> 0
begin 
select @part = substring(@nextposition,1,charindex('or',@nextposition)-2)
select @nextposition = replace(@nextposition,concat(@part,' or '),'')
set @end = charindex('or',@nextposition)
select @or = case when @inc%2 = 0 then ' OR ' else ' or ' end
set @inc = @inc+1
set @final = concat(@final,@part,@or)
end
select @ifendsOR = case when @inc%2 = 0 then upper(@ifendsOR) else @ifendsOR end
select concat(@final,@last,@ifendsOR)
Related