如何在SQL Server中使用查找表替换列中的子字符串

时间:2018-08-14 07:16:38

标签: sql sql-server tsql sql-update lookup-tables

我有一个表,其中有两列,其数据代码点如。这些代码点需要用日语字符更改。我有一个包含日语字符的代码点的查找表。但是问题是在两列中单行中都有多个代码点。

主表:-

Id    body                                      subject
1    <U+9876> Hi <U+1234>No <U+6543>           <U+9876> Hi <U+1234>No <U+6543>
2    <U+9826> <U+5678><U+FA32> data            <U+9006> <U+6502>

查询表:-

char     value
<U+9876>  だ
<U+9826>  づ

我尝试在内部联接中使用like运算符创建一个更新查询,但是这很耗时,因为我们在主表中有14k行,在查找表中有6k值。

2 个答案:

答案 0 :(得分:1)

您可以创建一个自定义函数,该函数循环遍历任何替换其代码点的文本。

CREATE FUNCTION DecodeString( @STRING nvarchar(1000) )
RETURNS nvarchar(1000)
AS
BEGIN
  DECLARE @POS int
  DECLARE @CODE nvarchar(20)

  SET @POS = CHARINDEX('<', @STRING);
  WHILE @POS > 0 BEGIN
    SET @CODE = SUBSTRING(@STRING, @POST , CHARINDEX('>', @STRING) - @POS + 1);

    SELECT @STRING = REPLACE(@STRING, @CODE, VALUE)
    FROM MYLOOKUPTABLE 
    WHERE CHAR = @CODE;

    SET @POS = CHARINDEX('<', @STRING);
  END

  RETURN @STRING;
END
GO

现在,您可以使用该函数来获取或更新结果字符串,并且它将仅在每个字符串上查找所需的键。

SELECT Body, DecodeString(Body) as JapaneseBody,
       Subject, DecodeString(Subject) as JapaneseSubject
FROM MYTABLE

请记住,在查找表的“ char”列上有一个索引,因此这些搜索将是最佳的。

答案 1 :(得分:0)

如果性能真的很重要,则需要预先实现数据。这可以通过创建单独的表并使用触发器或修改填充原始表的例程来完成。如果您没有按批次插入/更新记录,则不会损害CRUD的执行时间。

您可以轻松创建美观的T-SQL短语句来构建用于执行6K更新的动态代码,因此您也可以尝试一下-不要使用LIKE或复杂的条件-只需简单的{每个查找值的{1}}条语句。

在某些情况下,我使用SQL CLR函数进行此类替换。例如:

UPDATE-REPLACE

enter image description here

我在下面向您显示代码,但这只是一个想法。您可以自由地自行实现满足您的性能要求的东西。

Here,您将看到如何创建SQL CLR函数。这是聚合函数与订单连接的一种变体:

DECLARE @Main TABLE
(
    [id] TINYINT
   ,[body] NVARCHAR(MAX)
   ,[subject] NVARCHAR(MAX)
);

DECLARE @Lookup TABLE
(
    [id] TINYINT -- you can use row_number to order
   ,[char] NVARCHAR(32)
   ,[value] NVARCHAR(32)
);

INSERT INTO @Main ([id], [body], [subject])
VALUES (1, '<U+9876> Hi <U+1234>No <U+6543>', '<U+9876> Hi <U+1234>No <U+6543>')
      ,(2, '<U+9826> <U+5678><U+FA32> data', '<U+9006> <U+6502>');

INSERT INTO @Lookup ([id], [char], [value])
VALUES (1, '<U+9876>', N'だ')
      ,(2, '<U+9826>', N'づ');

DECLARE @Pattern NVARCHAR(MAX)
       ,@Replacement NVARCHAR(MAX);

SELECT @Pattern = [dbo].[ConcatenateWithOrderAndDelimiter] ([id], [char], '|')
      ,@Replacement = [dbo].[ConcatenateWithOrderAndDelimiter] ([id], [value], '|')
