【问题标题】:Sql Multiple Replace based on query基于查询的 Sql 多重替换
【发布时间】:2017-05-22 21:09:08
【问题描述】:

我一直在尝试设置一个 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))

【问题讨论】:

    标签: sql sql-server


    【解决方案1】:

    我实际上建议在应用程序代码中这样做。但是,您可以使用递归 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;
    

    【讨论】:

    • 这个看起来很紧凑。但有一件事,如果您有超过 100 条记录可供替换,则会收到错误 The statement terminated. The maximum recursion 100 has been exhausted before statement completion. 但您可以通过在查询末尾添加 option (maxrecursion 1000) 来修复它。
    【解决方案2】:

    试试下面的查询:

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

    【讨论】:

    • 这个答案严重缺乏这个问题的要求:如果我们有一定数量的标签,我可以使用REPLACE 函数轻松做到这一点,但我想要这些标签由用户定义,并且每个描述中可能有也可能没有特定标签
    【解决方案3】:

    我同意 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 |
    +--------------------------------------------------------------------+-----------------------------------------+
    

    【讨论】:

      【解决方案4】:

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

      测试数据:

      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!
      

      【讨论】:

        【解决方案5】:

        我发现有人想做类似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 语句中返回的每条记录运行一次替换。

        (我最初已将此添加到我的问题中,但听起来将其添加为答案是更好的协议。)

        【讨论】:

          猜你喜欢
          • 2017-03-11
          • 2019-04-16
          • 1970-01-01
          • 2018-06-08
          • 2012-06-18
          • 2014-08-04
          • 2019-01-31
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多