2009-05-28 23 views
58

Tengo una tabla Personas con datos personales y más. Hay muchas columnas pero las que una vez fueron de interés aquí son: addressindex, lastname y firstname donde addressindex es una dirección única perforada hasta la puerta del apartamento. Así que si tengo 'como a continuación' dos personas con el lastname y uno el firstnames son los mismos que probablemente sean duplicados.Coincidencia difusa usando T-SQL

Necesito una forma de listar estos duplicados.

tabledata: 

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

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

Yo sé cómo hacer esto si tuviera que coincidir exactamente en todas las columnas pero necesito coincidencia parcial para hacer el truco con (en el ejemplo anterior) un resultado como:

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

Alguna pista sobre cómo resolver esto de una buena manera?

+3

Por cierto modo, es muy probable que no es la misma persona en el caso dado. Los padres y los hijos viven juntos a veces, ya sabes. – HLGEM

+1

Este es siempre el problema con los algoritmos de evaluación de direcciones semi-inteligentes. Puedes hacer una suposición, pero nunca puedes estar seguro. – Tomalak

+1

Buen punto aunque La descición es otro problema basado en el resultado de la coincidencia difusa. – Frederik

Respuesta

7

Usaría la indización de texto completo de SQL Server, que le permitirá realizar búsquedas y devolver cosas que no solo contengan la palabra sino que también puedan tener un error de ortografía.

+0

aquí hay un buen artículo al respecto: http://www.developer.com/db/article.php/3446891 –

+0

Thand, lo he considerado bit se use edición estándar y la búsqueda de texto completo no es una opción aquí. – Frederik

+0

La búsqueda de texto completo está disponible en todas las ediciones de SQL Server 2005 y 2008 –

0

Puede usar SOUNDEX y la función DIFERENCIA relacionada en SQL Server para buscar nombres similares. La referencia en MSDN es here.

14

Además de la otra buena información aquí, es posible que desee considerar el uso del algoritmo fonético Double Metaphone que es muy superior a SOUNDEX. Hay un Transact-SQL version (link to code here).

que ayudarán a los nombres que coincidan con ligeras faltas de ortografía, por ejemplo, Carl vs. Karl .

+0

Ese enlace está muerto. –

+0

@ Lèsemajesté Enlace fijo. – RedFilter

+0

Ese enlace ahora está muerto ... otra vez – codingbadger

1

En cuanto a las cosas para quitar el doblez, su partición y combinación de cuerdas es excelente primer corte. Si hay elementos conocidos sobre los datos que pueden aprovecharse para reducir la carga de trabajo y/o producir mejores resultados, siempre es bueno aprovecharlos. Tenga en cuenta que, a menudo, para eliminar el duplicado, es imposible eliminar por completo el trabajo manual, aunque puede hacerlo mucho más fácil capturando tanto como puede de forma automática y luego generando informes de sus "casos de incertidumbre".

En cuanto a la coincidencia de nombres: SOUNDEX es horrible para la calidad de coincidencia y especialmente malo para el tipo de trabajo que está tratando de hacer, ya que coincidirá con cosas que están demasiado lejos del objetivo. Es mejor usar una combinación de resultados de doble metafonía y la distancia de Levenshtein para realizar la coincidencia de nombres. Con un sesgo apropiado, esto funciona muy bien y probablemente podría usarse para un segundo pase después de hacer una limpieza en sus conocimientos.

Es posible que también desee considerar el uso de un paquete SSIS y buscar en las transformaciones de búsqueda difusa y agrupamiento (http://msdn.microsoft.com/en-us/library/ms345128(SQL.90).aspx).

El uso de la búsqueda de texto completo de SQL (http://msdn.microsoft.com/en-us/library/cc879300.aspx) también es una posibilidad, pero es probable que no sea apropiado para su dominio de problema específico.

4

Yo personalmente uso una implementación CLR del algoritmo Jaro-Winkler que parece funcionar bastante bien - tiene problemas con cadenas de más de 15 caracteres y no le gusta las direcciones de correo electrónico coincidentes, pero es bastante bueno - la guía completa de implementación puede se encuentran here

Si no puede utilizar funciones CLR por cualquier razón, tal vez usted podría intentar ejecutar los datos a través de un paquete SSIS (utilizando la transformación Búsqueda difusa) - detallada here

17

He descubierto que el material de SQL Server le da para hacer la coincidencia aproximada es bastante torpe. He tenido muy buena suerte con mis propias funciones de CLR usando el algoritmo de distancia de Levenshtein y algunas ponderaciones. Usando ese algoritmo, hice una UDF llamada GetSimilarityScore que toma dos cadenas y devuelve una puntuación entre 0.0 y 1.0. Cuanto más cerca de 1.0 esté el partido, mejor. Luego, consulte con un umbral de> = 0.8 o más para obtener las coincidencias más probables. Algo como esto:

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 

Simplemente no lo hagas con mesas muy grandes. Es un proceso lento

Aquí está la UDF CLR:

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

Al no tener acceso a MDS y agradecerle que no estoy trabajando con Big Data, esto parece una excelente opción. Apreciamos mucho los detalles. – justSteve

0

hacerlo de esta manera

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

y más tarde crear un disparador

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

ahora lo que puedo hacer es que puedo crear una procedimiento que se parece a esto

  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] 

esto le devolverá todos los nombres que son casi en coincidencia con los nombres proporcionados por un PersonaID particular,