【问题标题】:Populate Address Lines Into a Standard Set of Columns将地址行填充到一组标准列中
【发布时间】:2020-11-24 04:17:43
【问题描述】:

我正在尝试确定如何在 SQL Server 查询中清理和填充一组地址行。 (需要兼容 SQL Server 2008 及更高版本)

我有 5 个地址行 Addr1 - Addr5,我想在查询中将它们输出到 4 个连续填充的列中。如果 N 个地址不为空,则应填充最终地址 FAddr1 到 FAddrN。 (任何剩余的列都应该为空。如果所有 5 个地址行都已填充,则最后一个被删除。)

请注意,稍后我可能会添加第 6 个 Addr 列,但这并不需要对任何 N 列进行通用解决。 (6 Addr 列应该是最大值。)数据无法事先标准化,所以我必须以最快的方式即时执行此操作。

复杂性在于如何将地址行列“向左”移动以覆盖空白列。

这是一个例子:

Addr1           Addr2           Addr3           Addr4           Addr5           
--------------------------------------------------------------------------------
                101 Main St     Apt 1           Here, AZ 11111  USA
                102 Main St                     Here, AZ 22222
103 Main St     Apt 3           Here, AZ 33333  USA
104 Main St                                     Here, AZ 44444  USA

对于这些示例,我想输出:

FAddr1          FAddr2          FAddr3          FAddr4          
----------------------------------------------------------------
101 Main St     Apt 1           Here, AZ 11111
102 Main St     Here, AZ 22222
103 Main St     Apt 3           Here, AZ 33333  USA
104 Main St     Here, AZ 44444  USA

我是用 C# 编写的,但复杂性在于以一种简洁的方式在 SQL 中执行此操作。通过连接成一个字符串来实现这一点要容易得多(请参阅下面的相关问题)。但我需要将这些放在 4 个单独的列中。

相关问题: Most Similar Question - Different Use Case/Similar But With String Instead of Columns

初始版本:

我在 SQL 中使用了一个似乎可以正常工作的测试用例编写了以下版本,但我希望有一种更简洁的方法。这基本上会不断移动非空元素。但是每列需要一个子选择才能将所有内容向左移动。

Select
    Addr1,
    case when Addr2 is null then Addr3 else Addr2 end as Addr2,
    case when Addr2 is null then null else Addr3 end as Addr3,
    case when Addr4 is null then Addr5 else Addr4 end as Addr4
    -- Truncate to 4 - case when Addr4 is null then null else Addr5 end as Addr5
From
(
Select
    case when Addr1 is null then Addr2 else Addr1 end as Addr1,
    case when Addr1 is null then null else Addr2 end as Addr2,
    case when Addr3 is null then Addr4 else Addr3 end as Addr3,
    case when Addr3 is null then null else Addr4 end as Addr4,
    Addr5
From
(
Select
    Addr1,
    case when Addr2 is null then Addr3 else Addr2 end as Addr2,
    case when Addr2 is null then null else Addr3 end as Addr3,
    case when Addr4 is null then Addr5 else Addr4 end as Addr4,
    case when Addr4 is null then null else Addr5 end as Addr5
From
(
Select
    case when Addr1 is null then Addr2 else Addr1 end as Addr1,
    case when Addr1 is null then null else Addr2 end as Addr2,
    case when Addr3 is null then Addr4 else Addr3 end as Addr3,
    case when Addr3 is null then null else Addr4 end as Addr4,
    Addr5
From
(
Select
    Addr1,
    case when Addr2 is null then Addr3 else Addr2 end as Addr2,
    case when Addr2 is null then null else Addr3 end as Addr3,
    case when Addr4 is null then Addr5 else Addr4 end as Addr4,
    case when Addr4 is null then null else Addr5 end as Addr5
From
(
    SELECT NULL as Addr1, NULL as Addr2, NULL as Addr3, NULL as Addr4, NULL  as Addr5 UNION
    SELECT NULL, NULL, NULL, NULL,  '5' UNION
    SELECT NULL, NULL, NULL,  '4', NULL UNION
    SELECT NULL, NULL, NULL,  '4',  '5' UNION
    SELECT NULL, NULL,  '3', NULL, NULL UNION
    SELECT NULL, NULL,  '3', NULL,  '5' UNION
    SELECT NULL, NULL,  '3',  '4', NULL UNION
    SELECT NULL, NULL,  '3',  '4',  '5' UNION
    SELECT NULL,  '2', NULL, NULL, NULL UNION
    SELECT NULL,  '2', NULL, NULL,  '5' UNION
    SELECT NULL,  '2', NULL,  '4', NULL UNION
    SELECT NULL,  '2', NULL,  '4',  '5' UNION
    SELECT NULL,  '2',  '3', NULL, NULL UNION
    SELECT NULL,  '2',  '3', NULL,  '5' UNION
    SELECT NULL,  '2',  '3',  '4', NULL UNION
    SELECT NULL,  '2',  '3',  '4',  '5' UNION
    SELECT  '1', NULL, NULL, NULL, NULL UNION
    SELECT  '1', NULL, NULL, NULL,  '5' UNION
    SELECT  '1', NULL, NULL,  '4', NULL UNION
    SELECT  '1', NULL, NULL,  '4',  '5' UNION
    SELECT  '1', NULL,  '3', NULL, NULL UNION
    SELECT  '1', NULL,  '3', NULL,  '5' UNION
    SELECT  '1', NULL,  '3',  '4', NULL UNION
    SELECT  '1', NULL,  '3',  '4',  '5' UNION
    SELECT  '1',  '2', NULL, NULL, NULL UNION
    SELECT  '1',  '2', NULL, NULL,  '5' UNION
    SELECT  '1',  '2', NULL,  '4', NULL UNION
    SELECT  '1',  '2', NULL,  '4',  '5' UNION
    SELECT  '1',  '2',  '3', NULL, NULL UNION
    SELECT  '1',  '2',  '3', NULL,  '5' UNION
    SELECT  '1',  '2',  '3',  '4', NULL UNION
    SELECT  '1',  '2',  '3',  '4',  '5'
) as Base
) as Pass1 ) as Pass2 ) as Pass3 ) as Pass4

