2017-05-19 104 views
1

我一直在嘗試設置一個SQL函數來構建帶有「tags」的描述。例如,我將要開始一段描述: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)) 

回答

0

其實我推薦在應用程序代碼做這個。但是,您可以使用遞歸CTE:

with t as (
     select t.*, row_number() over (order by t.tag) as seqnum 
     from tags t 
    ), 
     cte as (
     select replace(@description, t.tag, t.replacement) as d, t.seqnum 
     from t 
     where seqnum = 1 
     union all 
     select replace(d, t.tag, t.replacement), t.seqnum 
     from cte join 
      t 
      on t.seqnum = cte.seqnum + 1 
    ) 
select top 1 cte.* 
from cte 
order by seqnum desc; 
+1

This一個看起來很緊湊。但有一件事,如果你有超過100個可用的記錄可以替換,你會得到一個錯誤'語句終止。在語句完成之前,最大遞歸100已經耗盡。「但是,您可以在查詢結尾處添加'option(maxrecursion 1000)'來修復它。 – Quark

0

嘗試:

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

這個答案是遠遠短需要什麼這個問題:*我可以做如果我們有一定數量的標籤,這很容易通過'REPLACE'函數**,但是我希望這些標籤是用戶定義的**,並且**每個描述可能有也可能沒有特定的標籤**。* – iamdave

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 | 
+--------------------------------------------------------------------+-----------------------------------------+ 
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! 
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語句返回的更換一次。

(我原本這個加入到我的問題,但它聽起來就像是更好的協議,將其添加爲一個答案。)