Как заменить несколько символов в SQL?

Это основано на аналогичном вопросе Как заменить несколько символов в Access SQL?

Я написал это, так как sql server 2005, похоже, имеет ограничение на функцию replace() до 19 замен внутри предложения where.

У меня есть следующая задача: нужно выполнить сопоставление в столбце и повысить шансы на то, что совпадение удалит несколько ненужных символов с помощью функции replace()

DECLARE @es NVarChar(1) SET @es = ''
DECLARE @p0 NVarChar(1) SET @p0 = '!'
DECLARE @p1 NVarChar(1) SET @p1 = '@'
---etc...

SELECT *
FROM t1,t2 
WHERE  REPLACE(REPLACE(t1.stringkey,@p0, @es), @p1, @es) 
     = REPLACE(REPLACE(t2.stringkey,@p0, @es), @p1, @es)    
---etc 

Если в предложении where есть> 19 REPLACE(), это не работает. Итак, решение, которое я придумал, состоит в том, чтобы создать функцию sql с именем trimChars в этом примере (извините, они начинаются с @22

CREATE FUNCTION [trimChars] (
   @string varchar(max)
) 

RETURNS varchar(max) 
AS
BEGIN

DECLARE @es NVarChar(1) SET @es = ''
DECLARE @p22 NVarChar(1) SET @p22 = '^'
DECLARE @p23 NVarChar(1) SET @p23 = '&'
DECLARE @p24 NVarChar(1) SET @p24 = '*'
DECLARE @p25 NVarChar(1) SET @p25 = '('
DECLARE @p26 NVarChar(1) SET @p26 = '_'
DECLARE @p27 NVarChar(1) SET @p27 = ')'
DECLARE @p28 NVarChar(1) SET @p28 = '`'
DECLARE @p29 NVarChar(1) SET @p29 = '~'
DECLARE @p30 NVarChar(1) SET @p30 = '{'

DECLARE @p31 NVarChar(1) SET @p31 = '}'
DECLARE @p32 NVarChar(1) SET @p32 = ' '
DECLARE @p33 NVarChar(1) SET @p33 = '['
DECLARE @p34 NVarChar(1) SET @p34 = '?'
DECLARE @p35 NVarChar(1) SET @p35 = ']'
DECLARE @p36 NVarChar(1) SET @p36 = '\'
DECLARE @p37 NVarChar(1) SET @p37 = '|'
DECLARE @p38 NVarChar(1) SET @p38 = '<'
DECLARE @p39 NVarChar(1) SET @p39 = '>'
DECLARE @p40 NVarChar(1) SET @p40 = '@'
DECLARE @p41 NVarChar(1) SET @p41 = '-'

return   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       @string, @p22, @es), @p23, @es), @p24, @es), @p25, @es), @p26, @es), @p27, @es), @p28, @es), @p29, @es), @p30, @es), @p31, @es), @p32, @es), @p33, @es), @p34, @es), @p35, @es), @p36, @es), @p37, @es), @p38, @es), @p39, @es), @p40, @es), @p41, @es)
END 

Затем это можно использовать в дополнение к другим строкам замены.

SELECT *
FROM t1,t2 
WHERE  trimChars(REPLACE(REPLACE(t1.stringkey,@p0, @es), @p1, @es) 
         = REPLACE(REPLACE(t2.stringkey,@p0, @es), @p1, @es))   

