SQL循环多子串替换

SQL循环多子串替换,sql,sql-server,Sql,Sql Server,我需要构建一个函数来替换表中所有行的多个子字符串 性能不是一个大问题,因为这是一个一次性操作,但有48个映射和大约30000行。我知道在整个数据库上循环48次是相当愚蠢的,但SQL不是我的舵手。如果是java或C++,那就是蛋糕。 基本上,我需要以下函数的SQL模拟。如果SQL不能短路循环,那就好了。我见过SQLReplace函数,但将其正确封装在用户定义的函数中是我的主要障碍 我使用的是微软SQL Server,如果这会产生任何特殊的怪癖的话 mapping[] maps = { {" st

我需要构建一个函数来替换表中所有行的多个子字符串

性能不是一个大问题,因为这是一个一次性操作,但有48个映射和大约30000行。我知道在整个数据库上循环48次是相当愚蠢的,但SQL不是我的舵手。如果是java或C++,那就是蛋糕。 基本上,我需要以下函数的SQL模拟。如果SQL不能短路循环,那就好了。我见过SQLReplace函数,但将其正确封装在用户定义的函数中是我的主要障碍

我使用的是微软SQL Server,如果这会产生任何特殊的怪癖的话

mapping[] maps = { {" st ", " Street "}, {" st. ", " Street "}, ...};

for(row r : table) {
    String orig = r.data(colName);
    for(mapping m : maps) {
        r.data(colName).replace(m.first, m.second);
        if(r.data(colName) != orig)
            break;
    }
}

@霍根的想法是对的。此语法应该更接近于工作:

WITH map as (
      SELECT v.*
      FROM (VALUES (' st ', ' Street ', 1),
                   (' st. ', ' Street ', 2)
           ) v(str, repstr, n)
     ),
     cte as (
      SELECT replace(t.field, map.str, map.repstr) as field, map.n as n
      FROM t JOIN
           map
           ON map.n = 1
      UNION ALL
      SELECT replace(cte.field, map.str, map.repstr) as field, map.n + 1
      FROM cte JOIN
           map
           ON map.n = cte.n + 1
     )
SELECT field 
FROM (SELECT cte.*, MAX(cte.n) OVER (PARTITION BY cte.field) as maxn
      FROM cte
     ) x
WHERE n = maxn;
您可能希望在原始表的CTE中包含更多字段

CREATE FUNCTION [dbo].[StandardizeAddress](@address varchar(123))
RETURNS varchar(250)
WITH SCHEMABINDING
AS
BEGIN
    RETURN
        REPLACE(REPLACE(
                @address + ' '
                , ' st ', ' Street')
                , ' st. ', ' Street ')
END
我们就是这样创建一个标量函数的。使用上面的代码从一个171000行的表中计算地址需要240毫秒。使用我们的实际函数,它有80多个替换,并且对171000行执行一些其他操作需要5秒钟。但是,我们存储地址的标准化版本,因为为了性能起见,我们正在进行复杂的人员搜索和预计算标准值。因此,当添加行或修改地址时,该函数只运行一次,因此该函数的速度不是问题

相比之下,Gordon的解决方案对同一数据集需要4.5秒(而链式替换则需要240毫秒)。更换4次而不是两次,CTE解决方案需要7.8秒,而更换需要275毫秒

我需要添加一个警告,即可以嵌套多少个函数调用是有限制的。根据stackOverflow的另一个问题,限制是244,这比递归CTE的默认最大递归限制大很多

另一个比嵌套替换函数慢一点(大约多75%的时间)的选项如下:

select c3.address from (select REPLACE(@address, ' st ', ' Street ') address) c1
        cross apply (select REPLACE(c1.address, ' st. ', ' Street ') address) c2
        cross apply (select REPLACE(c2.address, ' dr ', ' Drive ') address) c3
但我看不出有什么好处。您也可以编写一个c#CLR函数,但我怀疑调用开销可能会使它比只使用嵌套的REPLACE调用慢

编辑-
自从发布这篇文章后,我发布了一个类似于嵌套替换的速度标准,但代码更干净(更易于维护)。

您在maps中拥有的东西是常量(即文字)还是动态的,比如从另一个表中?您试图更新的模式是什么?你能把表格定义和一些样本数据放在前后吗?这里没有足够的信息来真正回答您的问题。如果您不关心性能,为什么需要在“更好的”sql中这样做?您知道有一个循环可以完全满足您的需要--只需使用它。在不了解更多信息的情况下,只需将调用链接到
REPLACE
,ex)
REPLACE(REPLACE(colName,'街','街','街')
@hatchet-我不认为用多重替换对其进行硬编码是他所要求的——你也没有——这就是为什么你要添加注释而不是回答。既然速度不是一个很大的问题,那么使用映射表和递归清理的解决方案不是更容易维护吗?我们的版本很容易维护。而且我们不必担心递归限制。大多数开发人员理解REPLACE。没有那么多人理解递归通用表表达式。没错,SQL Server有递归限制。您不需要人们查看CTE——只需要一个带有
REPLACE\u target
REPLACE\u和
REPLACE\u的映射表。嗯,REPLACE版本n是一个数量级,使用少量映射(可能使用大量映射)更快,易于维护,易于理解,并且可以工作。这对我来说已经足够好了。当您从Gordon的解决方案中的cte order BY n DESC将最终选择更改为
select TOP 1字段时?