Sql Multiple Replace基于查询

时间:2017-05-19 12:17:03

标签: sql sql-server

我一直在尝试设置SQL函数来使用"标记"来构建描述。例如,我想从描述开始:

"This is [length] ft. long and [height] ft. high"

使用相关表格中的数据修改说明,最后得到:

"This is 75 ft. long and 20 ft. high"

如果我们有一定数量的标签,我可以使用REPLACE函数轻松完成此操作,但我希望这些标记是用户定义的,并且每个描述可能包含也可能没有特定标记。除了使用游标为每个可用标记一次通过字符串之外,还有更好的方法吗? SQL是否具有任何内置功能来进行多次替换?类似的东西:

Replace(description,(select tag, replacement from tags))

5 个答案:

答案 0 :(得分:0)

尝试以下查询:

SELECT REPLACE(DESCRIPTION,'[length]',( SELECT replacement FROM tags WHERE tag 
 = '[length]') )

答案 1 :(得分:0)

我实际上建议在应用程序代码中执行此操作。但是,你可以使用递归CTE来做到这一点:

.gitignore

答案 2 :(得分:0)

我同意Gordon的观点,最好在您的应用程序代码中处理。

如果由于某种原因该选项不可用,并且如果您不想根据Gordon的回答使用递归,则可以使用计数表方法来交换您的值。

您需要测试为每个值执行的for xml的性能,但是......

假设您有一个Tag替换值表:

create table TagReplacementTable(Tag nvarchar(50), Replacement nvarchar(50));
insert into TagReplacementTable values('[test]',999)
                                     ,('[length]',75)
                                     ,('[height]',20)
                                     ,('[other length]',40)
                                     ,('[other height]',50);

您可以创建一个可以使用Descriptions的内联表格功能,并使用TagReplacementTable作为参考来删除必要的部分:

create function dbo.Tag_Replace(@str      nvarchar(4000)
                               ,@tagstart nvarchar(1)
                               ,@tagend   nvarchar(1)
                               )
returns table
as
return
(
    with n(n)     as (select n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n(n))
                      -- Select the same number of rows as characters in @str as incremental row numbers.
                      -- Cross joins increase exponentially to a max possible 10,000 rows to cover largest @str length.
        ,t(t)     as (select top (select len(@str) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
                      -- Return the position of every value that starts or ends a part of the description.
                      -- This will be the first character (t='f'), the start of any tag (t='s') and the end of any tag (t='e').
        ,s(s,t)   as (select 1, 'f'
                      union all select t+1, 's' from t where substring(@str,t,1) = @tagstart
                      union all select t+1, 'e' from t where substring(@str,t,1) = @tagend
                      )
                      -- Return the start and length of every value, to use in the SUBSTRING function.
                      -- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
                      -- Using the t value we can determine which CHARINDEX to look for.
        ,l(t,s,l) as (select t,s,isnull(nullif(charindex(case t when 'f' then @tagstart when 's' then @tagend when 'e' then @tagstart end,@str,s),0)-s,4000) from s)
    -- Each element of the string is returned in an ordered list along with its t value.
    -- Where this t value is 's' this means the value is a tag, so append the start and end identifiers and join to the TagReplacementTable.
    -- Where no replacement is found, simply return the part of the Description.
    -- Finally, concatenate into one string value.
    select (select isnull(r.Replacement,k.Item)
                from(select row_number() over(order by s) as ItemNumber
                          ,case when l.t = 's' then '[' else '' end
                           + substring(@str,s,l)
                           + case when l.t = 's' then ']' else '' end as Item
                          ,t
                     from l
                    ) k
                    left join TagReplacementTable r
                        on(k.Item = r.Tag)
                order by k.ItemNumber
                for xml path('')
            ) as NewString
);

然后outer apply到函数的结果,对所有Description值进行替换:

declare @t table (Descr nvarchar(100));
insert into @t values('This is [length] ft. long and [height] ft. high'),('[test] This is [other length] ft. long and [other height] ft. high');

select *
from @t t
    outer apply dbo.Tag_Replace(t.Descr,'[',']') r;

输出:

+--------------------------------------------------------------------+-----------------------------------------+
|                               Descr                                |                NewString                |
+--------------------------------------------------------------------+-----------------------------------------+
| This is [length] ft. long and [height] ft. high                    | This is 75 ft. long and 20 ft. high     |
| [test] This is [other length] ft. long and [other height] ft. high | 999 This is 40 ft. long and 50 ft. high |
+--------------------------------------------------------------------+-----------------------------------------+

答案 3 :(得分:0)

我不会遍历单个字符串,而是在整个字符串列上运行更新。我不确定这是不是你的意图,但这会比一次一个字符串快得多。

测试数据:

Create TABLE #strs ( mystr VARCHAR(MAX) )

Create TABLE #rpls (i INT IDENTITY(1,1) NOT NULL, src VARCHAR(MAX) , Trg VARCHAR(MAX) )


INSERT INTO #strs
 ( mystr )
 SELECT 'hello ##color## world'
 UNION ALL SELECT 'see jack ##verboftheday##! ##verboftheday## Jack, ##verboftheday##!'
 UNION ALL SELECT 'on ##Date##, the ##color## StockMarket was ##MarketDirection##!'


INSERT INTO #rpls ( src ,Trg )
 SELECT '##Color##', 'Blue'
 UNION SELECT ALL '##verboftheday##' , 'run'
 UNION SELECT ALL '##Date##' , CONVERT(VARCHAR(MAX), GETDATE(), 9)
 UNION SELECT ALL '##MarketDirection##' , 'UP' 

然后像这样的循环:

DECLARE @i INTEGER = 0
DECLARE @count INTEGER

SELECT @count = COUNT(*)
    FROM #rpls R

WHILE @i < @count
    BEGIN
        SELECT @i += 1

        UPDATE #strs
            SET mystr = REPLACE(mystr, ( SELECT R.src
                                            FROM #rpls R
                                            WHERE i = @i ), ( SELECT R.Trg
                                                                FROM #rpls R
                                                                WHERE i = @i ))
    END

SELECT *
    FROM #strs S

产生以下

hello Blue world
see jack run!  run Jack, run!
on May 19 2017  9:48:02:390AM, the Blue StockMarket was UP!

答案 4 :(得分:0)

我发现有人希望使用一系列选项来做类似here的事情:

SELECT @target = REPLACE(@target, invalidChar, '-')
FROM (VALUES ('~'),(''''),('!'),('@'),('#')) AS T(invalidChar)

我可以这样修改它:

declare @target as varchar(max) = 'This is [length] ft. long and [height] ft. high'

select @target = REPLACE(@target,'[' + tag + ']',replacement)
from tags

然后对select语句中返回的每条记录运行一次替换。

(我最初在我的问题中添加了这个,但听起来更好的协议是将其添加为答案。)