代码之家  ›  专栏  ›  技术社区  ›  Brisbe

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

  •  1
  • Brisbe  · 技术社区  · 15 年前

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

    我们从销售表中得到以下信息:
    Service ID 地址, 邮政编码 电子邮件地址, 家庭电话, 第一名, 姓

    客户信息包括所有这些信息,以及“最后交易”日期。

    这些字段中的任何一个都可以映射回0个或多个客户。我们将匹配计算为销售表中的服务ID、地址+邮政编码、电子邮件地址或住宅电话与客户完全匹配的任何时间。

    问题是我们有许多客户的信息,有时同一个家庭有多个客户。这意味着我们可能有约翰·多伊,简·多伊,吉姆·多伊和鲍勃·多伊在同一个房子里。它们都将在地址+邮政编码和家庭电话上匹配——而且可能不止一个在serviceid上匹配。

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

    任何想法都会受到赞赏。

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

    6 回复  |  直到 15 年前
        1
  •  3
  •   KM.    15 年前

    对于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
    
        2
  •  1
  •   Philip Kelley    15 年前

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

    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
    

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

        3
  •  1
  •   HLGEM    15 年前

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

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

        4
  •  0
  •   Janco    15 年前

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

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

    函数match_customer根据输入参数返回匹配数…我想可能是慢了,因为这个查询总是扫描完整的customer表

        5
  •  0
  •   Joeri Sebrechts    15 年前

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

    例如,在Oracle中有utl_match.jaro_winkler_相似性函数:
    http://www.psoug.org/reference/utl_match.html

        6
  •  0
  •   John MacIntyre    15 年前