You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将表传入地址字符串比较的表值函数以提升性能?

批量地址对比函数改造与性能优化

问题背景

现有AddressCompare表值函数仅支持单条地址对比,需传入两组地址字段(各5个)及ID参数,返回单条匹配结果。处理10万条记录时需调用函数10万次,性能极低。需求将其改造为接收整表作为输入的批量处理函数,支持任意符合结构的输入表(包含两组地址字段:Street1/Street2/City/State/Zip,以及唯一ID字段),一次性输出所有记录的匹配状态。

现有实现的性能瓶颈

  • 逐行调用开销:每条记录单独触发函数调用,累计10万次的上下文切换开销极大
  • 低效的字符串处理:使用WHILE循环去除空格、替换街道后缀,属于逐行迭代操作,无法利用SQL的集合处理优势
  • 多语句表值函数(MS TVF):现有函数是多语句表值函数,SQL优化器无法对其进行查询计划优化,性能远低于内联表值函数(Inline TVF)

改造方案

1. 创建用户定义表类型(UDTT)

先定义符合输入结构的表类型,作为函数的参数:

CREATE TYPE dbo.AddressComparisonInput AS TABLE (
    Street1_Old VARCHAR(64),
    Street2_Old VARCHAR(64),
    City_Old VARCHAR(64),
    State_Old VARCHAR(32),
    Zip_Old VARCHAR(16),
    Street1_New VARCHAR(64),
    Street2_New VARCHAR(64),
    City_New VARCHAR(64),
    State_New VARCHAR(32),
    Zip_New VARCHAR(16),
    Join_Id INT PRIMARY KEY -- 主键提升批量处理性能
);

2. 重写为内联表值函数(Inline TVF)

内联表值函数会被SQL优化器当作视图展开,能充分利用索引和集合处理能力,同时重构所有字符串处理逻辑为集合式操作:

