SQL中的大规模单词替换

时间:2012-11-13 01:20:53

标签: sql-server tsql

我试图在大型数据库中的字段上执行数据清理。我有一个参考表,其中包含带有替换的单词,如果您愿意,还包含宏。我想以最有效的方式将这些更改应用于包含数百万行的表。话虽如此,让我在下面提供一些虚拟数据,以便您可以看到过程:

Street_Addresses表:

Street_Name       | Expanded_Name
------------------+--------------
100 Main St Ste 5 | NULL
25 10th Ave Apt 2 | NULL
75 Bridge Rd      | NULL

Word_Substitutions表:

Word | Replacement
-----+------------
St   | Street
Ave  | Avenue
Rd   | Road
Ste  | Suite
Apt  | Apartment

因此最终结果将是更新后的结果:

Street_Name       | Expanded_Name
------------------+--------------
100 Main St Ste 5 | 100 Main Street Suite 5
25 10th Ave Apt 2 | 25 10th Avenue Apartment 2
75 Bridge Rd      | 75 Bridge Road

这里的挑战是需要进行的大量替换,实际上是单个值的多次替换。想到的最初的想法是使用标量函数来封装这个逻辑。但是你可以想象,这不是数百万行的表现。

CREATE FUNCTION Substitute_Words (@Text varchar(MAX))
RETURNS varchar(MAX) AS
BEGIN
    SELECT @Text = REPLACE(' ' + @Text + ' ', ' ' + Word + ' ',
    ' ' + Replacement + ' ') FROM Word_Substitutions
    RETURN LTRIM(RTRIM(@Text))
END

我决定改为查看基于集合的操作,并提出以下建议:

WHILE (1 = 1)
BEGIN
    UPDATE A SET Expanded_Name = LTRIM(RTRIM(REPLACE(
    ' ' + ISNULL(A.Expanded_Name, A.Street_Name) + ' ',
    ' ' + W.Word + ' ', ' ' + W.Replacement + ' ')))
    FROM Street_Addresses AS A
    CROSS APPLY (SELECT TOP 1 Word, Replacement
    FROM Word_Substitutions WHERE CHARINDEX(' ' + Word + ' ',
    ' ' + ISNULL(A.Expanded_Name, A.Street_Name) + ' ') > 0) AS W

    IF (@@ROWCOUNT = 0)
        BREAK
END

现在,根据我的实际数据集大约需要2个小时,如果可能,我想减少这个数据 - 是否有人有优化建议?

更新

通过仅使用内部联接,我能够将执行时间减少到大约5分钟。我最初认为使用带有返回多行的内连接的更新是行不通的。似乎更新仍然有效,但源行将获得单个而不是多个更新。显然,SQL Server为更新选择一个随机结果行,丢弃其他行。

WHILE (1 = 1)
BEGIN
    UPDATE A SET Expanded_Name = LTRIM(RTRIM(REPLACE(
    ' ' + ISNULL(A.Expanded_Name, A.Street_Name) + ' ',
    ' ' + W.Word + ' ', ' ' + W.Replacement + ' ')))
    FROM Street_Addresses AS A
    INNER JOIN Word_Substitutions AS W ON CHARINDEX(' ' + W.Word + ' ',
    ' ' + ISNULL(A.Expanded_Name, A.Street_Name) + ' ') > 0

    IF (@@ROWCOUNT = 0)
        BREAK
END

2 个答案:

答案 0 :(得分:2)

我认为这里最好的方法是将修改后的数据存储在数据库中。您可以使用ID和格式化地址创建单独的表,也可以在当前表中添加其他列。

然后,因为你已经有很多记录,你应该更新它们。在这里,我需要选择,创建内部函数并将其用于更新当前记录(可能很慢,但一旦结束,您将在表中已有数据)或创建CLR过程和使用正则表达式的力量。

然后,对于新插入的记录,创建AFTER INSERT TRIGGER将非常灵活,它将调用您的SQL或CLR函数并更新当前插入的记录。

答案 1 :(得分:2)

你总是可以做一些荒谬的事情并将其作为动态SQL运行,并且所有替换内联:

declare @sql nvarchar(max)
set @sql = 'Street_Name'
select @sql = 'replace(' + @sql + ', '' ' + Word + ' '', '' ' + Replacement + ' '')'
from Word_Substitutions
set @sql = 'update Street_Addresses set Expanded_Name = ' + @sql
exec sp_executesql @sql

是的,我完全期待一个或两个downvote,但是这种方法在某些情况下可以正常工作,因为UDF和递归CTE在大型数据集上有时会非常慢。并且不时发布离墙解决方案很有趣。

无论如何,我很想知道这会如何运行,特别是如果结合@gotqn存储和基于触发器的更新的建议(我同意并且已经投票)。

我目前正在运行大约3秒钟,在一个适度的盒子上有275个替换单词和100k地址。