【问题标题】:How can I choose the closest match in SQL Server 2005?如何在 SQL Server 2005 中选择最接近的匹配项?
【发布时间】:2009-08-27 20:44:59
【问题描述】:

在 SQL Server 2005 中,我有一个成功销售的输入表,以及包含已知客户信息及其详细信息的各种表。对于每一行销售,我需要匹配 0 或 1 个已知客户。

我们从销售表中得到以下信息:
服务标识, 地址, 邮政编码, 电子邮件地址, 家庭电话, 名, 姓氏

客户信息包括所有这些,以及“LastTransaction”日期。

这些字段中的任何一个都可以映射回 0 个或多个客户。我们将销售表中的 ServiceId、Address+ZipCode、EmailAddress 或 HomePhone 与客户完全匹配的任何时间计为匹配。

问题在于我们拥有许多客户的信息,有时是同一家庭的多个客户。这意味着我们可能在同一所房子里有 John Doe、Jane Doe、Jim Doe 和 Bob Doe。它们都将在 Address+ZipCode 和 HomePhone 上匹配 - 并且可能不止一个在 ServiceId 上匹配。

我需要某种方式在交易中优雅地跟踪客户的“最佳”匹配。如果一个匹配 6 个字段,而其他只匹配 5 个,则该客户应与该记录保持匹配。在多个匹配 5 且没有匹配更多的情况下,应保留最近的 LastTransaction 日期。

任何想法都将不胜感激。

更新:为了更清楚一点,我正在寻找一种好方法来验证数据行中完全匹配的数量,并根据该信息选择要关联的行。如果姓氏是“Doe”,它必须与客户姓氏完全匹配,才能算作匹配参数,而不是非常接近的匹配。

