2009-05-28 16 views
58

Mam tabelę Osoby z personaldata i tak dalej. Istnieje wiele kolumn, ale interesujące są tu: addressindex, lastname i firstname, gdzie addressindex to wyjątkowy adres wywiercony do drzwi mieszkania. Więc jeśli mam "jak poniżej" dwie osoby z lastname i jedną z firstnames są takie same, najprawdopodobniej są duplikatami.Dopasowanie rozmyte za pomocą T-SQL

Potrzebuję sposobu na wyświetlenie tych duplikatów.

tabledata: 

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

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

wiem, jak to zrobić, jeśli były, aby dopasować dokładnie na wszystkich kolumnach ale muszę rozmytej mecz rade z (z powyższego przykładu) wynik takiego:

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

żadnych wskazówek o tym, jak rozwiązać ten problem w dobry sposób?

+3

BTW sposób, to jest całkiem prawdopodobne, to nie jest ta sama osoba w danym przypadku. Ojcowie i synowie żyją razem w czasach, o których wiesz. – HLGEM

+1

To zawsze jest problem z pół-sprytnymi algorytmami oceny adresu. Możesz założyć, ale nigdy nie możesz być tego pewien. – Tomalak

+1

Dobra uwaga, chociaż Descition to kolejna kwestia oparta na wyniku rozmytego meczu. – Frederik

Odpowiedz

7

Chciałbym używać indeksowania pełnotekstowego SQL Server, który pozwoli ci wyszukiwać i zwracać rzeczy, które nie tylko zawierają słowo, ale także mogą mieć błędy w pisowni.

+0

tutaj jest fajny artykuł na ten temat: http://www.developer.com/db/article.php/3446891 –

+0

Thand, uważam, że nieco se używać standardowej edycji i wyszukiwanie pełnotekstowe nie jest tutaj opcja. – Frederik

+0

Wyszukiwanie pełnotekstowe jest dostępne we wszystkich wydaniach programu SQL Server 2005 i 2008 –

0

Możesz użyć funkcji SOUNDEX i pokrewnej DIFFERENCE w SQL Server, aby znaleźć podobne nazwy. Numer referencyjny na MSDN to here.

1

Jeśli chodzi o usuwanie duplikatów, podział i dopasowanie pasów to świetne pierwsze cięcie. Jeśli znane są dane dotyczące danych, które można wykorzystać w celu zmniejszenia obciążenia pracą i/lub uzyskania lepszych wyników, zawsze dobrze jest z nich skorzystać. Pamiętaj, że często w przypadku usuwania duplikatów niemożliwe jest całkowite wyeliminowanie pracy ręcznej, chociaż możesz zrobić to o wiele łatwiej, łapiąc tyle, ile możesz, a następnie generując raporty o twoich "przypadkach niepewności".

Jeśli chodzi o dopasowanie nazwy: SOUNDEX jest okropny pod względem jakości dopasowania, a szczególnie zły w odniesieniu do rodzaju pracy, którą próbujesz wykonać, ponieważ dopasowuje rzeczy, które są zbyt odległe od celu. Lepiej używać kombinacji podwójnych wyników metafonu i odległości Levenshteina, aby dopasować nazwę. Przy odpowiednim promowaniu działa to naprawdę dobrze i prawdopodobnie można go użyć do drugiego przejścia po oczyszczeniu znanych.

Możesz także rozważyć skorzystanie z pakietu SSIS i wyszukiwanie przekształceń Fuzzy Lookup i Grouping (http://msdn.microsoft.com/en-us/library/ms345128(SQL.90).aspx).

Korzystanie z funkcji wyszukiwania pełnotekstowego SQL (http://msdn.microsoft.com/en-us/library/cc879300.aspx) również jest możliwe, ale prawdopodobnie nie jest odpowiednie dla konkretnej domeny problemu.

4

Osobiście używam implementacji CLR algorytmu Jaro-Winkler, który wydaje się działać całkiem dobrze - ma problemy z ciągami dłuższymi niż około 15 znaków i nie lubi pasujących adresów e-mail, ale poza tym jest całkiem niezły - pełny przewodnik po implementacji znaleźć here

Jeśli nie jesteś w stanie korzystać z funkcji CLR z jakichkolwiek przyczyn, może można spróbować uruchomić danych za pośrednictwem pakietu SSIS (za pomocą rozmytej transformacja wyszukiwania) - szczegółowe here

17

Znalazłem że rzeczy SQL Server daje Ci zrobić rozmyty dopasowanie jest dość niezgrabne. Miałem naprawdę dużo szczęścia z własnymi funkcjami CLR przy użyciu algorytmu odległości Levenshteina i pewnej wagi. Korzystając z tego algorytmu, stworzyłem UDF o nazwie GetSimilarityScore, który pobiera dwa ciągi znaków i zwraca wynik między 0,0 a 1,0. Im bliżej 1,0 meczu, tym lepiej. Następnie zapytaj o próg> = 0,8 lub więcej, aby uzyskać najbardziej prawdopodobne dopasowania. Coś takiego:

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 

Po prostu nie rób tego z naprawdę dużymi tabelami. To powolny proces.

Oto 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

Brak dostępu do MDS i dziękuję, że nie pracuję z Big Data - wygląda na to, że świetnie pasuje. Bardzo doceniam szczegóły. – justSteve

0

zrobić to w ten sposób

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

a później utworzyć wyzwalacz

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

teraz, co mogę zrobić, to mogę utworzyć procedura podobna do tej

  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] 

to zwróci wszystkie imiona, które są prawie w meczu z nazwami przewidzianych dla danego PersonID

Powiązane problemy