2016-08-31 4 views
1

У меня есть два набора данных. Существующие клиенты и потенциальные клиенты.SQL Fuzzy Join - MSSQL

Моя основная цель - выяснить, есть ли у любого из потенциальных клиентов уже существующие клиенты. Тем не менее, соглашения об именах клиентов в наборах данных непоследовательны.

СУЩЕСТВУЮЩИЕ КЛИЕНТЫ

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 

Я смутно знакомы с понятиями Левенштейна и Двойной метафон, но я не знаю, как применить его здесь.

В идеале я хотел бы, чтобы часть JOIN оператора SELECT читала что-то вроде: LEFT JOIN ExistingCustomers as B WHERE a.Customer LIKE b.Customer, но я знаю, что этот синтаксис неверен.

Любые предложения приветствуются. Спасибо!

ответ

2

Вот как это может быть сделано с помощью Левенштейна:

Создать эту функцию: (Execute это первый)

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, @[email protected], 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, @[email protected]+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 

(Функция развившейся Джозефом Гама)

, а затем просто использовать этот запрос, чтобы получить совпадения

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; 

Полный Script af тер вы создаете функцию:

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; 

Здесь вы можете найти T-SQL пример в http://www.kodyaz.com/articles/fuzzy-string-matching-using-levenshtein-distance-sql-server.aspx

+0

Ближе всего я дошел до сих пор, но все же некоторые проблемы. Во-первых, для выполнения запроса требуется очень много времени. Во-вторых, я получаю большое количество ложноположительных матчей. Например, один из моих потенциальных клиентов соответствует 700 существующим клиентам ... Есть ли способ получить расстояние levenshtein как столбец в инструкции select? – hansolo

+1

использование этого: SELECT A.Customer, b.ID, b.Customer, \t 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, '', '')) <5; –

+0

вам нужно играть с дистанцией и посмотреть, какие сюиты вам лучше всего ... прямо сейчас его соответствие «<5». Как и предлагалось iamdave, производительность всегда будет проблемой с такими типами совпадений –

2

Один из способов - использовать функцию 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+'%') 
+0

хорошее предложение, но что произойдет, если название компании в одном из множества ** Oracle Inc ** и **. Oracle ** в другом? – hansolo

+0

вам нужно расширить свое состояние ON. Просто просмотрите обновленный код. –

+0

Это настолько невероятно недальновидно, что даже не работает на всех примерах, заданных в вопросе. – iamdave

3

Попытка сделать это в рамках SQL будет постоянной проблемой и тем, что вы вряд ли выиграете. Вы можете пойти довольно далеко, убрав без аз или 0-9 символов или попробовав что-то вроде Soundex или Metaphone совпадения или Levenshtein Distance, но всегда будет еще один краевой кейс, который вы не забрали во всех своих заменах, фонетирование или обычное подтасовка.

Если вам удалось найти что-то, что работает с достаточной точностью для вас, вы тогда столкнетесь с проблемами производительности.

Короче говоря, ваша лучшая надежда идет по маршруту SQLCLR и изучает много C# по пути или вообще не беспокоит и просто чистит ваши данные в источнике или создает таблицу поиска «чистых» имен, которые потребует постоянного обслуживания при появлении новых вариантов.

+0

довольно уверен, что я хочу использовать пример Leveshtein Distance, который вы выложили. Я понимаю, что это ограничения, но я просто занимаюсь поисковыми задачами, но пока не пытаюсь автоматизировать. С учетом сказанного у меня есть элементарный вопрос. Для меня достаточно просто написать сценарий функции Levenshtein, но могли бы вы помочь мне понять, как я могу передать два значения, PRE EXISTING клиентов и ПОТЕНЦИАЛЬНЫХ клиентов, в функцию? – hansolo