在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”,则必须与客户姓氏完全匹配,才能算作匹配参数,而不是非常接近的匹配。
答案 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)