2009-05-28 8 views
58

Ich habe eine Tabelle Personen mit personaldata und so weiter. Es gibt viele Spalten, aber die ein Mal von Interesse hier sind: addressindex, lastname und firstname wo addressindex ist eine einzigartige Adresse bis zur Tür der Wohnung gebohrt. Also wenn ich 'wie unten' zwei Personen mit der lastname und eine der firstnames sind die gleichen sind, sie sind wahrscheinlich Duplikate.Fuzzy-Matching mit T-SQL

Ich brauche eine Möglichkeit, diese Duplikate aufzulisten.

tabledata: 

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

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

Ich weiß, wie dies zu tun, wenn ich genau auf alle Spalten passen waren, aber ich brauche Fuzzy-Match mit den Trick zu tun (aus dem obigen Beispiel) ein Ergebnis wie:

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

Irgendwelche Hinweise Wie kann man das gut lösen?

+3

BTW Weise zur Verfügung gestellt Namen fast in Übereinstimmung ist, ist es sehr wahrscheinlich, es gegeben nicht die gleiche Person in dem Fall ist. Väter und Söhne leben manchmal zusammen, wie du weißt. – HLGEM

+1

Dies ist immer das Problem mit semi-cleveren Adressauswertungsalgorithmen. Sie können eine Annahme machen, aber Sie können nie sicher sein. – Tomalak

+1

Guter Punkt, obwohl Die Angabe ist ein anderes Problem, das auf dem Ergebnis der Fuzzy-Übereinstimmung basiert. – Frederik

Antwort

7

Ich würde SQL Server-Volltext-Indexierung verwenden, die es Ihnen ermöglichen wird, Suchvorgänge durchzuführen und Dinge zurückzugeben, die nicht nur das Wort enthalten, sondern möglicherweise auch falsch geschrieben sind.

+0

hier ist ein schöner Artikel darüber: http://www.developer.com/db/article.php/3446891 –

+0

Thand, ich habe es in Betracht gezogen, bit se verwenden Standard Edition und Volltextsuche ist keine Option hier. – Frederik

+0

Volltextsuche ist in allen Editionen von SQL Server 2005 und 2008 verfügbar –

0

Sie können die SOUNDEX- und verwandte DIFFERENCE-Funktion in SQL Server verwenden, um ähnliche Namen zu finden. Die Referenz auf MSDN ist here.

14

Zusätzlich zu den anderen guten Informationen hier, möchten Sie vielleicht mit dem Double Metaphone phonetischen Algorithmus überlegen, der SOUNDEX viel überlegen ist. Es gibt eine Transact-SQL version (link to code here).

, die in passenden Namen, die mit leichten Fehlbuchstabierungen unterstützen, z.B. Carl vs. Karl.

+0

Diese Verbindung ist tot. –

+0

@ Lèsemajesté Link behoben. – RedFilter

+0

Dieser Link ist jetzt tot ... wieder – codingbadger

1

In Bezug auf die Deduplizierung von Dingen ist Ihre Saite aufgeteilt und passt gut zum ersten Schnitt. Wenn es Daten über die Daten gibt, die genutzt werden können, um die Arbeitsbelastung zu reduzieren und/oder bessere Ergebnisse zu erzielen, ist es immer gut, sie zu nutzen. Denken Sie daran, dass es oft nicht möglich ist, manuelle Arbeit zu dedupfen, obwohl Sie dies viel einfacher machen können, indem Sie so viel wie möglich automatisch erfassen und dann Berichte über Ihre "Ungewissheitsfälle" generieren.

In Bezug auf Namensanpassung: SOUNDEX ist schrecklich für die Qualität des Abgleichs und besonders schlecht für die Art der Arbeit, die Sie versuchen zu tun, da es Dinge zusammenbringt, die zu weit vom Ziel entfernt sind. Es ist besser, eine Kombination aus doppelten Metaphone-Ergebnissen und der Levenshtein-Distanz zu verwenden, um einen Namensvergleich durchzuführen. Bei entsprechender Vorspannung funktioniert das sehr gut und könnte wahrscheinlich für einen zweiten Durchlauf verwendet werden, nachdem Sie Ihre Bekannten gereinigt haben.

Sie können auch in Erwägung ziehen, ein SSIS-Paket zu verwenden und Fuzzy-Lookup- und Gruppierungsumwandlungen zu untersuchen (http://msdn.microsoft.com/en-us/library/ms345128(SQL.90).aspx).

Die Verwendung der SQL-Volltextsuche (http://msdn.microsoft.com/en-us/library/cc879300.aspx) ist ebenfalls möglich, ist aber wahrscheinlich nicht für Ihre spezifische Problemdomäne geeignet.

4

Ich persönlich benutze eine CLR-Implementierung des Jaro-Winkler Algorithmus, der ziemlich gut funktioniert - es kämpft ein wenig mit Strings länger als etwa 15 Zeichen und mag keine übereinstimmenden E-Mail-Adressen, aber ansonsten ist ziemlich gut - vollständige Implementierungshilfe kann CLR-Funktionen zu verwenden, für welche Gründe auch immer zu finden here

Wenn Sie nicht in der Lage sind, vielleicht versuchen Sie könnten die Daten durch ein SSIS-Paket ausgeführt wird (unter Verwendung des Fuzzy-Transformation-Lookup) - detaillierten here

17

ich gefunden habe, dass die Sachen SQL Server gibt Ihnen Fuzzy Matching zu tun ist ziemlich klobig. Ich hatte wirklich viel Glück mit meinen eigenen CLR-Funktionen unter Verwendung des Levenshtein-Distanzalgorithmus und einiger Gewichtung. Mit diesem Algorithmus habe ich dann eine UDF mit dem Namen GetSimilarityScore erstellt, die zwei Strings verwendet und einen Wert zwischen 0.0 und 1.0 liefert. Je näher das Spiel an 1,0 liegt, desto besser. Dann Abfrage mit einem Schwellenwert von> = 0.8 oder so, um die wahrscheinlichsten Übereinstimmungen zu erhalten. Etwas wie das:

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 

Tun Sie es einfach nicht mit wirklich großen Tabellen. Es ist ein langsamer Prozess.

Hier ist die CLR UDF:

''' <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

Ich habe keinen Zugang zu MDS und bedanke mich, dass ich nicht mit Big Data arbeite - das sieht gut aus. Sehr schätzen die Details. – justSteve

0

es tun auf diese Weise

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

und später auf einen Trigger erstellen

  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]; 

jetzt, was ich tun kann, ist, ich kann ein erstellen Verfahren, das in etwa so aussieht

  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] 

dies kehrt Sie alle Namen, die von für einen bestimmten PersonID mit den