2

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

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
4

2 回答 2

2

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

然后,因为您已经有很多记录,您应该更新它们。在这里,您必须选择创建一个内部函数并将其用于更新当前记录(它可能很慢,但一旦结束,您的表中就会有数据)或创建CLR过程并使用常用表达。

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

于 2012-11-13T07:17:36.147 回答
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

是的,我完全期待一两次投票,但考虑到 UDF 和递归 CTE 在大型数据集上有时会非常慢,这种方法有时可以很好地工作。不时发布现成的解决方案很有趣。

无论如何,我很想知道这将如何运行,特别是如果结合@gotqn 提出的存储和基于触发器的更新的建议(我同意并赞成)。

我目前在一个普通的盒子上运行了大约 3 秒,有 275 个替换词和 100k 个地址。

于 2012-11-13T21:58:11.403 回答