查找链中的最后一条记录-客户合并过程

时间:2019-01-02 01:27:15

标签: sql tsql sql-server-2016

我正在从另一家供应商的系统中导入客户数据,我们有合并流程,用于识别潜在的重复客户帐户,如果满足某些条件(例如,相同的名字,姓氏,SSN和DOB),它们将合并它们。在此过程中,我看到了我们在哪里创建链-例如,将客户A合并到客户B,然后再将其合并到客户C。

我希望这样做来识别这些链并更新客户记录以指向链中的最后一条记录。因此,在上面的示例中,客户A和客户B在合并的“收件人”字段中都将拥有客户C的ID。

CustID FName   LName     CustStatusType  isMerged  MergedTo
1      Kevin   Smith     M               1         2 
2      Kevin   Smith     M               1         3
3      Kevin   Smith     M               1         4
4      Kevin   Smith     O               0         NULL
5      Mary    Jones     O               0         NULL
6      Wyatt   Earp      M               1         7
7      Wyatt   Earp      O               1         NULL
8      Bruce   Wayn      M               1         10
9      Brice   Wayne     M               1         10
10     Bruce   Wane      M               1         11
11     Bruce   Wayne     O               1         NULL

CustStatusType指示客户帐户是打开(“ O”)还是合并(“ M”)。然后,我们有一个isMerged字段作为BIT字段,用于指示该帐户是否已被合并,最后是MergedTo字段,该字段指示该记录被合并到哪个客户帐户。

使用提供的示例,我想要实现的是将CustID记录1和2的MergedTo记录设置为3-而CustID 3可以更新或保持不变。对于客户ID 4、5和6-找到了这些记录,不需要更新这些记录。但是,对于Cust ID 8-10,我希望将这些记录设置为11-如下表所示。

CustID FName   LName     CustStatusType  isMerged  MergedTo
1      Kevin   Smith     M               1         4 
2      Kevin   Smith     M               1         4
3      Kevin   Smith     M               1         4
4      Kevin   Smith     O               0         NULL
5      Mary    Jones     O               0         NULL
6      Wyatt   Earp      M               1         7
7      Wyatt   Earp      O               1         NULL
8      Bruce   Wayn      M               1         11
9      Brice   Wayne     M               1         11
10     Bruce   Wane      M               1         11
11     Bruce   Wayne     O               1         NULL

我无法弄清楚如何使用TSQL实现这一点-建议?

测试数据:

DROP TABLE IF EXISTS #Customers;

CREATE TABLE #Customers
    (
        CustomerID INT ,
        FirstName VARCHAR (25) ,
        LastName VARCHAR (25) ,
        CustomerStatusTypeID VARCHAR (1) ,
        isMerged BIT ,
        MergedTo INT
    );
INSERT INTO #Customers
VALUES ( 1, 'Kevin', 'Smith', 'M', 1, 2 ) ,
       ( 2, 'Kevin', 'Smith', 'M', 1, 3 ) ,
       ( 3, 'Kevin', 'Smith', 'M', 1, 4 ) ,
       ( 4, 'Kevin', 'Smith', 'O', 0, NULL ) ,
       ( 5, 'Mary', 'Jones', 'O', 0, NULL ) ,
       ( 6, 'Wyatt', 'Earp', 'M', 1, 7 ) ,
       ( 7, 'Wyatt', 'Earp', 'O', 1, NULL ) ,
       ( 8, 'Bruce', 'Wayn', 'M', 1, 10 ) ,
       ( 9, 'Brice', 'Wayne', 'M', 1, 10 ) ,
       ( 10, 'Bruce', 'Wane', 'M', 1, 11 ) ,
       ( 11, 'Bruce', 'Wayne', 'O', 1, NULL );

SELECT *
FROM   #Customers;

DROP TABLE #Customers;

3 个答案:

答案 0 :(得分:1)

