查找包含类似字符串的sql记录

时间:2011-03-14 14:32:40

标签: sql sql-server sql-server-2008

我有下表有2列:ID和标题包含超过500.000条记录。例如:

ID  Title
--  ------------------------
1   Aliens
2   Aliens (1986)
3   Aliens vs Predator
4   Aliens 2
5   The making of "Aliens"

我需要找到非常相似的记录,并且我的意思是它们有3-6个字母不同,通常这个差异在标题的末尾。所以我必须设计一个返回记录号的查询。 1,2和4.我已经看过levenstein距离,但我不知道如何应用它。此外,由于记录的数量,查询不应该整夜。

感谢您的任何想法或建议

6 个答案:

答案 0 :(得分:13)

如果你真的想以你在问题中提出的确切方式定义相似性,那么你 - 如你所说 - 你必须实施Levensthein距离计算。在由DataReader检索的每一行上计算的代码中,或者作为SQL Server函数。

所陈述的问题实际上比初看起来更棘手,因为您不能假设知道两个字符串之间的共享元素是什么。

因此,除了Levensthein Distance之外,您可能还想指定实际必须匹配的最小连续字符数(以便得出足够的相似性)。

总之:这听起来像是一个过于复杂和耗时/慢的方法。

有趣的是,在SQL Server 2008中,您拥有DIFFERENCE函数,可以用于此类函数。

它评估两个字符串的语音值并计算差异。我不确定你是否能让它适用于多字表达式,如电影片,因为它不能很好地处理空格或数字,并过分强调字符串的开头,但它仍然是一个有趣的谓词要注意。

如果你实际试图描述的是某种搜索功能,那么你应该研究SQL Server 2008的Full Text Search功能。它提供了内置的{{3 ,花哨的SQL Thesaurus support和“最佳匹配”的排名机制

编辑:如果您希望消除重复项,也许您可​​以查看SSIS predicates。我自己没有试过这个,但它看起来很有希望。

EDIT2:如果您不想深入研究SSIS并且仍然在努力解决Levensthein Distance算法的性能问题,那么您可以尝试使用Fuzzy Lookup and Fuzzy Group Transformation,这似乎不那么复杂。

答案 1 :(得分:4)

对于遇到这个问题的所有Google员工,虽然它已被标记为已回答,但我想我会分享一些代码来帮助解决这个问题。如果您能够在SQL Server上执行CLR用户定义的函数,则可以实现自己的Levensthein Distance算法,然后从中创建一个函数,为您提供名为dbo.GetSimilarityScore()的“相似性得分”。我的分数不区分大小写,对于混乱的单词顺序和非字母数字字符没什么重要性。您可以根据需要调整评分算法,但这是一个好的开始。感谢this code project link让我入门。

Option Explicit On
Option Strict On
Option Compare Binary
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports System.Text
Imports System.Text.RegularExpressions
Imports Microsoft.SqlServer.Server

Partial Public Class UserDefinedFunctions

    Private Const Xms As RegexOptions = RegexOptions.IgnorePatternWhitespace Or RegexOptions.Multiline Or RegexOptions.Singleline
    Private Const Xmsi As RegexOptions = Xms Or RegexOptions.IgnoreCase

    ''' <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 score1 As SqlDouble = InternalGetSimilarityScore(s1, s2)
        If score1.IsNull Then Return SqlDouble.Null

        Dim mod1 As String = GetSimilarityString(s1)
        Dim mod2 As String = GetSimilarityString(s2)
        Dim score2 As SqlDouble = InternalGetSimilarityScore(mod1, mod2)
        If score2.IsNull Then Return SqlDouble.Null

        If score1 = 1.0F AndAlso score2 = 1.0F Then Return 1.0F
        If score1 = 0.0F AndAlso score2 = 0.0F Then Return 0.0F
        ' Return weighted result
        Return (score1 * 0.2F) + (score2 * 0.8F)
    End Function

    Private Shared Function InternalGetSimilarityScore(s1 As String, s2 As String) As SqlDouble
        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>
    ''' Removes all non-alpha numeric characters and then sorts
    ''' the words in alphabetical order.
    ''' </summary>
    Private Shared Function GetSimilarityString(s1 As String) As String
        Dim normString = Regex.Replace(If(s1, ""), "\W|_", " ", Xms)
        normString = Regex.Replace(normString, "\s+", " ", Xms).Trim()
        Dim words As New List(Of String)(normString.Split(" "c))
        words.Sort()
        Return String.Join(" ", words.ToArray())
    End Function

End Class

答案 2 :(得分:2)

select id, title
from my_table
where 
    title like 'Aliens%' 
    and 
    len(rtrim(title)) < len('Aliens') + 7

答案 3 :(得分:1)

根据你的要求,我想你所寻找的差异不应该只是原始标题末尾的一个单词。这就是为什么返回1,2和4的原因?

无论如何,我做了一个查询,检查最后的差异是由一个单词组成,没有空格。

declare @title varchar(20)
set @title = 'Aliens'
select id, title
from movies with (nolock)
where ltrim(title) like @title + '%'
and Charindex(' ', ltrim(right(title, len(title) - len(@title)))) = 0
and len(ltrim(right(title, len(title) - len(@title)))) < 7
希望它有所帮助。

答案 4 :(得分:0)

如果您使用的是sql server 2008,则应该可以使用FULLTEXT功能。

基本步骤是:

1)在列上创建全文索引。这将标记每个字符串(stremmers,splitters等)并让你搜索'LIKE THIS'字符串。

免责声明是我从来没有使用它,但我认为它可以做你想要的。

从这里开始阅读:http://msdn.microsoft.com/en-us/library/ms142571.aspx

答案 5 :(得分:-1)

您也可以在Oracle中使用utl_match。 enter link description here