Как заменить нечетные шаблоны внутри строки?
-
21-12-2019 - |
Вопрос
Я создаю временную процедуру в SQL, потому что у меня есть значение таблицы, записанное в уценке, поэтому оно отображается в веб-браузере как визуализированный HTML. (преобразование уценки в HTML).
Строка столбца на данный момент выглядит так:
Questions about **general computing hardware and software** are off-topic for Stack Overflow unless they directly involve tools used primarily for programming. You may be able to get help on [Super User](http://superuser.com/about)
В настоящее время я работаю с жирным и курсивным текстом.Это означает (в случае жирного текста) Мне нужно будет заменить нечетное N раз шаблон**
с<b>
и даже раз с</b>
.
Я видел заменять() но он выполняет замену для всех шаблонов строки.
Итак, как я могу заменить подстроку, только если она нечетная или только четная?
Обновлять: Некоторые люди задаются вопросом, какие схемы я использую, поэтому просто посмотрите. здесь.
Еще одно дополнение, если хотите: Гиперссылка в стиле уценки на гиперссылку html выглядит не так просто.
Решение
Использование TheSTUFF
Function и SimpleGenerAcodicCodeLoop:
CREATE FUNCTION dbo.fn_OddEvenReplace(@text nvarchar(500),
@textToReplace nvarchar(10),
@oddText nvarchar(10),
@evenText nvarchar(500))
RETURNS varchar(max)
AS
BEGIN
DECLARE @counter tinyint
SET @counter = 1
DECLARE @switchText nvarchar(10)
WHILE CHARINDEX(@textToReplace, @text, 1) > 0
BEGIN
SELECT @text = STUFF(@text,
CHARINDEX(@textToReplace, @text, 1),
LEN(@textToReplace),
IIF(@counter%2=0,@evenText,@oddText)),
@counter = @counter + 1
END
RETURN @text
END
.
, и вы можете использовать это так:
SELECT dbo.fn_OddEvenReplace(column, '**', '<b>', '</b>')
FROM table
.
Обновление:
Это повторяется как SP:
CREATE PROC dbo.##sp_OddEvenReplace @text nvarchar(500),
@textToReplace nvarchar(10),
@oddText nvarchar(10),
@evenText nvarchar(10),
@returnText nvarchar(500) output
AS
BEGIN
DECLARE @counter tinyint
SET @counter = 1
DECLARE @switchText nvarchar(10)
WHILE CHARINDEX(@textToReplace, @text, 1) > 0
BEGIN
SELECT @text = STUFF(@text,
CHARINDEX(@textToReplace, @text, 1),
LEN(@textToReplace),
IIF(@counter%2=0,@evenText,@oddText)),
@counter = @counter + 1
END
SET @returnText = @text
END
GO
.
и выполнить:
DECLARE @returnText nvarchar(500)
EXEC dbo.##sp_OddEvenReplace '**a** **b** **c**', '**', '<b>', '</b>', @returnText output
SELECT @returnText
. Другие советы
По запросу OP я изменил мой предыдущий ответ на выполнение в качестве временной сохраненной процедуры.Я оставил свой более ранний ответ, поскольку я считаю, что использование в отношении таблицы строк также будет полезным.
Если известно, что известный (или цифры) таблица уже существует с не менее 8000 значений, то отмеченная раздел CTE может быть опущена, а ссылка CTE Tally заменена на имясуществующая таблица для заполнения.
create procedure #HtmlTagExpander(
@InString varchar(8000)
,@OutString varchar(8000) output
)as
begin
declare @Delimiter char(2) = '**';
create table #t(
StartLocation int not null
,EndLocation int not null
,constraint PK unique clustered (StartLocation desc)
);
with
-- vvv Only needed in absence of Tally table vvv
E1(N) as (
select 1 from (values
(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1)
) E1(N)
), --10E+1 or 10 rows
E2(N) as (select 1 from E1 a cross join E1 b), --10E+2 or 100 rows
E4(N) As (select 1 from E2 a cross join E2 b), --10E+4 or 10,000 rows max
tally(N) as (select row_number() over (order by (select null)) from E4),
-- ^^^ Only needed in absence of Tally table ^^^
Delimiter as (
select len(@Delimiter) as Length,
len(@Delimiter)-1 as Offset
),
cteTally(N) AS (
select top (isnull(datalength(@InString),0))
row_number() over (order by (select null))
from tally
),
cteStart(N1) AS
select
t.N
from cteTally t cross join Delimiter
where substring(@InString, t.N, Delimiter.Length) = @Delimiter
),
cteValues as (
select
TagNumber = row_number() over(order by N1)
,Location = N1
from cteStart
),
HtmlTagSpotter as (
select
TagNumber
,Location
from cteValues
),
tags as (
select
Location = f.Location
,IsOpen = cast((TagNumber % 2) as bit)
,Occurrence = TagNumber
from HtmlTagSpotter f
)
insert #t(StartLocation,EndLocation)
select
prev.Location
,data.Location
from tags data
join tags prev
on prev.Occurrence = data.Occurrence - 1
and prev.IsOpen = 1;
set @outString = @Instring;
update this
set @outString = stuff(stuff(@outString,this.EndLocation, 2,'</b>')
,this.StartLocation,2,'<b>')
from #t this with (tablockx)
option (maxdop 1);
end
go
.
вызывается так:
declare @InString varchar(8000)
,@OutString varchar(8000);
set @inString = 'Questions about **general computing hardware and software** are off-topic **for Stack Overflow.';
exec #HtmlTagExpander @InString,@OutString out; select @OutString;
set @inString = 'Questions **about** general computing hardware and software **are off-topic** for Stack Overflow.';
exec #HtmlTagExpander @InString,@OutString out; select @OutString;
go
drop procedure #HtmlTagExpander;
go
.
Это дает как выход:
Questions about <b>general computing hardware and software</b> are off-topic **for Stack Overflow.
Questions <b>about</b> general computing hardware and software <b>are off-topic</b> for Stack Overflow.
. Один из вариантов — использовать регулярное выражение, поскольку оно упрощает замену таких шаблонов.Функции RegEx не встроены в SQL Server, поэтому вам необходимо использовать SQL CLR, скомпилированную вами или из существующей библиотеки.
Для этого примера я буду использовать SQL# (SQLsharp) (автором которой я являюсь), но функции RegEx доступны в бесплатной версии.
SELECT SQL#.RegEx_Replace
(
N'Questions about **general computing hardware and software** are off-topic\
for Stack Overflow unless **they** directly involve tools used primarily for\
**programming. You may be able to get help on [Super User]\
(https://superuser.com/about)', -- @ExpressionToValidate
N'\*\*([^\*]*)\*\*', -- @RegularExpression
N'<b>$1</b>', -- @Replacement
-1, -- @Count (-1 = all)
1, - @StartAt
'IgnoreCase' -- @RegEx options
);
Вышеупомянутый шаблон \*\*([^\*]*)\*\*
просто ищет что-нибудь, окруженное двойными звездочками.В этом случае вам не нужно беспокоиться о нечетном/четном.Это также означает, что вы не получите плохо сформированную <b>
-тег только если по каким-то причинам есть лишний **
в строке.Я добавил к исходной строке два дополнительных тестовых примера:полный набор **
вокруг слова they
и бесподобный набор **
прямо перед словом programming
.Результат:
Questions about <b>general computing hardware and software</b> are off-topicfor Stack Overflow unless <b>they</b> directly involve tools used primarily for **programming. You may be able to get help on [Super User](https://superuser.com/about)
который отображается как:
Вопросы о общее вычислительное оборудование и программное обеспечение не по теме для переполнения стека, если только они напрямую задействовать инструменты, используемые в основном для **программирования.Вы можете получить помощь по Суперпользователь
Это решение использует методы, описанные JEFF MODEN в Эта статья на работеПроблема в SQL .Это решение длинно, но путем использования в SQL Server Update в SQL Server по поводу кластерного индекса, удерживает обещание быть намного более эффективным по большим набовам данных, чем решения на основе курсора.
Предполагая, что существование подселочного стола создано такое (с не менее 8000 строк):
create table dbo.tally (
N int not null
,unique clustered (N desc)
);
go
with
E1(N) as (
select 1 from (values
(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1)
) E1(N)
), --10E+1 or 10 rows
E2(N) as (select 1 from E1 a cross join E1 b), --10E+2 or 100 rows
E4(N) As (select 1 from E2 a cross join E2 b) --10E+4 or 10,000 rows max
insert dbo.tally(N)
select row_number() over (order by (select null)) from E4;
go
.
и
create function dbo.HtmlTagSPotter(
@pString varchar(8000)
,@pDelimiter char(2))
returns table with schemabinding as
return
WITH
Delimiter as (
select len(@pDelimiter) as Length,
len(@pDelimiter)-1 as Offset
),
cteTally(N) AS (
select top (isnull(datalength(@pstring),0))
row_number() over (order by (select null))
from dbo.tally
),
cteStart(N1) AS (--==== Returns starting position of each "delimiter" )
select
t.N
from cteTally t cross join Delimiter
where substring(@pString, t.N, Delimiter.Length) = @pDelimiter
),
cteValues as (
select
ItemNumber = row_number() over(order by N1)
,Location = N1
from cteStart
)
select
ItemNumber
,Location
from cteValues
go
.
Затем выполнение следующего SQL выполнит требуемую замену.Обратите внимание, что внутреннее соединение в конце предотвращает преобразование любых трейлинга «нечетных» тегов:
create table #t(
ItemNo int not null
,Item varchar(8000) null
,StartLocation int not null
,EndLocation int not null
,constraint PK unique clustered (ItemNo,StartLocation desc)
);
with data(i,s) as ( select i,s from (values
(1,'Questions about **general computing hardware and software** are off-topic **for Stack Overflow.')
,(2,'Questions **about **general computing hardware and software** are off-topic **for Stack Overflow.')
--....,....1....,....2....,....3....,....4....,....5....,....6....,....7....,....8....,....9....,....0
)data(i,s)
),
tags as (
select
ItemNo = data.i
,Item = data.s
,Location = f.Location
,IsOpen = cast((TagNumber % 2) as bit)
,Occurrence = TagNumber
from data
cross apply dbo.HtmlTagSPotter(data.s,'**') f
)
insert #t(ItemNo,Item,StartLocation,EndLocation)
select
data.ItemNo
,data.Item
,prev.Location
,data.Location
from tags data
join tags prev
on prev.ItemNo = data.ItemNo
and prev.Occurrence = data.Occurrence - 1
and prev.IsOpen = 1
union all
select
i,s,8001,8002
from data
;
declare @ItemNo int
,@ThisStting varchar(8000);
declare @s varchar(8000);
update this
set @s = this.Item = case when this.StartLocation > 8000
then this.Item
else stuff(stuff(@s,this.EndLocation, 2,'</b>')
,this.StartLocation,2,'<b>')
end
from #t this with (tablockx)
option (maxdop 1);
select
Item
from (
select
Item
,ROW_NUMBER() over (partition by ItemNo order by StartLocation) as rn
from #t
) t
where rn = 1
go
.
Урожайность:
Item
------------------------------------------------------------------------------------------------------------
Questions about <b>general computing hardware and software</b> are off-topic **for Stack Overflow.
Questions <b>about </b>general computing hardware and software<b> are off-topic </b>for Stack Overflow.
.