SQL 模糊连接 - MSSQL

Posted

技术标签:

【中文标题】SQL 模糊连接 - MSSQL【英文标题】:SQL Fuzzy Join - MSSQL 【发布时间】:2017-01-08 03:16:05 【问题描述】:

我有两组数据。现有客户和潜在客户。

我的主要目标是确定任何潜在客户是否已经是现有客户。但是,跨数据集的客户的命名约定是不一致的。

现有客户

Customer /  ID
Ed's Barbershop /   1002
GroceryTown /   1003
Candy Place /   1004
Handy Man / 1005

潜在客户

Customer
Eds Barbershop
Grocery Town
Candy Place
Handee Man
Beauty Salon
The Apple Farm
Igloo Ice Cream
Ride-a-Long Bikes

我想写一些如下的选择语句来达到我的目标:

SELECT a.Customer, b.ID
FROM PotentialCustomers a LEFT JOIN
     ExistingCustomers B
     ON a.Customer = b.Customer

结果如下所示:

Customer /  ID
Eds Barbershop  / 1002
Grocery Town    / 1003
Candy Place / 1004
Handee Man  / 1005
Beauty Salon /  NULL
The Apple Farm /    NULL
Igloo Ice Cream / NULL
Ride-a-Long Bikes / NULL

我对 Levenshtein Distance 和 Double Metaphone 的概念有点熟悉,但我不知道如何在这里应用它。

理想情况下,我希望 SELECT 语句的 JOIN 部分读取如下内容:LEFT JOIN ExistingCustomers as B WHERE a.Customer LIKE b.Customer,但我知道语法不正确。

欢迎提出任何建议。谢谢!

【问题讨论】:

【参考方案1】:

以下是使用 Levenshtein Distance 的方法:

创建这个函数:(先执行这个)

CREATE FUNCTION ufn_levenshtein(@s1 nvarchar(3999), @s2 nvarchar(3999))
RETURNS int
AS
BEGIN
 DECLARE @s1_len int, @s2_len int
 DECLARE @i int, @j int, @s1_char nchar, @c int, @c_temp int
 DECLARE @cv0 varbinary(8000), @cv1 varbinary(8000)

 SELECT
  @s1_len = LEN(@s1),
  @s2_len = LEN(@s2),
  @cv1 = 0x0000,
  @j = 1, @i = 1, @c = 0

 WHILE @j <= @s2_len
  SELECT @cv1 = @cv1 + CAST(@j AS binary(2)), @j = @j + 1

 WHILE @i <= @s1_len
 BEGIN
  SELECT
   @s1_char = SUBSTRING(@s1, @i, 1),
   @c = @i,
   @cv0 = CAST(@i AS binary(2)),
   @j = 1

  WHILE @j <= @s2_len
  BEGIN
   SET @c = @c + 1
   SET @c_temp = CAST(SUBSTRING(@cv1, @j+@j-1, 2) AS int) +
    CASE WHEN @s1_char = SUBSTRING(@s2, @j, 1) THEN 0 ELSE 1 END
   IF @c > @c_temp SET @c = @c_temp
   SET @c_temp = CAST(SUBSTRING(@cv1, @j+@j+1, 2) AS int)+1
   IF @c > @c_temp SET @c = @c_temp
   SELECT @cv0 = @cv0 + CAST(@c AS binary(2)), @j = @j + 1
 END

 SELECT @cv1 = @cv0, @i = @i + 1
 END

 RETURN @c
END

(Joseph Gama 开发的函数)

然后只需使用此查询来获取匹配项

SELECT A.Customer,
       b.ID,
       b.Customer
FROM #POTENTIALCUSTOMERS a
     LEFT JOIN #ExistingCustomers b ON dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) < 5;

创建该函数后完成脚本:

IF OBJECT_ID('tempdb..#ExistingCustomers') IS NOT NULL
    DROP TABLE #ExistingCustomers;

CREATE TABLE #ExistingCustomers
(Customer VARCHAR(255),
 ID       INT
);

INSERT INTO #ExistingCustomers
VALUES
('Ed''s Barbershop',
 1002
);

INSERT INTO #ExistingCustomers
VALUES
('GroceryTown',
 1003
);

INSERT INTO #ExistingCustomers
VALUES
('Candy Place',
 1004
);

INSERT INTO #ExistingCustomers
VALUES
('Handy Man',
 1005
);

IF OBJECT_ID('tempdb..#POTENTIALCUSTOMERS') IS NOT NULL
    DROP TABLE #POTENTIALCUSTOMERS;

CREATE TABLE #POTENTIALCUSTOMERS(Customer VARCHAR(255));

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Eds Barbershop');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Grocery Town');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Candy Place');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Handee Man');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Beauty Salon');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('The Apple Farm');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Igloo Ice Cream');

