2009-05-28 2 views
58

У меня есть таблица Персоны с персональными данными и т. Д. Есть много колонок, но один раз интересны здесь: addressindex, lastname и firstname где addressindex - уникальный адрес, просверленный до двери квартиры. Итак, если у меня есть «как внизу» два человека с lastname, а один firstnames - это то же самое, что они, скорее всего, дубликаты.Нечеткое сопоставление с использованием T-SQL

Мне нужен способ перечислить эти дубликаты.

tabledata: 

personid  1 
firstname "Carl" 
lastname  "Anderson" 
addressindex 1 

personid  2 
firstname "Carl Peter" 
lastname  "Anderson" 
addressindex 1 

Я знаю, как это сделать, если бы я должен был точно соответствовать по всем столбцам, но мне нужно нечеткого матч, чтобы сделать трюк с (из приведенного выше примера) результат, как:

Row  personid  addressindex  lastname  firstname 
1  2    1    Anderson  Carl Peter 
2  1    1    Anderson  Carl 
..... 

Любые намеки о том, как решить это в хорошем смысле?

+3

BTW образом, вполне вероятно, это не то же лицо, в случае данного. Отцы и сыновья живут вместе, как вы знаете. – HLGEM

+1

Это всегда проблема с полу-умными алгоритмами оценки адресов. Вы можете сделать предположение, но вы никогда не сможете быть уверены. – Tomalak

+1

Хорошая точка, хотя проблема - это еще одна проблема, основанная на результате нечеткого совпадения. – Frederik

ответ

7

Я бы использовал SQL Server Full Text Indexing, который позволит вам выполнять поиск и возвращать вещи, которые не только содержат слово, но также могут иметь орфографическую ошибку.

+0

вот хорошая статья на нем: http://www.developer.com/db/article.php/3446891 –

+0

Thand, я подумал, что это стандартное издание, использующее стандартную версию, и полный текстовый поиск здесь не вариант. – Frederik

+0

Полнотекстовый поиск доступен во всех выпусках SQL Server 2005 и 2008 –

0

Вы можете использовать SOUNDEX и соответствующую функцию DIFFERENCE в SQL Server, чтобы найти похожие имена. Ссылка на MSDN: here.

14

В дополнение к другой полезной информации здесь вы можете рассмотреть использование фонетического алгоритма Double Metaphone, который значительно превосходит SOUNDEX. Существует Transact-SQL version (link to code here).

Это будет способствовать совпадающие имена с небольшими опечатками, например, Carl против Карл.

+0

Эта ссылка мертва. –

+0

@ Lèsemajesté Ссылка исправлена. – RedFilter

+0

Эта ссылка сейчас мертва ... снова – codingbadger

1

Что касается де-дубинга вещей, то ваша раскол и совпадение строк великолепны при первом разрезе. Если известны данные о данных, которые могут быть использованы для уменьшения рабочей нагрузки и/или получения лучших результатов, всегда полезно воспользоваться ими. Имейте в виду, что часто для устранения дублирования невозможно полностью исключить ручную работу, хотя вы можете сделать это намного проще, поймав столько, сколько сможете, и затем создайте отчеты о своих «случаях неопределенности».

Что касается соответствия имени: SOUNDEX является ужасным для качества соответствия и особенно плохого для типа работы, которую вы пытаетесь сделать, поскольку это будет соответствовать вещам, которые находятся слишком далеко от цели. Лучше использовать комбинацию двойных результатов метафонов и расстояние Левенштейна для выполнения сопоставления имен. При соответствующем смещении это работает очень хорошо и, вероятно, может быть использовано для второго прохода после выполнения очистки ваших известных.

