Агрегатная замена в SQL Server?

#sql #tsql #dynamic #replace

#sql #tsql #динамический #заменить

Вопрос:

Чего я пытаюсь добиться, так это сделать динамической серию замен, которые должны выполняться в определенном поле. (Чтобы сделать все еще проще, я хочу фактически удалить данные, поэтому я всегда буду сравнивать с

Скажите, что иногда мне придется делать только одну замену:

 ... REPLACE(myField, stringToRemove, '')
 

Иногда мне понадобятся две замены:

 ... REPLACE(REPLACE(myField, stringToRemove, ''), anotherStringToRemove, '')
 

Однако мне нужно сделать это динамичным, и я заранее не знаю, сколько из этих значений у меня будет, и, следовательно, сколько замен (удалений) Я должен буду сделать.

Я попытался выполнить поиск функций обработки агрегированных строк, и, конечно же, их нет. Я также знаю, что это может быть достигнуто с помощью агрегатной функции CLR, но у меня нет возможности ее использовать.

Есть идеи?

Комментарии:

1. Каковы ваши условия для количества REPLACE() вызовов?

2. Динамическая переменная. Сейчас это количество строк, соответствующих определенному условию. Вы можете представить это как таблицу конфигурации: Replacements(stringToRemove nvarchar)

Ответ №1:

Вы можете настроить табличную переменную с помощью fromValue и toValue и использовать цикл while для выполнения замен.

 -- Table to replace in
declare @T table
(
  Value varchar(50)
)

insert into @T values
('first second third'),
('first second third')

-- Table with strings to replace
declare @Rep table
(
  ID int identity primary key,
  FromValue varchar(50),
  ToValue varchar(50)
)

insert into @Rep values
('second', 'fourth'),
('third', 'fifth')

declare @ID int
select @ID = max(ID)
from @Rep

while @ID > 0
begin
  update @T
  set Value = replace(Value, FromValue, ToValue)
  from @Rep
  where ID = @ID

  set @ID -= 1
end

select *
from @T
 

Результат:

 Value 
-------------------
first fourth fifth
first fourth fifth
 

Если вы хотите запрашивать только значения, вы можете сделать что-то вроде этого.

 ;with C as
(
  select 0 as ID, 
         Value,
         0 as Lvl
  from @T
  union all
  select R.ID,
         cast(replace(C.Value, R.FromValue, R.ToValue) as varchar(50)),
         Lvl   1
  from @Rep as R
    inner join C
      on C.ID   1 = R.ID
)
select top 1 with ties Value
from C
order by Lvl desc
 

Комментарии:

1. Я склоняюсь перед вами! Вы не только попадаете в точку, но и делаете так, чтобы это выглядело просто (что обычно является признаком хорошего учителя). Большое спасибо. 🙂

2. @Alpha — Спасибо, я рад, что смог помочь.

Ответ №2:

Как только вы реализуете агрегатную функцию CLR ниже, вы можете сделать:

 SELECT dbo.ReplaceAgg(t.[text], w.badword, w.goodword) // call CLR aggregate function
FROM [Texts] t CROSS JOIN BadWords w
GROUP BY t.[text]
 

Агрегатная функция CLR в C#

 /// <summary>
/// Allows to apply regex-replace operations to the same string.
/// For example:
/// SELECT dbo.ReplaceAgg(t.[text], w.badpattern, "...") 
/// FROM [Texts] t CROSS JOIN BadPatterns w
/// GROUP BY t.[text]
/// </summary>
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined, 
    IsInvariantToDuplicates = true, IsInvariantToOrder = false, 
    IsInvariantToNulls = true, MaxByteSize = -1)]
public class RegexReplaceAgg : IBinarySerialize
{
    private string str;
    private string needle;
    private string replacement;
    public void Init()
    {
        str = null;
        needle = null;
        replacement = null;
    }
    public void Accumulate(SqlString haystack, SqlString needle, SqlString replacement)
    {
        // Null values are excluded from aggregate.
        if (needle.IsNull) return;
        if (replacement.IsNull) return;
        if (haystack.IsNull) return;
        str = str ?? haystack.Value;
        this.needle = needle.Value;
        this.replacement = replacement.Value;
        str = Regex.Replace(str, this.needle, this.replacement, RegexOptions.Compiled | RegexOptions.CultureInvariant);
    }

    public void Merge(RegexReplaceAgg group)
    {
        Accumulate(group.Terminate(), new SqlString(needle), new SqlString(replacement));
    }

    public SqlString Terminate() => new SqlString(str);

    public void Read(BinaryReader r)
    {
        str = r.ReadString();
        needle = r.ReadString();
        replacement = r.ReadString();
    }

    public void Write(BinaryWriter w)
    {
        w.Write(str);
        w.Write(needle);
        w.Write(replacement);
    }
}
 

Ответ №3:

Возможно, вам придется написать скалярную функцию, которой вы передаете исходную строку, и достаточно информации, чтобы она знала, какие строки нужно удалить, и чтобы она перебирала их и возвращала результат набора замен.