如何在SQL Server 2005中选择最接近的匹配?

时间:2009-08-27 20:44:59

标签: sql sql-server-2005

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

我们从销售表中输入以下信息:
SERVICEID, 地址, 邮政编码, 电子邮件地址, 家庭电话, 名字, 名字

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

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

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

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

任何想法都会非常感激。

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

6 个答案:

答案 0 :(得分:3)

对于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

答案 1 :(得分:1)

使用SQL Server代码,这是一种相当丑陋的方法。假设:
  - Customer表中存在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。)当然,如果你的表很大,除非你的所有列上都有索引,否则它会像奶牛一样飞行。

答案 2 :(得分:1)

坦率地说,由于您的数据中没有唯一标识符,我会对此完全保持警惕。

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

答案 3 :(得分:0)

我可能会为此创建一个存储函数(在Oracle中)和oder在最高匹配

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

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

答案 4 :(得分:0)

对于近似匹配,您还可以查看许多字符串相似度算法。

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

答案 5 :(得分:0)