SQL 模糊连接 - MSSQL
SQL Fuzzy Join - MSSQL
我有两组数据。现有客户和潜在客户。
我的主要 objective 是弄清楚是否有任何潜在客户已经是现有客户。但是,客户跨数据集的命名约定是不一致的。
现有客户
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 语句来达到我的 objective:
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 距离和 Double Metaphone 的概念有点熟悉,但我不确定如何在这里应用它。
理想情况下,我希望 SELECT 语句的 JOIN 部分读取如下内容:LEFT JOIN ExistingCustomers as B WHERE a.Customer LIKE b.Customer
但我知道语法不正确。
欢迎提出任何建议。谢谢!
一种方法是在比较列的两侧使用 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+'%')
尝试在 SQL 内做到这一点将是一项持续的挑战,而且您不太可能获胜。您可以通过删除非 a-z 或 0-9 字符或尝试 Soundex or Metaphone matching or Levenshtein Distance 之类的方法来走得更远,但总会有 另一个 边缘情况,您没有在您所有的替换、通配符、拼音或简单的捏造。
如果您设法找到对您来说足够准确的东西,您就会遇到性能问题。
简而言之,您最好的希望是沿着 SQLCLR 路线走下去并在途中学习大量 C# 或根本不打扰,只需在源头清理数据或创建查找 table 的 'clean' 个名称随着新变体的出现需要不断维护。
以下是使用 Levenshtein 距离完成此操作的方法:
创建这个函数:(先执行这个)
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 示例
您需要超过 1 个字段才能有效地完成此操作。你有城市、州、邮编、地址等信息吗?然后,您可以创建一个将这些字段连接在一起的多部分密钥。您可能希望将某些内容截断为前 5 个字符或其他内容,但变化越大,误报就越多。
我已经这样做并创建了几个键,每个键的限制较少。然后匹配尝试每个键并在找到匹配项时分配匹配等级。
我有两组数据。现有客户和潜在客户。
我的主要 objective 是弄清楚是否有任何潜在客户已经是现有客户。但是,客户跨数据集的命名约定是不一致的。
现有客户
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 语句来达到我的 objective:
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 距离和 Double Metaphone 的概念有点熟悉,但我不确定如何在这里应用它。
理想情况下,我希望 SELECT 语句的 JOIN 部分读取如下内容:LEFT JOIN ExistingCustomers as B WHERE a.Customer LIKE b.Customer
但我知道语法不正确。
欢迎提出任何建议。谢谢!
一种方法是在比较列的两侧使用 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+'%')
尝试在 SQL 内做到这一点将是一项持续的挑战,而且您不太可能获胜。您可以通过删除非 a-z 或 0-9 字符或尝试 Soundex or Metaphone matching or Levenshtein Distance 之类的方法来走得更远,但总会有 另一个 边缘情况,您没有在您所有的替换、通配符、拼音或简单的捏造。
如果您设法找到对您来说足够准确的东西,您就会遇到性能问题。
简而言之,您最好的希望是沿着 SQLCLR 路线走下去并在途中学习大量 C# 或根本不打扰,只需在源头清理数据或创建查找 table 的 'clean' 个名称随着新变体的出现需要不断维护。
以下是使用 Levenshtein 距离完成此操作的方法:
创建这个函数:(先执行这个)
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 示例您需要超过 1 个字段才能有效地完成此操作。你有城市、州、邮编、地址等信息吗?然后,您可以创建一个将这些字段连接在一起的多部分密钥。您可能希望将某些内容截断为前 5 个字符或其他内容,但变化越大,误报就越多。
我已经这样做并创建了几个键,每个键的限制较少。然后匹配尝试每个键并在找到匹配项时分配匹配等级。