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秒.
您可以尝试使用数字表拆分字符串并使用它重建它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)
我从Aaron Bertrand的博客文章中借用了字符串
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)
另一个选项是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)
我同意 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)