CREATE FUNCTION dbo.AddressCompare_Batch
(
    @InputData dbo.AddressComparisonInput READONLY
)
RETURNS TABLE
AS
RETURN
WITH StandardizedAddresses AS (
    -- 第一步:移除标点、统一大写、初步去空格
    SELECT
        Join_Id,
        -- 标准化旧地址
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Street1_Old, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS Street1_Old_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Street2_Old, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS Street2_Old_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(City_Old, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS City_Old_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(State_Old, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS State_Old_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Zip_Old, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS Zip_Old_Standard,
        -- 标准化新地址
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Street1_New, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS Street1_New_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Street2_New, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS Street2_New_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(City_New, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS City_New_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(State_New, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS State_New_Standard,
        TRIM(UPPER(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Zip_New, '.', ''), '#', ''), '-', ' '), ',', ''), '''', ''), '  ', ' '))) AS Zip_New_Standard
    FROM @InputData
),
RemoveExtraSpaces AS (
    -- 第二步:彻底去除多余空格(用递归CTE替代WHILE循环)
    SELECT
        Join_Id,
        Street1_Old_Standard,
        Street2_Old_Standard,
        City_Old_Standard,
        State_Old_Standard,
        Zip_Old_Standard,
        Street1_New_Standard,
        Street2_New_Standard,
        City_New_Standard,
        State_New_Standard,
        Zip_New_Standard
    FROM StandardizedAddresses
    UNION ALL
    SELECT
        Join_Id,
        REPLACE(Street1_Old_Standard, '  ', ' '),
        REPLACE(Street2_Old_Standard, '  ', ' '),
        REPLACE(City_Old_Standard, '  ', ' '),
        State_Old_Standard,
        Zip_Old_Standard,
        REPLACE(Street1_New_Standard, '  ', ' '),
        REPLACE(Street2_New_Standard, '  ', ' '),
        REPLACE(City_New_Standard, '  ', ' '),
        State_New_Standard,
        Zip_New_Standard
    FROM RemoveExtraSpaces
    WHERE 
        LEN(Street1_Old_Standard) - LEN(REPLACE(Street1_Old_Standard, '  ', ' ')) > 0
        OR LEN(Street2_Old_Standard) - LEN(REPLACE(Street2_Old_Standard, '  ', ' ')) > 0
        OR LEN(City_Old_Standard) - LEN(REPLACE(City_Old_Standard, '  ', ' ')) > 0
        OR LEN(Street1_New_Standard) - LEN(REPLACE(Street1_New_Standard, '  ', ' ')) > 0
        OR LEN(Street2_New_Standard) - LEN(REPLACE(Street2_New_Standard, '  ', ' ')) > 0
        OR LEN(City_New_Standard) - LEN(REPLACE(City_New_Standard, '  ', ' ')) > 0
),
FinalStandardized AS (
    -- 取最终去空格后的结果(递归到无多余空格为止)
    SELECT
        Join_Id,
        TRIM(Street1_Old_Standard) AS Street1_Old_Final,
        TRIM(Street2_Old_Standard) AS Street2_Old_Final,
        TRIM(City_Old_Standard) AS City_Old_Final,
        State_Old_Standard AS State_Old_Final,
        Zip_Old_Standard AS Zip_Old_Final,
        TRIM(Street1_New_Standard) AS Street1_New_Final,
        TRIM(Street2_New_Standard) AS Street2_New_Final,
        TRIM(City_New_Standard) AS City_New_Final,
        State_New_Standard AS State_New_Final,
        Zip_New_Standard AS Zip_New_Final
    FROM RemoveExtraSpaces
    WHERE 
        LEN(Street1_Old_Standard) = LEN(REPLACE(Street1_Old_Standard, '  ', ' '))
        AND LEN(Street2_Old_Standard) = LEN(REPLACE(Street2_Old_Standard, '  ', ' '))
        AND LEN(City_Old_Standard) = LEN(REPLACE(City_Old_Standard, '  ', ' '))
        AND LEN(Street1_New_Standard) = LEN(REPLACE(Street1_New_Standard, '  ', ' '))
        AND LEN(Street2_New_Standard) = LEN(REPLACE(Street2_New_Standard, '  ', ' '))
        AND LEN(City_New_Standard) = LEN(REPLACE(City_New_Standard, '  ', ' '))
),
ExpandedSuffixes AS (
    -- 第三步:替换街道后缀缩写(用CROSS APPLY集合式替换替代WHILE循环)
    SELECT
        fs.Join_Id,
        -- 替换旧地址后缀
        TRIM(REPLACE(' ' + fs.Street1_Old_Final + ' ', ' ' + UPPER(s.ctw_shortdescr) + ' ', ' ' + UPPER(s.ctw_description) + ' ')) AS Street1_Old_Expanded,
        CASE WHEN fs.Street2_Old_Final <> '' THEN TRIM(REPLACE(' ' + fs.Street2_Old_Final + ' ', ' ' + UPPER(s.ctw_shortdescr) + ' ', ' ' + UPPER(s.ctw_description) + ' ')) ELSE fs.Street2_Old_Final END AS Street2_Old_Expanded,
        TRIM(REPLACE(' ' + fs.City_Old_Final + ' ', ' ' + UPPER(s.ctw_shortdescr) + ' ', ' ' + UPPER(s.ctw_description) + ' ')) AS City_Old_Expanded,
        fs.State_Old_Final,
        fs.Zip_Old_Final,
        -- 替换新地址后缀
        TRIM(REPLACE(' ' + fs.Street1_New_Final + ' ', ' ' + UPPER(s.ctw_shortdescr) + ' ', ' ' + UPPER(s.ctw_description) + ' ')) AS Street1_New_Expanded,
        CASE WHEN fs.Street2_New_Final <> '' THEN TRIM(REPLACE(' ' + fs.Street2_New_Final + ' ', ' ' + UPPER(s.ctw_shortdescr) + ' ', ' ' + UPPER(s.ctw_description) + ' ')) ELSE fs.Street2_New_Final END AS Street2_New_Expanded,
        TRIM(REPLACE(' ' + fs.City_New_Final + ' ', ' ' + UPPER(s.ctw_shortdescr) + ' ', ' ' + UPPER(s.ctw_description) + ' ')) AS City_New_Expanded,
        fs.State_New_Final,
        fs.Zip_New_Final
    FROM FinalStandardized fs
    CROSS JOIN (
        SELECT ctw_shortdescr, ctw_description
        FROM Codes
        WHERE CTW_Type = 'Street Suffix'
          AND UPPER(CTW_Reference) = 'ADDRESSCOMPARE'
    ) s
),
FinalAddresses AS (
    -- 取最终后缀替换后的结果(去重)
    SELECT DISTINCT
        Join_Id,
        Street1_Old_Expanded,
        Street2_Old_Expanded,
        City_Old_Expanded,
        State_Old_Final AS State_Old_Expanded,
        Zip_Old_Final AS Zip_Old_Expanded,
        Street1_New_Expanded,
        Street2_New_Expanded,
        City_New_Expanded,
        State_New_Final AS State_New_Expanded,
        Zip_New_Final AS Zip_New_Expanded
    FROM ExpandedSuffixes
)
-- 第四步:匹配逻辑
SELECT
    CAST(IIF(fa.Street1_Old_Expanded = fa.Street1_New_Expanded, 1, 0) AS BIT) AS Street1_Match,
    CAST(IIF(fa.Street2_Old_Expanded = fa.Street2_New_Expanded, 1, 0) AS BIT) AS Street2_Match,
    CAST(IIF(fa.City_Old_Expanded = fa.City_New_Expanded, 1, 0) AS BIT) AS City_Match,
    CAST(IIF(fa.State_Old_Expanded = fa.State_New_Expanded, 1, 0) AS BIT) AS State_Match,
    CAST(IIF(fa.Zip_Old_Expanded = fa.Zip_New_Expanded, 1, 0) AS BIT) AS Zip_Match,
    CAST(IIF(
        fa.Street1_Old_Expanded = fa.Street1_New_Expanded
        AND fa.Street2_Old_Expanded = fa.Street2_New_Expanded
        AND fa.City_Old_Expanded = fa.City_New_Expanded
        AND fa.State_Old_Expanded = fa.State_New_Expanded
        AND fa.Zip_Old_Expanded = fa.Zip_New_Expanded,
        1, 0
    ) AS BIT) AS Total_Match,
    fa.Join_Id
FROM FinalAddresses fa;

3. 调用示例

-- 将#bkas数据传入批量函数,生成匹配结果
SELECT * 
INTO #address_match_data
FROM dbo.AddressCompare_Batch(
    (SELECT 
        BKA_Street1 AS Street1_Old,
        BKA_Street2 AS Street2_Old,
        BKA_City AS City_Old,
        BKA_State AS State_Old,
        BKA_Zip AS Zip_Old,
        BKA_Street1_Current AS Street1_New,
        BKA_Street2_Current AS Street2_New,
        BKA_City_Current AS City_New,
        BKA_State_Current AS State_New,
        BKA_Zip_Current AS Zip_New,
        BKA_Id AS Join_Id
     FROM #bkas)
);

-- 原UPDATE逻辑改造为批量更新
UPDATE b
SET Address_Match_Flag = 1
FROM #bkas b
JOIN #address_match_data amd ON b.BKA_Id = amd.Join_Id
WHERE amd.Total_Match = 1;

额外性能优化建议

  • 缓存街道后缀数据:将Codes表中用于后缀替换的数据持久化为索引视图或单独的小表,避免每次函数调用都查询大表
  • 字符串处理优化:若SQL Server版本支持(2017+),可使用TRANSLATE函数简化标点移除逻辑,替代多层REPLACE
  • CLR函数加速:复杂字符串处理(如多次空格移除、多后缀替换)可考虑用CLR函数实现,性能远优于T-SQL
  • 输入表索引:确保传入的表参数(临时表或用户定义表类型)的Join_Id字段有主键或索引,提升关联性能
  • 避免递归CTE:若SQL Server版本支持2022+,可使用STRING_AGG结合拆分函数实现更高效的去空格操作,替代递归CTE

内容的提问来源于stack exchange,提问作者DizzleBeans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 22:27:35