以您的示例为例,soundex()似乎足够好。它返回一个基于单词的英语发音的代码。在名字和姓氏上使用它来连接客户表和一个子查询,该子查询查询客户表,添加由名称的Soundex分区的row_number()和按ID降序的订单-为“最新”记录编号与1。对于连接条件,请使用名称的Soundex,行号为1以及ID的不等式。

UPDATE c1
       SET c1.mergedto = x.customerid
       FROM #customers c1
            LEFT JOIN (SELECT c2.customerid,
                              soundex(c2.firstname) sefn,
                              soundex(c2.lastname) seln,
                              row_number() OVER (PARTITION BY soundex(c2.firstname),
                                                              soundex(c2.lastname)
                                                 ORDER BY c2.customerid DESC) rn
                              FROM #customers c2) x
                      ON x.sefn = soundex(c1.firstname)
                         AND x.seln = soundex(c1.lastname)
                         AND x.rn = 1
                         AND x.customerid <> c1.customerid;

db<>fiddle

我对customerstatustypeidismerged列背后的概念并不了解。据我了解,它们都是根据mergedto是否为null派生的。但是样本数据都不符合预期的结果。但是,由于这些列在您的示例输入和输出之间显然没有改变,我想这没关系,我只是将它们留了下来。

如果证明Soundex不足以满足您的需求,则可能需要寻找其他字符串距离度量标准,例如Levenshtein distance。 AFAIK没有包含在SQL Server中的实现,但是搜索引擎可能会吐出第三方的实现,或者也许可以通过CLR使用某些实现。当然,也可以自己动手。

答案 1 :(得分:1)

下面的查询找到与每个客户匹配的最新CustomerID,并在Ref列中返回ID

select *
, Ref = (select top 1 CustomerID from #Customers where soundex(FirstName) = soundex(ma.FirstName) and soundex(LastName) = soundex(ma.LastName) order by CustomerID desc)
from #Customers ma

使用以下更新,您可以更新MergedTo列

;with ct as (
select *
, Ref = (select top 1 CustomerID from #Customers where soundex(FirstName) = soundex(ma.FirstName) and soundex(LastName) = soundex(ma.LastName) order by CustomerID desc)
from #Customers ma
)
update c1
set c1.MergedTo = iif(c1.CustomerID = ct.Ref, null, ct.Ref)
from #Customers c1
    inner join ct on ct.CustomerID = c1.CustomerID

更新后“客户”表中的最终数据

Final Result

答案 2 :(得分:0)

递归可用于此目的

WITH CTE as
(
  SELECT P.CustomerID, P.MergedTo, CAST(P.CustomerID AS VarChar(Max)) as Levels
  FROM #Customers P
  WHERE P.MergedTo IS NULL

  UNION ALL

  SELECT  P1.CustomerID, P1.MergedTo, M.Levels + ', ' + CAST(P1.CustomerID AS VarChar(Max)) 
  FROM #Customers P1  
  INNER JOIN CTE M ON M.CustomerID = P1.MergedTo
 )
SELECT
       CustomerID
     , MergedTo 
     , x           -- "end of chain"
     , Levels
FROM CTE
CROSS APPLY (
    SELECT LEFT(levels,charindex(',',levels+',')-1) x
    ) a
WHERE MergedTo IS NOT NULL

结果:

+----+------------+----------+----+------------+
|    | CustomerID | MergedTo | x  |   levels   |
+----+------------+----------+----+------------+
|  1 |         10 |       11 | 11 | 11, 10     |
|  2 |          8 |       10 | 11 | 11, 10, 8  |
|  3 |          9 |       10 | 11 | 11, 10, 9  |
|  4 |          6 |        7 |  7 | 7, 6       |
|  5 |          3 |        4 |  4 | 4, 3       |
|  6 |          2 |        3 |  4 | 4, 3, 2    |
|  7 |          1 |        2 |  4 | 4, 3, 2, 1 |
+----+------------+----------+----+------------+

请注意,字符串levels是由递归形成的,以这种方式连接起来,第一部分将是“链的末端”(请参阅​​列x)。尽管不是必须的,但是使用交叉应用程序提取了第一部分。

demo

的形式提供