Возможно, вы также захотите рассмотреть возможность использования пакета SSIS и поиск преобразований нечеткого поиска и группировки (http://msdn.microsoft.com/en-us/library/ms345128(SQL.90).aspx).

Использование полнотекстового поиска по SQL (http://msdn.microsoft.com/en-us/library/cc879300.aspx) также возможно, но, вероятно, не подходит для вашего конкретного проблемного домена.

4

Я лично использую реализацию CLR алгоритма Jaro-Winkler, который, похоже, работает очень хорошо - он немного борется со строками длиной более 15 символов и не любит сопоставлять адреса электронной почты, но в остальном неплохо - полное руководство по реализации может найти here

Если вы не можете использовать функции CLR по каким-либо причинам, может быть, вы могли бы попробовать запустить данные через пакет SSIS (с использованием нечеткой поиск преобразования) - подробный here

7

с первого выпуска Master Data Services, у вас есть доступ к более продвинутым алгоритмам с нечеткой логикой, чем то, что S OUNDEX реализует. Таким образом, при условии, что у вас установлен MDS, вы сможете найти функцию под названием Сходство() в схеме mdq (база данных MDS).

Более подробная информация о том, как это работает: http://blog.hoegaerden.be/2011/02/05/finding-similar-strings-with-fuzzy-logic-functions-built-into-mds/

17

Я обнаружил, что материал SQL Server дает вам сделать нечеткое соответствие довольно неуклюжая. Мне очень повезло с моими собственными функциями CLR, используя алгоритм расстояния Левенштейна и некоторый вес. Используя этот алгоритм, я сделал UDF под названием GetSimilarityScore, который берет две строки и возвращает результат между 0.0 и 1.0. Чем ближе к 1.0 матч, тем лучше. Затем запросите с порогом> = 0,8 или около того, чтобы получить наиболее вероятные совпадения. Что-то вроде этого:

if object_id('tempdb..#similar') is not null drop table #similar 
select a.id, (
    select top 1 x.id 
    from MyTable x 
    where x.id <> a.id 
    order by dbo.GetSimilarityScore(a.MyField, x.MyField) desc 
) as MostSimilarId 
into #similar 
from MyTable a 

select *, dbo.GetSimilarityScore(a.MyField, c.MyField) 
from MyTable a 
join #similar b on a.id = b.id 
join MyTable c on b.MostSimilarId = c.id 

Просто не делайте этого с действительно большими столами. Это медленный процесс.

Вот CLR UDFs:

''' <summary> 
''' Compute the distance between two strings. 
''' </summary> 
''' <param name="s1">The first of the two strings.</param> 
''' <param name="s2">The second of the two strings.</param> 
''' <returns>The Levenshtein cost.</returns> 
<Microsoft.SqlServer.Server.SqlFunction()> _ 
Public Shared Function ComputeLevenstheinDistance(ByVal string1 As SqlString, ByVal string2 As SqlString) As SqlInt32 
    If string1.IsNull OrElse string2.IsNull Then Return SqlInt32.Null 
    Dim s1 As String = string1.Value 
    Dim s2 As String = string2.Value 

    Dim n As Integer = s1.Length 
    Dim m As Integer = s2.Length 
    Dim d As Integer(,) = New Integer(n, m) {} 

    ' Step 1 
    If n = 0 Then Return m 
    If m = 0 Then Return n 

    ' Step 2 
    For i As Integer = 0 To n 
     d(i, 0) = i 
    Next 

    For j As Integer = 0 To m 
     d(0, j) = j 
    Next 

    ' Step 3 
    For i As Integer = 1 To n 
     'Step 4 
     For j As Integer = 1 To m 
      ' Step 5 
      Dim cost As Integer = If((s2(j - 1) = s1(i - 1)), 0, 1) 

      ' Step 6 
      d(i, j) = Math.Min(Math.Min(d(i - 1, j) + 1, d(i, j - 1) + 1), d(i - 1, j - 1) + cost) 
     Next 
    Next 
    ' Step 7 
    Return d(n, m) 
End Function 

''' <summary> 
''' Returns a score between 0.0-1.0 indicating how closely two strings match. 1.0 is a 100% 
''' T-SQL equality match, and the score goes down from there towards 0.0 for less similar strings. 
''' </summary> 
<Microsoft.SqlServer.Server.SqlFunction()> _ 
Public Shared Function GetSimilarityScore(string1 As SqlString, string2 As SqlString) As SqlDouble 
    If string1.IsNull OrElse string2.IsNull Then Return SqlInt32.Null 

    Dim s1 As String = string1.Value.ToUpper().TrimEnd(" "c) 
    Dim s2 As String = string2.Value.ToUpper().TrimEnd(" "c) 
    If s1 = s2 Then Return 1.0F ' At this point, T-SQL would consider them the same, so I will too 

    Dim flatLevScore As Double = InternalGetSimilarityScore(s1, s2) 

    Dim letterS1 As String = GetLetterSimilarityString(s1) 
    Dim letterS2 As String = GetLetterSimilarityString(s2) 
    Dim letterScore As Double = InternalGetSimilarityScore(letterS1, letterS2) 

    'Dim wordS1 As String = GetWordSimilarityString(s1) 
    'Dim wordS2 As String = GetWordSimilarityString(s2) 
    'Dim wordScore As Double = InternalGetSimilarityScore(wordS1, wordS2) 

    If flatLevScore = 1.0F AndAlso letterScore = 1.0F Then Return 1.0F 
    If flatLevScore = 0.0F AndAlso letterScore = 0.0F Then Return 0.0F 

    ' Return weighted result 
    Return (flatLevScore * 0.2F) + (letterScore * 0.8F) 
End Function 

Private Shared Function InternalGetSimilarityScore(s1 As String, s2 As String) As Double 
    Dim dist As SqlInt32 = ComputeLevenstheinDistance(s1, s2) 
    Dim maxLen As Integer = If(s1.Length > s2.Length, s1.Length, s2.Length) 
    If maxLen = 0 Then Return 1.0F 
    Return 1.0F - Convert.ToDouble(dist.Value)/Convert.ToDouble(maxLen) 
End Function 

''' <summary> 
''' Sorts all the alpha numeric characters in the string in alphabetical order 
''' and removes everything else. 
''' </summary> 
Private Shared Function GetLetterSimilarityString(s1 As String) As String 
    Dim allChars = If(s1, "").ToUpper().ToCharArray() 
    Array.Sort(allChars) 
    Dim result As New StringBuilder() 
    For Each ch As Char In allChars 
     If Char.IsLetterOrDigit(ch) Then 
      result.Append(ch) 
     End If 
    Next 
    Return result.ToString() 
End Function 

''' <summary> 
''' Removes all non-alpha numeric characters and then sorts 
''' the words in alphabetical order. 
''' </summary> 
Private Shared Function GetWordSimilarityString(s1 As String) As String 
    Dim words As New List(Of String)() 
    Dim curWord As StringBuilder = Nothing 
    For Each ch As Char In If(s1, "").ToUpper() 
     If Char.IsLetterOrDigit(ch) Then 
      If curWord Is Nothing Then 
       curWord = New StringBuilder() 
      End If 
      curWord.Append(ch) 
     Else 
      If curWord IsNot Nothing Then 
       words.Add(curWord.ToString()) 
       curWord = Nothing 
      End If 
     End If 
    Next 
    If curWord IsNot Nothing Then 
     words.Add(curWord.ToString()) 
    End If 

    words.Sort(StringComparer.OrdinalIgnoreCase) 
    Return String.Join(" ", words.ToArray()) 
End Function 
+1

Не имея доступа к MDS и благодарность за то, что я не работаю с Big Data - это выглядит великолепно. Высоко ценим детали. – justSteve

0

сделать это таким образом

  create table person(
     personid int identity(1,1) primary key, 
     firstname varchar(20), 
     lastname varchar(20), 
     addressindex int, 
     sound varchar(10) 
     ) 

и позже создать триггер

  create trigger trigoninsert for dbo.person 
     on insert 
     as 
     declare @personid int; 
     select @personid=personid from inserted; 
     update person 
     set sound=soundex(firstname) where [email protected]; 

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

  create procedure getfuzzi(@personid int) 
      as 
     declare @sound varchar(10); 
     set @sound=(select sound from person where [email protected]; 
     select personid,firstname,lastname,addressindex from person 
     where [email protected] 

это вернет вам все имена, которые почти в матче с именами, предусмотренных для конкретного PersonId

Смежные вопросы