Я создал еще несколько функций для аналогичной замены, например trimChars(trimMoreChars(

SELECT *
FROM t1,t2 
WHERE  trimChars(trimMoreChars(REPLACE(REPLACE(t1.stringkey,@p0, @es), @p1, @es) 
         = REPLACE(REPLACE(t2.stringkey,@p0, @es), @p1, @es)))

Может ли кто-нибудь дать мне лучшее решение этой проблемы с точки зрения производительности и, возможно, более чистой реализации?


person kiev    schedule 16.10.2009    source источник
comment
Можете ли вы дезинфицировать свой ввод перед передачей его в базу данных?   -  person Juliet    schedule 16.10.2009
comment
Насколько велики таблицы t1 и t2? Возможна ли работа вне базы данных? Похоже на работу для регулярных выражений.   -  person Eric H    schedule 16.10.2009


Ответы (10)


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

person Cade Roux    schedule 16.10.2009

Одним из полезных приемов в SQL является возможность использовать @var = function(...) для присвоения значения. Если у вас есть несколько записей в вашем наборе записей, ваша переменная назначается несколько раз с побочными эффектами:

declare @badStrings table (item varchar(50))

INSERT INTO @badStrings(item)
SELECT '>' UNION ALL
SELECT '<' UNION ALL
SELECT '(' UNION ALL
SELECT ')' UNION ALL
SELECT '!' UNION ALL
SELECT '?' UNION ALL
SELECT '@'

declare @testString varchar(100), @newString varchar(100)

set @teststring = 'Juliet ro><0zs my s0x()rz!!?!one!@!@!@!'
set @newString = @testString

SELECT @newString = Replace(@newString, item, '') FROM @badStrings

select @newString -- returns 'Juliet ro0zs my s0xrzone'
person Juliet    schedule 16.10.2009
comment
Это очень круто - как включить это в предложение where в моем вопросе выше? - Благодарность - person kiev; 17.10.2009
comment
@kiev: вы не можете поместить это в предложение WHERE. - person Peter Radocchia; 17.10.2009
comment
@kiev: создание определяемой пользователем функции - правильный подход. Однако вам лучше использовать мой подход, а не вкладывать миллиарды замен друг в друга, поскольку мой подход поддерживает неопределенное количество замен. Вы можете сделать функцию более динамичной, передав разделенный запятыми список строк для замены, используя функцию разделения (sqlteam.com/forums/topic.asp?TOPIC_ID=50648), чтобы преобразовать список в таблицу, а затем вернуть замененную строку. - person Juliet; 17.10.2009

Мне очень нравится решение @Juliett! Я бы просто использовал CTE, чтобы получить все недопустимые символы:

DECLARE @badStrings VARCHAR(100)
DECLARE @teststring VARCHAR(100)

SET @badStrings = '><()!?@'
SET @teststring = 'Juliet ro><0zs my s0x()rz!!?!one!@!@!@!'

;WITH CTE AS
(
  SELECT SUBSTRING(@badStrings, 1, 1) AS [String], 1 AS [Start], 1 AS [Counter]
  UNION ALL
  SELECT SUBSTRING(@badStrings, [Start] + 1, 1) AS [String], [Start] + 1, [Counter] + 1 
  FROM CTE 
  WHERE [Counter] < LEN(@badStrings)
)

SELECT @teststring = REPLACE(@teststring, CTE.[String], '') FROM CTE

SELECT @teststring

Джульетта ro0zs моя s0xrzone

person Duanne    schedule 20.03.2015
comment
Это решение сломало мой awesometer. Использует рекурсивный CTE, а затем рекурсивный REPLACE @teststring... - person Baodad; 23.06.2015
comment
Это круто. Будьте осторожны, потому что это не приведет к появлению пробела в @badStrings. - person Mike T.; 14.03.2016
comment
Можно ли применить это решение к выбору таблицы? IE Выберите поле, где значение в ('1', '2') выберет поле '‹›!1' - person Adam; 12.05.2017

Я предлагаю вам создать скалярную пользовательскую функцию. Это пример (извините заранее, потому что имена переменных на испанском языке):

CREATE FUNCTION [dbo].[Udf_ReplaceChars] (
  @cadena VARCHAR(500),  -- String to manipulate
  @caracteresElim VARCHAR(100),  -- String of characters to be replaced
  @caracteresReem VARCHAR(100)   -- String of characters for replacement
) 
RETURNS VARCHAR(500)
AS
BEGIN
  DECLARE @cadenaFinal VARCHAR(500), @longCad INT, @pos INT, @caracter CHAR(1), @posCarER INT;
  SELECT
    @cadenaFinal = '',
    @longCad = LEN(@cadena),
    @pos = 1;

  IF LEN(@caracteresElim)<>LEN(@caracteresReem)
    BEGIN
      RETURN NULL;
    END

  WHILE @pos <= @longCad
    BEGIN
      SELECT
        @caracter = SUBSTRING(@cadena,@pos,1),
        @pos = @pos + 1,
        @posCarER = CHARINDEX(@caracter,@caracteresElim);

      IF @posCarER <= 0
        BEGIN
          SET @cadenaFinal = @cadenaFinal + @caracter;
        END
      ELSE
        BEGIN
          SET @cadenaFinal = @cadenaFinal + SUBSTRING(@caracteresReem,@posCarER,1)
        END
    END

  RETURN @cadenaFinal;
END

Вот пример использования этой функции:

SELECT dbo.Udf_ReplaceChars('This is a test.','sat','Z47');

И результат: 7hiZ iZ 4 7eZ7.

Как видите, каждый символ параметра @caracteresElim заменяется символом в той же позиции из параметра @caracteresReem.

person Guillermo Gutiérrez    schedule 18.01.2013

Хотя этот вопрос был задан о SQL Server 2005, стоит отметить, что с Sql Server 2017 запрос можно выполнить с помощью новой функции TRANSLATE.

https://docs.microsoft.com/en-us/sql/t-sql/functions/translate-transact-sql

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

person Ethan1701    schedule 14.02.2018

У меня была разовая проблема переноса данных, когда исходные данные не могли правильно выводить некоторые необычные/технические символы, а также вездесущие лишние запятые в CSV.

Мы решили, что для каждого такого символа исходный экстракт должен заменить их чем-то, что было бы распознаваемо как исходной системой, так и SQL Server, который их загружал, но в противном случае не было бы в данных.

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

create function [dbo].[udf_ReplaceMultipleChars]
(
    @OriginalString nvarchar(4000)
  , @ReplaceTheseChars nvarchar(100)
  , @LengthOfReplacement int = 1
)
returns nvarchar(4000)
begin

    declare @RevisedString nvarchar(4000) = N'';
    declare @lengthofinput int =
            (
            select len(@OriginalString)
            );

with AllNumbers
as (select 1 as  Number
    union all
    select Number + 1
    from AllNumbers
    where Number < @lengthofinput)
select @RevisedString += case
                             when (charindex(substring(@OriginalString, Number, 1), @ReplaceTheseChars, 1) - 1) % 2
    = 0 then
                                 substring(
                                              @ReplaceTheseChars
                                            , charindex(
                                                           substring(@OriginalString, Number, 1)
                                                         , @ReplaceTheseChars
                                                         , 1
                                                       ) + 1
                                            , @LengthOfReplacement
                                          )
                             else
                                 substring(@OriginalString, Number, 1)
                         end
    from AllNumbers
    option (maxrecursion 4000);
    return (@RevisedString);
end;

Он работает, отправляя как оцениваемую строку, так и символы для замены (@OriginalString) вместе со строкой парных символов, где первый символ должен быть заменен вторым, третий - четвертым, пятый - шестым и т. д. на (@ReplaceTheseChars).

Вот строка символов, которые мне нужно было заменить, и их замены... [']"~,{Ø}°$±|¼¦¼ª½¬½^¾#✓

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

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

Условие изложения случая имеет важное значение. Это гарантирует, что он заменяет символ только в том случае, если он найден в вашей переменной @ReplaceTheseChars, и что символ должен быть найден в позиции с нечетным номером (минус 1 из результата charindex гарантирует, что все, что НЕ найдено, возвращает отрицательное значение по модулю). то есть, если вы найдете тильду (~) в позиции 5, она заменит ее запятой, но если при последующем запуске она найдет запятую в позиции 6, она не заменит ее фигурной скобкой ({).

Лучше всего это можно продемонстрировать на примере...

declare @ProductDescription nvarchar(20) = N'abc~def[¦][123';
select @ProductDescription
= dbo.udf_ReplaceMultipleChars(
                                  @ProductDescription
/* NB the doubling up of the apostrophe is necessary in the string but resolves to a single apostrophe when passed to the function */
                                ,'['']"~,{Ø}°$±|¼¦¼ª½¬½^¾#✓' 
                                , default
                              );
select @ProductDescription
 , dbo.udf_ReplaceMultipleChars(
                                   @ProductDescription
                                 ,'['']"~,{Ø}°$±|¼¦¼ª½¬½^¾#✓'
/* if you didn't know how to type those peculiar chars in then you can build a string like  this... '[' + nchar(0x0027) + ']"~,{' + nchar(0x00D8) + '}' + nchar(0x00B0) etc */
                                ,
                                 default
                               );

Это вернет значение как после первого прохода через функцию, так и во второй раз следующим образом... abc,def'¼"'123 abc,def'¼"'123

Обновление таблицы будет просто

update a
set a.Col1 = udf.ReplaceMultipleChars(a.Col1,'~,]"',1)
from TestTable a

Наконец (я слышал, вы говорите!), хотя у меня не было доступа к функции перевода, я считаю, что эта функция может довольно легко обработать пример, показанный в документации. Демонстрация функции ПЕРЕВОД

SELECT TRANSLATE('2*[3+4]/{7-2}', '[]{}', '()()');

который возвращает 2*(3+4)/(7-2), хотя я понимаю, что это может не работать на 2*[3+4]/[7-2] !!

Моя функция подошла бы к этому следующим образом, перечислив каждый заменяемый символ, за которым следует его замена [ --> (, { --> ( и т.

select dbo.udf_ReplaceMultipleChars('2*[3+4]/{7-2}', '[({(])})', 1);

который также будет работать для

select dbo.udf_ReplaceMultipleChars('2*[3+4]/[7-2]', '[({(])})', 1);

Я надеюсь, что кто-то найдет это полезным, и если вы сможете проверить его производительность на больших таблицах, сообщите нам об этом так или иначе!

person Chloe Williams    schedule 28.03.2019
comment
На stackoverflow.com/a/55906638/5736369 есть отличный пример, который может работать. REPLACE(TRANSLATE(T2.[ShipToCode], '[];'',$@', '#######'), '#', '') - person T.S; 06.10.2020

Одним из вариантов является использование таблицы чисел/подсчета для управления итеративным процессом с помощью запроса на основе псевдомножества.

Общую идею замены символов можно продемонстрировать с помощью простого подхода к таблице символов:

create table charMap (srcChar char(1), replaceChar char(1))
insert charMap values ('a', 'z')
insert charMap values ('b', 'y')


create table testChar(srcChar char(1))
insert testChar values ('1')
insert testChar values ('a')
insert testChar values ('2')
insert testChar values ('b')

select 
coalesce(charMap.replaceChar, testChar.srcChar) as charData
from testChar left join charMap on testChar.srcChar = charMap.srcChar

Затем вы можете использовать подход таблицы подсчета, чтобы выполнить поиск по каждой позиции символа в строке.

create table tally (i int)
declare @i int
set @i = 1
while @i <= 256 begin
    insert tally values (@i)
    set @i = @i + 1
end

create table testData (testString char(10))
insert testData values ('123a456')
insert testData values ('123ab456')
insert testData values ('123b456')

select
    i,
    SUBSTRING(testString, i, 1) as srcChar,
    coalesce(charMap.replaceChar, SUBSTRING(testString, i, 1)) as charData
from testData cross join tally
    left join charMap on SUBSTRING(testString, i, 1) = charMap.srcChar
where i <= LEN(testString)
person ahains    schedule 16.10.2009

Я не знаю, почему Чарльз Бретана удалил свой ответ, поэтому я добавляю его обратно в качестве ответа CW, но сохраняемый вычисляемый столбец - это ДЕЙСТВИТЕЛЬНО хороший способ справиться с этими случаями, когда вам почти все время нужны очищенные или преобразованные данные. , но нужно сохранить оригинальный мусор. Его предложение актуально и уместно НЕЗАВИСИМО от того, как вы решите очистить свои данные.

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

person Community    schedule 17.10.2009

Вот шаги

  1. Создайте функцию CLR

См. следующий код:

public partial class UserDefinedFunctions 
{

[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString Replace2(SqlString inputtext, SqlString filter,SqlString      replacewith)
{

    string str = inputtext.ToString();
    try
    {
        string pattern = (string)filter;
        string replacement = (string)replacewith;
        Regex rgx = new Regex(pattern);
        string result = rgx.Replace(str, replacement);
        return (SqlString)result;

    }
    catch (Exception s)
    {
        return (SqlString)s.Message;
    }
}
}
  1. Разверните свою функцию CLR

  2. Теперь проверьте это

См. следующий код:

create table dbo.test(dummydata varchar(255))
Go
INSERT INTO dbo.test values('P@ssw1rd'),('This 12is @test')
Go
Update dbo.test
set dummydata=dbo.Replace2(dummydata,'[0-9@]','')

select * from dbo.test
dummydata, Psswrd, This is test booom!!!!!!!!!!!!!
person HimalayanNinja    schedule 27.09.2013

Вот современное решение с использованием STRING_SPLIT очень кратко. Недостатком является то, что вам нужна по крайней мере версия SQL Server 2016, работающая на уровне совместимости 130.

Declare @strOriginal varchar(100) = 'Juliet ro><0zs my s0x()rz!!?!one!@!@!@!'
Declare @strModified varchar(100) = @strOriginal
Declare @disallowed  varchar(100) = '> < ( ) ! ? @'

Select 
   @strModified = Replace(@strModified, value, '') 
From 
   String_Split(@disallowed,' ')

Select @strModified

Он возвращает:

Juliet ro0zs my s0xrzone
person Tony    schedule 06.04.2021