【问题讨论】:

    标签: sql sql-server-2005


    【解决方案1】:

    对于 SQL Server 2005 及更高版本尝试:

    ;WITH SalesScore AS (
    SELECT
        s.PK_ID as S_PK
            ,c.PK_ID AS c_PK
            ,CASE 
                 WHEN c.PK_ID IS NULL THEN 0
                 ELSE CASE WHEN s.ServiceId=c.ServiceId THEN 1 ELSE 0 END
                      +CASE WHEN (s.Address=c.Address AND s.Zip=c.Zip) THEN 1 ELSE 0 END
                      +CASE WHEN s.EmailAddress=c.EmailAddress THEN 1 ELSE 0 END
                      +CASE WHEN s.HomePhone=c.HomePhone THEN 1 ELSE 0 END
             END AS Score
        FROM Sales s
            LEFT OUTER JOIN Customers c ON s.ServiceId=c.ServiceId
                                           OR (s.Address=c.Address AND s.Zip=c.Zip)
                                           OR s.EmailAddress=c.EmailAddress
                                           OR s.HomePhone=c.HomePhone 
    )
    SELECT 
        s.*,c.*
        FROM (SELECT
                  S_PK,MAX(Score) AS Score
                  FROM SalesScore 
                  GROUP BY S_PK
             ) dt
            INNER JOIN Sales          s ON dt.s_PK=s.PK_ID 
            INNER JOIN SalesScore    ss ON dt.s_PK=s.PK_ID AND dt.Score=ss.Score
            LEFT OUTER JOIN Customers c ON ss.c_PK=c.PK_ID
    

    编辑 我讨厌在没有给出 shema 的情况下编写这么多实际代码,因为我实际上无法运行它并确保它有效。然而,要回答如何使用最后交易日期处理平局的问题,这里是上述代码的更新版本:

    ;WITH SalesScore AS (
    SELECT
        s.PK_ID as S_PK
            ,c.PK_ID AS c_PK
            ,CASE 
                 WHEN c.PK_ID IS NULL THEN 0
                 ELSE CASE WHEN s.ServiceId=c.ServiceId THEN 1 ELSE 0 END
                      +CASE WHEN (s.Address=c.Address AND s.Zip=c.Zip) THEN 1 ELSE 0 END
                      +CASE WHEN s.EmailAddress=c.EmailAddress THEN 1 ELSE 0 END
                      +CASE WHEN s.HomePhone=c.HomePhone THEN 1 ELSE 0 END
             END AS Score
        FROM Sales s
            LEFT OUTER JOIN Customers c ON s.ServiceId=c.ServiceId
                                           OR (s.Address=c.Address AND s.Zip=c.Zip)
                                           OR s.EmailAddress=c.EmailAddress
                                           OR s.HomePhone=c.HomePhone 
    )
    SELECT
        *
        FROM (SELECT 
                  s.*,c.*,row_number() over(partition by s.PK_ID order by s.PK_ID ASC,c.LastTransaction DESC) AS RankValue
                  FROM (SELECT
                            S_PK,MAX(Score) AS Score
                            FROM SalesScore 
                            GROUP BY S_PK
                       ) dt
                      INNER JOIN Sales          s ON dt.s_PK=s.PK_ID 
                      INNER JOIN SalesScore    ss ON dt.s_PK=s.PK_ID AND dt.Score=ss.Score
                      LEFT OUTER JOIN Customers c ON ss.c_PK=c.PK_ID
             ) dt2
        WHERE dt2.RankValue=1
    

    【讨论】:

    • 谢谢,我正在研究这是否对我有用,但到目前为止,这似乎就是我想要的!
    • 您可以通过在评分逻辑中添加尽可能多的“案例”并对它们进行不同的加权(添加多于或少于其他)来自定义此设置
    • 这是迄今为止发布的最佳解决方案。您将如何考虑 LastTransaction 来解决关系?
    • @Philip Kelley,要处理平局,请使用 row_number()... AS x 然后 WHERE x=1 查看我的编辑...
    • 是的,我就是这么做的。我希望您可能有一些方法可以避免额外的子查询来“包装”排名函数。
    【解决方案2】:

    这是一个相当丑陋的方法,使用 SQL Server 代码。假设:
    - 客户表中存在列CustomerId,用于唯一标识客户。
    - 仅支持完全匹配(正如问题所暗示的那样)。

    SELECT top 1 CustomerId, LastTransaction, count(*) HowMany
     from (select Customerid, LastTransaction
            from Sales sa
             inner join Customers cu
              on cu.ServiceId = sa.ServiceId
           union all select Customerid, LastTransaction
            from Sales sa
             inner join Customers cu
              on cu.EmailAddress = sa.EmailAddress
           union all select Customerid, LastTransaction
            from Sales sa
             inner join Customers cu
              on cu.Address = sa.Address
               and cu.ZipCode = sa.ZipCode
           union all [etcetera -- repeat for each possible link]
          ) xx
     group by CustomerId, LastTransaction
     order by count(*) desc, LastTransaction desc
    

    我不喜欢使用“top 1”,但写起来更快。 (替代方法是使用排名函数,这将需要另一个子查询级别或将其作为 CTE 执行。)当然,如果您的表很大,除非您在所有列上都有索引,否则这会像牛一样飞。

    【讨论】:

      【解决方案3】:

      坦率地说,我不会这样做,因为您的数据中没有唯一标识符。

      约翰·史密斯和他的儿子约翰·史密斯住在一起,他们都使用相同的电子邮件地址和家庭电话。这是两个人,但你会将他们匹配为一个人。我们一直在处理我们的数据,因此没有自动匹配的解决方案。我们识别可能的重复,并实际打电话并找出他们是重复的。

      【讨论】:

      • 感谢您的意见。我们确实有一些上面没有提到的保护措施,以试图防止这种情况发生——其中许多措施处理姓氏和地址相同的情况(为了我们的目的,将客户分组到一个“家庭”中) .我主要使用名字/姓氏/地址示例来尝试简化解决方案的预期逻辑,而不是给出数据库结构的完整概念。
      • 我写了一些类似的东西来匹配名称,我们使用同义词库来匹配其他名称,例如BobRobert,听起来像是匹配 Mohamed -> Muhamed、Muhamad 等内容的查询
      【解决方案4】:

      我可能会为此创建一个存储函数(在 Oracle 中)并在最高匹配时排序

      SELECT * FROM (
       SELECT c.*, MATCH_CUSTOMER( Customer.Id, par1, par2, par3 ) matches FROM Customer c
      ) WHERE matches >0 ORDER BY matches desc
      

      函数 match_customer 根据输入参数返回匹配数...我猜可能很慢,因为此查询将始终扫描完整的客户表

      【讨论】:

        【解决方案5】:

        对于紧密匹配,您还可以查看一些字符串相似性算法。

        例如,在 Oracle 中有 UTL_MATCH.JARO_WINKLER_SIMILARITY 函数:
        http://www.psoug.org/reference/utl_match.html

        【讨论】:

          【解决方案6】:

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-05-11
            • 1970-01-01
            • 1970-01-01
            • 2011-05-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多