【问题讨论】:

    标签: sql sql-server street-address


    【解决方案1】:

    这应该适合你:

    with Temp1 (AddrStr) as (
    select
    TRIM(CONCAT( Addr1 + ', ', Addr2 + ', ', Addr3 + ', ', Addr4 + ', ', Addr5)) AS [AddrStr]
    From
    (
    ... code to select the 5 address columns
    
    )
    ) 
    as base
    )
    select
    AddrStr
    ,REVERSE(PARSENAME(REPLACE(REVERSE(AddrStr), ',', '.'), 1)) AS [Addr1]
    ,REVERSE(PARSENAME(REPLACE(REVERSE(AddrStr), ',', '.'), 2)) AS [Addr2]
    ,REVERSE(PARSENAME(REPLACE(REVERSE(AddrStr), ',', '.'), 3)) AS [Addr3]
    ,REVERSE(PARSENAME(REPLACE(REVERSE(AddrStr), ',', '.'), 4)) AS [Addr4]
    ,REVERSE(PARSENAME(REPLACE(REVERSE(AddrStr), ',', '.'), 5)) AS [Addr5]
    From Temp1
    

    当然,您可以通过将 REVERSE... 语句中的“AddrStr”替换为 TRIM(CONCAT ... 语句来简化此操作 - 但为了清楚起见,我将其分为 2 个步骤,例如

    ,REVERSE(PARSENAME(REPLACE(REVERSE(TRIM(CONCAT( Addr1 + ', ', Addr2 + ', ', Addr3 + ', ', Addr4 + ', ', Addr5))), ',', '.'), 1)) AS [Addr1]
    

    【讨论】:

    • 来自测试的快速cmets:1)这种方法很快,它和我最初的尝试一样快,但要短得多。 2) 看起来 PARSENAME() 只会获取标识符的前 3 个组件。我想我可以围绕这个进行编码。 Maybe this 3) 我的地址中有点和逗号。这个比较棘手,因为我的地址中可能有 6 个以上的逗号/点。我可以使用 CHAR(1) 代替逗号。 4)由于边缘情况,我宁愿不连接字符串,但也许这足够强大。
    【解决方案2】:

    您可以在这里使用apply

    select b.*, v.*
    from base b cross apply
         (select max(case when v.seqnum = 1 then v.addr end) as faddr1,
                 max(case when v.seqnum = 2 then v.addr end) as faddr2,
                 max(case when v.seqnum = 3 then v.addr end) as faddr3,
                 max(case when v.seqnum = 4 then v.addr end) as faddr4,
                 max(case when v.seqnum = 5 then v.addr end) as faddr5
          from (select v.*, row_number() over (order by ord) as seqnum
                from (values (b.addr1, 1), (b.addr2, 2), (b.addr3, 3), (b.addr4, 4), (b.addr5, 5)
                     ) v(addr, ord)
                where v.addr is not null
               ) v
          ) v;
    

    【讨论】:

    • 来自测试的快速cmets:1)这种方法很干净,比我原来的方法更短,更健壮。 2)在我更大的数据集上,这比我的原始/字符串解析方法要长 5-10 倍,这两种方法主要是计算标量。任何想法如何使这更快? 3) 它返回此消息:“警告:空值已被聚合或其他 SET 操作消除。”我没有看到临时修复的方法。 4)关于over & cross apply还有什么好的参考吗?谷歌搜索并没有让我找到一个清晰的资源来向我清楚地解释这一点。
    • @Russell。 . .不要担心警告。我很惊讶这比你的版本慢 5-10 倍。通常横向连接相对有效(较慢,但不会慢很多)。
    • @GordonLonoff 很好的警告,谢谢。可能是我的数据集让它变慢了,90% 的时间只有 Addr1 和 4 被填充,其余的为空。但我必须处理所有案件。我双向运行了几次,并循环使用 SQL Server 来清除缓存,所以这不是一个完全科学的测试,而是一个粗略的想法。它的记录不到 100 万条。
    • @Russell。 . . .鉴于您的查询被优化的方式,我怀疑您是否可以更快地构建任何东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-05
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 1970-01-01
    相关资源
    最近更新 更多