INSERT INTO #POTENTIALCUSTOMERS
VALUES('Ride-a-Long Bikes');

SELECT A.Customer,
       b.ID,
       b.Customer
FROM #POTENTIALCUSTOMERS a
     LEFT JOIN #ExistingCustomers b ON dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) < 5;

您可以在 http://www.kodyaz.com/articles/fuzzy-string-matching-using-levenshtein-distance-sql-server.aspx 找到 T-SQL 示例

【讨论】:

到目前为止我最接近,但仍然存在一些问题。首先,运行查询需要很长时间。其次,我得到了大量的误报匹配。例如,我的一个潜在客户正在匹配 700 个现有客户......有没有办法将 levenshtein 距离作为 select 语句中的一列? 使用这个:SELECT A.Customer, b.ID, b.Customer, dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) FROM #POTENTIALCUSTOMERS a LEFT JOIN #ExistingCustomers b ON dbo.ufn_levenshtein(REPLACE(A.Customer, ' ', ''), REPLACE(B.Customer, ' ', '')) 你必须玩距离,看看什么最适合你..现在它匹配“ 明白了。非常感谢您的牵手。我学到了很多,我很感激。 @KashifQureshi 有没有办法匹配最低的 Levenshtein 分数?而不是&lt;5,它碰巧得到1-1的结果。默认情况下以某种方式返回得分最低的匹配项?【参考方案2】:

尝试在 SQL 中执行此操作将是一项持续的挑战,而且您不太可能获胜。您可以通过去除非 az 或 0-9 字符或尝试诸如 Soundex 或 Metaphone 匹配或 Levenshtein Distance 之类的方法来走得很远,但总会有另一个你没有的边缘情况'不要接受你所有的替换、通配符、拼音或简单的捏造。

如果您确实设法找到对您来说足够准确的东西,那么您将遇到性能问题。

简而言之,你最好的希望是沿着 SQLCLR 路线走下去,并在途中学习大量 C#,或者根本不费心,只需从源头清理数据或创建一个“干净”名称的查找表,这将随着新变种的出现,需要不断维护。

【讨论】:

很确定我想使用您布置的 Leveshtein Distance 示例。我了解它的局限性,但我只是在做探索性任务,而不是寻求自动化。话虽如此,我有一个基本的问题。对我来说,编写 Levenshtein 函数的脚本很容易,但您能帮我理解如何将两个值(PRE EXISTING customer 和 POTENTIAL customers)传递到函数中吗?【参考方案3】:

一种方法是在比较列的两侧使用 REPLACE 函数的帮助。

SELECT a.Customer, b.ID
FROM PotentialCustomers a 
  LEFT JOIN ExistingCustomers B
     ON (LTRIM(RTRIM(REPLACE(REPLACE(REPLACE(a.Customer,' ',''),'-',''),'''',''))) = LTRIM(RTRIM(REPLACE(REPLACE(REPLACE(b.Customer,' ',''),'-',''),'''','')))) 
        OR (a.Customer LIKE '%'+b.Customer+'%') 
        OR (b.Customer LIKE '%'+a.Customer+'%') 

【讨论】:

好建议,但是如果一组中的公司名称是 Oracle Inc. 而另一组中的公司名称是 Oracle 会发生什么? 您需要扩展您的 ON 条件.. 只需查看更新后的代码。 这是非常短视的,它甚至不适用于问题中给出的所有示例。 我很欣赏@UnnikrishnanR 的建议,但我使用这种特定方法得到了太多误报匹配。但是,我可以使用此模板作为起点并针对我的数据对其进行调整。正如 iamdave 解释的那样,它目光短浅,需要大量的人工干预,但我想我暂时可以解决这个问题。再次感谢【参考方案4】:

您需要超过 1 个字段才能有效地完成此任务。你有城市、州、邮编、地址等信息吗?然后,您可以创建一个连接这些字段的多部分键。您可能希望将某些字符截断为前 5 个字符或其他内容,但变化越多,得到的误报就越多。

我已经这样做了,并创建了几个对每个键限制较少的键。然后匹配尝试每个键并在找到匹配项时分配匹配等级。

【讨论】:

也许可以提供一个实际的答案示例,以便更好地讨论 OP 的理解

以上是关于SQL 模糊连接 - MSSQL的主要内容,如果未能解决你的问题,请参考以下文章

mssql中一个简单的模糊查询语句怎么写请教大侠们

[转][Dapper]SQL 经验集

关于sql模糊查询(全字段)

PLSQL连接Oracle使用like模糊查询中文时返回结果为空

mssql 2000,以用户身份执行sql语句。

Mysql查询详解(条件查询、子查询、模糊查询、连接查询。。。)