web-dev-qa-db-ja.com

SQLで複数の文字を置き換える方法

これは、同様の質問に基づいています Access SQLで複数の文字を置換する方法?

SQL Server 2005のwhere()句内のreplace()関数には19個の置換があるという制限があるため、これを書きました。

私には次のタスクがあります:列で一致を実行する必要があり、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()がある場合、機能しません。したがって、私が思いついた解決策は、この例でtrimCharsというsql関数を作成することです(@ 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)))

パフォーマンスとよりクリーンな実装の点で、誰かがこの問題のより良い解決策を教えてもらえますか?

34
kiev

CLR UDFの作成代わりに を真剣に検討し、正規表現(文字列とパターンの両方をパラメーターとして渡すことができます)を使用して、文字の範囲。このSQL UDFを簡単に上回るはずです。

22
Cade Roux

SQLの便利なトリックの1つは、@var = function(...)を使用して値を割り当てる機能です。レコードセットに複数のレコードがある場合、varには複数の副作用が割り当てられます。

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'
48
Juliet

@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

19
Duanne

スカラーのユーザー定義関数を作成することをお勧めします。これは一例です(変数名がスペイン語であるため、前もってごめんなさい):

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パラメーターと同じ位置にある文字に置き換えられます。

ソースデータが異常な/技術的な文字に加えて、CSVのユビキタスな余分なコンマを正しく出力できないという1回限りのデータ移行の問題がありました。

このような各文字について、ソース抽出はそれらを、ソースシステムとそれらをロードしているがデータには含まれないSQL Serverの両方に認識可能なものに置き換える必要があると判断しました。

ただし、さまざまなテーブルのさまざまな列にこれらの置換文字が表示され、それらを置換する必要があることを意味していました。複数のREPLACE関数をネストすると、インポートコードが怖くなり、配置と括弧の数を誤判断するエラーが発生しやすくなるため、次の関数を作成しました。 3,000行のテーブルの列を1秒未満で処理できることは知っていますが、数百万行のテーブルにどれだけ迅速に拡張できるかはわかりません。

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)の両方を送信することで機能します。最初の文字は2番目に、3番目は4番目に、5番目には6番目に、というようにペアの文字列に置き換えます。 on(@ReplaceTheseChars)。

置換する必要のある文字列とその置換... ['] "〜、{Ø}°$±| ¼¦¼ª½¬½ ^¾#✓

すなわち、開き角括弧はアポストロフィを示し、閉じ角括弧は二重引用符を示します。下品な部分と度および直径の記号がそこにあったことがわかります。

長い文字列を置換する必要がある場合の開始点として含まれるデフォルトの@LengthOfReplacementがあります。私はプロジェクトでそれをいじりましたが、単一の文字の置換が主な機能でした。

Caseステートメントの条件は重要です。 @ReplaceTheseChars変数で見つかった場合にのみ文字を置き換え、奇数位置で文字を検出する必要があることを確認します(charindexの結果からマイナス1を指定すると、見つからないものはすべて負のモジュロ値を返します)。つまり、位置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
                               );

これにより、関数を最初に通過した後の値と、次の2回目の値の両方が返されます。abc、def'¼ "'123 abc、def'¼"' 123

テーブルの更新はただ

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

最後に(私はあなたの言うことを聞きます!)、私は翻訳関数にアクセスできませんでしたが、この関数はドキュメントに示されている例を非常に簡単に処理できると信じています。 TRANSLATE関数のデモは

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);

誰かがこれが便利だと思うことを望みます。もしあなたがより大きなテーブルに対してそのパフォーマンスをテストするようになったら、私たちに何らかの形で知らせてください!

2
Chloe Williams
declare @testVal varchar(20)

set @testVal = '?t/es?ti/n*g 1*2?3*'

select @testVal = REPLACE(@testVal, item, '') from (select '?' item union select '*' union select '/') list

select @testVal;
1
Adil

この質問はSQL Server 2005について尋ねられましたが、SQL Server 2017の時点では、新しいTRANSLATE関数を使用して要求を実行できることに注意する価値があります。

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

この情報が将来このページにアクセスする人々に役立つことを願っています。

1
Ethan1701

1つのオプションは、数値/集計テーブルを使用して、疑似セットベースのクエリを介して反復プロセスを実行することです。

文字置換の一般的な考え方は、単純な文字マップテーブルアプローチで実証できます。

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)
0
ahains

Charles Bretanaが答えを削除した理由がわからないので、CWの回答として追加し直していますが、永続化された計算列は、ほとんどの場合、データをクレンジングまたは変換する必要がある場合に対処するための本当に良い方法です、元のゴミを保存する必要があります。彼の提案は、データをクレンジングする方法に関係なく、適切かつ適切です。

具体的には、現在のプロジェクトでは、先行ゼロと矛盾して格納された特定の数値識別子からすべての先行ゼロを削除する永続的な計算列があります(幸いなことに、これは直線T-SQLで実際に簡単に処理されます)。これは、テーブルで永続化された計算列に格納され、インデックスにインデックスが付けられます。これは、その適合識別子が結合でよく使用されるためです。

0
Cade Roux

手順は次のとおりです

  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!!!!!!!!!!!!!
0
HimalayanNinja