使用join替换varchar(max)字段中的值

use*_*076 12 sql sql-server sql-server-2008-r2

我有一个包含带占位符的文本字段的表.像这样的东西:

Row Notes  
1.  This is some notes ##placeholder130## this ##myPlaceholder##, #oneMore#. End.
2.  Second row...just a ##test#.   
Run Code Online (Sandbox Code Playgroud)

(此表平均包含大约1-5k行.一行中的平均占位符数为5-15).

现在,我有一个如下所示的查找表:

Name             Value
placeholder130    Dog
myPlaceholder     Cat
oneMore           Cow
test              Horse   
Run Code Online (Sandbox Code Playgroud)

(查找表将包含10k到100k记录的任何内容)

我需要找到从字符串到查找表将这些占位符连接起来并用值替换的最快方法.所以,我的结果应该是这样的(第1行):

这是一些注释狗这只猫,牛.结束.

我想出的是为每个占位符将每一行拆分为多个,然后将其连接到查找表,然后将记录连接到具有新值的原始行,但平均需要大约10-30秒.

Mik*_*son 9

您可以尝试使用数字表拆分字符串并使用它重建它for xml path.

select (
       select coalesce(L.Value, T.Value)
       from Numbers as N
         cross apply (select substring(Notes.notes, N.Number, charindex('##', Notes.notes + '##', N.Number) - N.Number)) as T(Value)
         left outer join Lookup as L
           on L.Name = T.Value
       where N.Number <= len(notes) and
             substring('##' + notes, Number, 2) = '##'
       order by N.Number
       for xml path(''), type
       ).value('text()[1]', 'varchar(max)')
from Notes
Run Code Online (Sandbox Code Playgroud)

SQL小提琴

我从Aaron Bertrand的博客文章中借用了字符串


And*_*mar 6

SQL Server的字符串操作速度不是很快,所以这可能是客户端最好的.让客户端加载整个查找表,并在它们到达时替换它们.

话虽如此,它当然可以在SQL中完成.这是一个带有递归CTE的解决方案.它每个递归步骤执行一次查找:

; with  Repl as
        (
        select  row_number() over (order by l.name) rn
        ,       Name
        ,       Value
        from    Lookup l
        )
,       Recurse as
        (
        select  Notes
        ,       0 as rn
        from    Notes
        union all
        select  replace(Notes, '##' + l.name + '##', l.value)
        ,       r.rn + 1
        from    Recurse r
        join    Repl l
        on      l.rn = r.rn + 1
        )
select  *
from    Recurse
where   rn = 
        (
        select  count(*)
        from    Lookup
        )
option  (maxrecursion 0)
Run Code Online (Sandbox Code Playgroud)

SQL Fiddle的例子.

另一个选项是while循环以继续替换查找,直到找不到更多:

declare @notes table (notes varchar(max))

insert  @notes
select  Notes
from    Notes

while 1=1
    begin

    update  n
    set     Notes = replace(n.Notes, '##' + l.name + '##', l.value)
    from    @notes n
    outer apply
            (
            select  top 1 Name
            ,       Value
            from    Lookup l
            where   n.Notes like '%##' + l.name + '##%'
            ) l
    where   l.name is not null

    if @@rowcount = 0
        break
    end   

select  *
from    @notes
Run Code Online (Sandbox Code Playgroud)

SQL Fiddle的例子.


Nat*_*erl 4

我同意 tsql 不适合此操作的评论,但如果您必须在数据库中执行此操作,这里是一个使用函数来管理多个替换语句的示例。

由于每个音符中的标记数量相对较少 (5-15) 和大量标记 (10k-100k),我的函数首先从输入中提取标记作为潜在标记,并使用该集合来加入您的查找 ( dbo.Token 下面)。在每个音符中查找任何标记的出现的工作量太大了。

我使用 50k 令牌和 5k 笔记做了一些性能测试,这个函数运行得非常好,在 <2 秒内完成(在我的笔记本电脑上)。请报告该策略对您的效果如何。

注意:在您的示例数据中,令牌格式不一致(##_#, ##_##, #_#),我猜测这只是一个拼写错误,并假设所有令牌均采用##TokenName##的形式。

--setup
    if object_id('dbo.[Lookup]') is not null
        drop table dbo.[Lookup];
    go
    if object_id('dbo.fn_ReplaceLookups') is not null
        drop function dbo.fn_ReplaceLookups;
    go

    create table dbo.[Lookup] (LookupName varchar(100) primary key, LookupValue varchar(100));
    insert into dbo.[Lookup]
        select '##placeholder130##','Dog' union all
        select '##myPlaceholder##','Cat' union all
        select '##oneMore##','Cow' union all
        select '##test##','Horse';
    go

    create function [dbo].[fn_ReplaceLookups](@input varchar(max))
    returns varchar(max)
    as
    begin

        declare @xml xml;
        select @xml = cast(('<r><i>'+replace(@input,'##' ,'</i><i>')+'</i></r>') as xml);

        --extract the potential tokens
        declare @LookupsInString table (LookupName varchar(100) primary key);
        insert into @LookupsInString
            select  distinct '##'+v+'##'
            from    (   select  [v] = r.n.value('(./text())[1]', 'varchar(100)'),
                                [r] = row_number() over (order by n)
                        from    @xml.nodes('r/i') r(n)
                    )d(v,r)
            where   r%2=0;

        --tokenize the input
        select  @input = replace(@input, l.LookupName, l.LookupValue)
        from    dbo.[Lookup] l
        join    @LookupsInString lis on 
                l.LookupName = lis.LookupName;

        return @input;
    end
    go          
    return            

--usage
    declare @Notes table ([Id] int primary key, notes varchar(100));
    insert into @Notes
        select 1, 'This is some notes ##placeholder130## this ##myPlaceholder##, ##oneMore##. End.' union all
        select 2, 'Second row...just a ##test##.';

    select  *,
            dbo.fn_ReplaceLookups(notes)
    from    @Notes;
Run Code Online (Sandbox Code Playgroud)

返回:

Tokenized
--------------------------------------------------------
This is some notes Dog this Cat, Cow. End.
Second row...just a Horse.
Run Code Online (Sandbox Code Playgroud)