FROM @Lookup;


UPDATE @Main
SET [body] = [dbo].[fn_Utils_ReplaceStrings] ([body], @Pattern, @Replacement, '|')
   ,[subject] = [dbo].[fn_Utils_ReplaceStrings] ([subject], @Pattern, @Replacement, '|');

 SELECT [id]
       ,[body]
       ,[subject]
 FROM @Main;        

这是执行替换的功能的一种变体:

[Serializable]
[
    Microsoft.SqlServer.Server.SqlUserDefinedAggregate
    (
        Microsoft.SqlServer.Server.Format.UserDefined,
        IsInvariantToNulls = true,
        IsInvariantToDuplicates = false,
        IsInvariantToOrder = false,
        IsNullIfEmpty = false,
        MaxByteSize = -1
    )
]
/// <summary>
/// Concatenates <int, string, string> values defining order using the specified number and using the given delimiter
/// </summary>
public class ConcatenateWithOrderAndDelimiter : Microsoft.SqlServer.Server.IBinarySerialize
{
    private List<Tuple<int, string>> intermediateResult;
    private string delimiter;
    private bool isDelimiterNotDefined;

    public void Init()
    {
        this.delimiter = ",";
        this.isDelimiterNotDefined = true;
        this.intermediateResult = new List<Tuple<int, string>>();
    }

    public void Accumulate(SqlInt32 position, SqlString text, SqlString delimiter)
    {
        if (this.isDelimiterNotDefined)
        {
            this.delimiter = delimiter.IsNull ? "," : delimiter.Value;
            this.isDelimiterNotDefined = false;
        }

        if (!(position.IsNull || text.IsNull))
        {
            this.intermediateResult.Add(new Tuple<int, string>(position.Value, text.Value));
        }
    }

    public void Merge(ConcatenateWithOrderAndDelimiter other)
    {
        this.intermediateResult.AddRange(other.intermediateResult);
    }

    public SqlString Terminate()
    {
        this.intermediateResult.Sort();
        return new SqlString(String.Join(this.delimiter, this.intermediateResult.Select(tuple => tuple.Item2)));
    }

    public void Read(BinaryReader r)
    {
        if (r == null) throw new ArgumentNullException("r");

        int count = r.ReadInt32();
        this.intermediateResult = new List<Tuple<int, string>>(count);

        for (int i = 0; i < count; i++)
        {
            this.intermediateResult.Add(new Tuple<int, string>(r.ReadInt32(), r.ReadString()));
        }

        this.delimiter = r.ReadString();
    }

    public void Write(BinaryWriter w)
    {
        if (w == null) throw new ArgumentNullException("w");

        w.Write(this.intermediateResult.Count);
        foreach (Tuple<int, string> record in this.intermediateResult)
        {
            w.Write(record.Item1);
            w.Write(record.Item2);
        }
        w.Write(this.delimiter);
    }
}

或这一个,但使用正则表达式:

[SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)]
public static SqlString ReplaceStrings( SqlString input, SqlString pattern, SqlString replacement, SqlString separator ){
    string output = null;
    if(
        input.IsNull == false
        && pattern.IsNull == false
        && replacement.IsNull == false
    ){
        StringBuilder tempBuilder = new StringBuilder( input.Value );

        if( separator.IsNull || String.IsNullOrEmpty( separator.Value ) ){
            tempBuilder.Replace( pattern.Value, replacement.Value );
        }
        else{
            //both must have the exact number of elements
            string[] vals = pattern.Value.Split( new[]{separator.Value}, StringSplitOptions.None ),
                newVals = replacement.Value.Split( new[]{separator.Value}, StringSplitOptions.None );

            for( int index = 0, count = vals.Length; index < count; index++ ){
                tempBuilder.Replace( vals[ index ], newVals[ index ] );
            }
        }

        output = tempBuilder.ToString();
    }

    return output;
}