【问题标题】:Fuzzy logic matching模糊逻辑匹配
【发布时间】:2016-10-24 23:45:08
【问题描述】:

因此,我正在考虑在我的公司中实施模糊逻辑匹配,但无法获得良好的结果。首先,我正在尝试将公司名称与其他公司提供的列表中的名称进行匹配。
我的第一次尝试是使用 soundex,但看起来 soundex 只比较公司名称中的前几个声音,因此较长的公司名称很容易相互混淆。
我现在正在使用莱文斯坦距离比较进行第二次尝试。它看起来很有希望,特别是如果我先删除标点符号。但是,在没有太多误报的情况下,我仍然无法找到重复项。
我遇到的问题之一是 widgetsco vs widgets inc 等公司。因此,如果我比较较短名称的长度的子字符串,我还会选择 BBC 大学和 CBC 大学校园之类的内容。我怀疑使用距离和最长公共子字符串组合的分数可能是解决方案。
有没有人设法建立一种算法,以有限的误报进行这种匹配?

【问题讨论】:

  • 我唯一要做的匹配是基于子字符串匹配,例如BBC 大学和 BBC 大学。这假设字符串的起始部分在所有重复项中是相同的。对于像widgets inc这样的情况,我会首先去掉实体类型的缩写。如果您有其他相关数据,例如公司地址,公司 CEO,那么这个也可以比较得出两个公司相同的可能性分数。在尝试实际比较数据之前,我会先专注于规范化数据(通过去除绒毛)。
  • 无论您使用什么算法,去除绒毛通常都是一个好习惯。
  • 我将详细说明规范化数据。对于“Smith & Wesson”与“Smith and Wesson”匹配的情况,为了匹配,我会将所有“&”转换为“and”。我还会尝试去除实体类型及其缩写(inc. co.)并检查这是否会产生误报。如果数据具有更复杂的结构,例如“CompanyA trading as CompanyB”,则需要对其进行解析。 IMO,模糊匹配公司名称等结构良好的数据是一项错误的工作。
  • 你读过类似的 SO 问题吗?例如。 stackoverflow.com/questions/921978/fuzzy-matching-using-t-sql
  • 你真正想做什么?导入数据时匹配名称?或纠正用户拼写错误? SQL Server 的数据库引擎不提供模糊匹配。即使是全文搜索也不能很好地匹配拼写错误的名称。 SSIS确实提供了模糊查找和模糊分组运算符,非常适合导入数据,对于纠正用户错误无用

标签: sql sql-server tsql sql-server-2012 string-matching


【解决方案1】:

首先,我建议,你确保你不能匹配任何其他属性,并且公司名称就是你所拥有的(因为模糊匹配必然会给你一些误报)。如果您想继续进行模糊匹配,可以使用以下步骤:

  1. 从文本中删除所有停用词。例如:Co, Inc 等。

  2. 如果您的数据库非常大,请使用索引方法,例如分块或排序邻域索引。

  3. 最后使用 Levenshtein 距离计算模糊分数。您可以使用 Fuzzywuzzy 中的 token_set_ratio 或 partial_ratio 函数。

另外,我发现了以下旨在解决相同问题的视频:https://www.youtube.com/watch?v=NRAqIjXaZvw

Nanetets 博客还包含一些关于该主题的资源,可能会有所帮助。

【讨论】:

    【解决方案2】:

    我发现成功实现了我在 Stack Overflow 上找到的一个函数,该函数可以找到匹配字符串的百分比。然后,您可以调整容差,直到获得适当数量的匹配/不匹配。函数实现将在下面列出,但要点是在您的查询中包含类似的内容。

    DECLARE @tolerance DEC(18, 2) = 50;
    WHERE dbo.GetPercentageOfTwoStringMatching(first_table.name, second_table.name) > @tolerance
    

    以下百分比匹配函数的功劳归于 Dragos Durlut,2011 年 12 月 15 日Dragos Durlut 的代码中包含了 LEVENSHTEIN 函数的功劳。

    T-SQL Get percentage of character match of 2 strings

    CREATE FUNCTION [dbo].[GetPercentageOfTwoStringMatching]
    (
        @string1 NVARCHAR(100)
        ,@string2 NVARCHAR(100)
    )
    RETURNS INT
    AS
    BEGIN
    
        DECLARE @levenShteinNumber INT
    
        DECLARE @string1Length INT = LEN(@string1)
        , @string2Length INT = LEN(@string2)
        DECLARE @maxLengthNumber INT = CASE WHEN @string1Length > @string2Length THEN @string1Length ELSE @string2Length END
    
        SELECT @levenShteinNumber = [dbo].[LEVENSHTEIN] (   @string1  ,@string2)
    
        DECLARE @percentageOfBadCharacters INT = @levenShteinNumber * 100 / @maxLengthNumber
    
        DECLARE @percentageOfGoodCharacters INT = 100 - @percentageOfBadCharacters
    
        -- Return the result of the function
        RETURN @percentageOfGoodCharacters
    
    END
    
    
    
    
    -- =============================================     
    -- Create date: 2011.12.14
    -- Description: http://blog.sendreallybigfiles.com/2009/06/improved-t-sql-levenshtein-distance.html
    -- =============================================
    
    CREATE FUNCTION [dbo].[LEVENSHTEIN](@left  VARCHAR(100),
                                    @right VARCHAR(100))
    returns INT
    AS
      BEGIN
          DECLARE @difference    INT,
                  @lenRight      INT,
                  @lenLeft       INT,
                  @leftIndex     INT,
                  @rightIndex    INT,
                  @left_char     CHAR(1),
                  @right_char    CHAR(1),
                  @compareLength INT
    
      SET @lenLeft = LEN(@left)
      SET @lenRight = LEN(@right)
      SET @difference = 0
    
      IF @lenLeft = 0
        BEGIN
            SET @difference = @lenRight
    
            GOTO done
        END
    
      IF @lenRight = 0
        BEGIN
            SET @difference = @lenLeft
    
            GOTO done
        END
    
      GOTO comparison
    
      COMPARISON:
    
      IF ( @lenLeft >= @lenRight )
        SET @compareLength = @lenLeft
      ELSE
        SET @compareLength = @lenRight
    
      SET @rightIndex = 1
      SET @leftIndex = 1
    
      WHILE @leftIndex <= @compareLength
        BEGIN
            SET @left_char = substring(@left, @leftIndex, 1)
            SET @right_char = substring(@right, @rightIndex, 1)
    
            IF @left_char <> @right_char
              BEGIN -- Would an insertion make them re-align?
                  IF( @left_char = substring(@right, @rightIndex + 1, 1) )
                    SET @rightIndex = @rightIndex + 1
                  -- Would an deletion make them re-align?
                  ELSE IF( substring(@left, @leftIndex + 1, 1) = @right_char )
                    SET @leftIndex = @leftIndex + 1
    
                  SET @difference = @difference + 1
              END
    
            SET @leftIndex = @leftIndex + 1
            SET @rightIndex = @rightIndex + 1
        END
    
      GOTO done
    
      DONE:
    
      RETURN @difference
      END 
    

    注意:如果您需要比较两个或多个字段(我认为您不会这样做),您可以在 WHERE 子句中添加另一个对函数的调用,并具有最小容差。我还发现成功平均百分比匹配并将其与容差进行比较。

    DECLARE @tolerance DEC(18, 2) = 25; 
    --could have multiple different tolerances for each field (weighting some fields as more important to be matching)
    DECLARE @avg_tolerance DEC(18, 2) = 50;
    
    WHERE AND dbo.GetPercentageOfTwoStringMatching(first_table.name, second_table.name) > @tolerance
          AND dbo.GetPercentageOfTwoStringMatching(first_table.address, second_table.address) > @tolerance
          AND (dbo.GetPercentageOfTwoStringMatching(first_table.name, second_table.name)
                + dbo.GetPercentageOfTwoStringMatching(first_table.address, second_table.address)
               ) / 2 > @avg_tolerance
    

    此解决方案的好处是容差变量可以是每个字段的特定变量(加权某些字段匹配的重要性),平均值可以确保所有字段的一般匹配。

    【讨论】:

      【解决方案3】:

      使用 Lawrence Philips 创建的 Metaphone 功能,我们在姓名和地址匹配方面取得了良好的效果。它的工作方式与 Soundex 类似,但为整个值创建了声音/辅音模式。您可能会发现这与其他一些技术结合使用很有用,特别是如果您可以去除一些绒毛,例如“co”。和“公司”如其他 cmets 所述:

      create function [dbo].[Metaphone](@str as nvarchar(70), @KeepNumeric as bit = 0)
      returns nvarchar(25)
          /*
          Metaphone Algorithm
      
          Created by Lawrence Philips.
          Metaphone presented in article in "Computer Language" December 1990 issue.
      
                       *********** BEGIN METAPHONE RULES ***********
           Lawrence Philips' RULES follow:
           The 16 consonant sounds:
                                                       |--- ZERO represents "th"
                                                       |
                B  X  S  K  J  T  F  H  L  M  N  P  R  0  W  Y
           Drop vowels
      
          Exceptions:
          Beginning of word: "ae-", "gn", "kn-", "pn-", "wr-"   ----> drop first letter
          Beginning of word: "wh-"                              ----> change to "w"
          Beginning of word: "x"                                ----> change to "s"
          Beginning of word: vowel or "H" + vowel               ----> Keep it
      
          Transformations:
          B ----> B       unless at the end of word after "m", as in "dumb", "McComb"
          C ----> X       (sh) if "-cia-" or "-ch-"
                  S       if "-ci-", "-ce-", or "-cy-"
                          SILENT if "-sci-", "-sce-", or "-scy-"
                  K       otherwise
                  K       "-sch-"
          D ----> J       if in "-dge-", "-dgy-", or "-dgi-"
                  T       otherwise
          F ----> F
          G ---->         SILENT if   "-gh-" and not at end or before a vowel
                                      "-gn" or "-gned"
                                      "-dge-" etc., as in above rule
                  J       if "gi", "ge", "gy" if not double "gg"
                  K       otherwise
          H ---->         SILENT  if after vowel and no vowel follows
                                  or "-ch-", "-sh-", "-ph-", "-th-", "-gh-"
                  H       otherwise
          J ----> J
          K ---->         SILENT if after "c"
                  K       otherwise
          L ----> L
          M ----> M
          N ----> N
          P ----> F       if before "h"
                  P       otherwise
          Q ----> K
          R ----> R
          S ----> X       (sh) if "sh" or "-sio-" or "-sia-"
                  S       otherwise
          T ----> X       (sh) if "-tia-" or "-tio-"
                  0       (th) if "th"
                          SILENT if "-tch-"
                  T       otherwise
          V ----> F
          W ---->         SILENT if not followed by a vowel
                  W       if followed by a vowel
          X ----> KS
          Y ---->         SILENT if not followed by a vowel
                  Y       if followed by a vowel
          Z ----> S
          */
      
      
      as
      begin
      declare @Result varchar(25)
              ,@str3  char(3)
              ,@str2  char(2)
              ,@str1  char(1)
              ,@strp  char(1)
              ,@strLen tinyint
              ,@cnt   tinyint
      
      set @strLen = len(@str)
      set @cnt = 0
      set @Result = ''
      
      -- Preserve first 5 numeric values when required
      if @KeepNumeric = 1
          begin
              set @Result = case when isnumeric(substring(@str,1,1)) = 1
                                  then case when isnumeric(substring(@str,2,1)) = 1
                                          then case when isnumeric(substring(@str,3,1)) = 1
                                                  then case when isnumeric(substring(@str,4,1)) = 1
                                                          then case when isnumeric(substring(@str,5,1)) = 1
                                                                      then left(@str,5)
                                                                      else left(@str,4)
                                                                      end
                                                          else left(@str,3)
                                                          end
                                                  else left(@str,2)
                                                  end
                                          else left(@str,1)
                                          end
                                  else ''
                                  end
      
              set @str = right(@str,len(@str)-len(@Result))
          end
      
      --Process beginning exceptions
      set @str2 = left(@str,2)
      
      if @str2 = 'wh'
          begin
              set @str  = 'w' + right(@str , @strLen - 2)
              set @strLen =   @strLen - 1
          end
      else
          if @str2 in('ae', 'gn', 'kn', 'pn', 'wr')
              begin
                  set @str = right(@str , @strLen - 1)
                  set @strLen = @strLen - 1
              end
      
      
      
      set @str1 = left(@str,1)
      
      if @str1 =  'x' 
          set @str  = 's' + right(@str , @strLen - 1)
      else
          if @str1 in ('a','e','i','o','u')
              begin
                  set @str = right(@str, @strLen - 1)
                  set @strLen = @strLen - 1
                  set @Result = @Result + @str1
              end
      
      while @cnt <= @strLen
          begin
              set @cnt = @cnt + 1
              set @str1 = substring(@str,@cnt,1)
      
              set @strp = case when @cnt <> 0
                              then substring(@str,(@cnt-1),1)
                              else ' '
                              end
      
              -- Check if the current character is the same as the previous character.
              -- If we are keeping numbers, only compare non-numeric characters.
              if case when @KeepNumeric = 1 and @strp = @str1 and isnumeric(@str1) = 0 then 1
                      when @KeepNumeric = 0 and @strp = @str1 then 1
                      else 0
                      end = 1
                  continue    -- Skip this loop
      
              set @str2 = substring(@str,@cnt,2)
      
              set @Result = case when @KeepNumeric = 1 and isnumeric(@str1) = 1
                                      then @Result + @str1
                                  when @str1 in('f','j','l','m','n','r')
                                      then @Result + @str1
                                  when @str1 = 'q'
                                      then @Result + 'k'
                                  when @str1 = 'v'
                                      then @Result + 'f'
                                  when @str1 = 'x'
                                      then @Result + 'ks'
                                  when @str1 = 'z'
                                      then @Result + 's'
                                  when @str1 = 'b'
                                      then case when @cnt = @strLen
                                                  then case when substring(@str,(@cnt - 1),1) <> 'm'
                                                          then @Result + 'b'
                                                      else @Result
                                                      end
                                              else @Result + 'b'
                                              end
                                  when @str1 = 'c'
                                      then case when @str2  = 'ch' or substring(@str,@cnt,3) = 'cia'
                                                  then @Result + 'x'
                                                  else case when @str2 in('ci','ce','cy') and @strp <> 's'
                                                          then @Result + 's'
                                                          else @Result + 'k'
                                                          end
                                                  end
                                  when @str1 = 'd'
                                      then case when substring(@str,@cnt,3) in ('dge','dgy','dgi')
                                                  then @Result + 'j'
                                                  else @Result + 't'
                                                  end
                                  when @str1 = 'g'
                                      then case when substring(@str,(@cnt - 1),3) not in ('dge','dgy','dgi','dha','dhe','dhi','dho','dhu')
                                                  then case when @str2 in('gi', 'ge','gy')
                                                          then @Result + 'j'
                                                          else case when @str2 <> 'gn' or (@str2 <> 'gh' and @cnt+1 <> @strLen)
                                                                      then @Result + 'k'
                                                                      else @Result
                                                                      end
                                                          end
                                                  else @Result
                                                  end
                                  when @str1 = 'h'
                                      then case when @strp not in ('a','e','i','o','u') and @str2 not in ('ha','he','hi','ho','hu')
                                                  then case when @strp not in ('c','s','p','t','g')
                                                              then @Result + 'h'
                                                              else @Result
                                                              end
                                                  else @Result
                                                  end
                                  when @str1 = 'k'
                                      then case when @strp <> 'c'
                                                  then @Result + 'k'
                                                  else @Result
                                                  end
                                  when @str1 = 'p'
                                      then case when @str2 = 'ph'
                                                  then @Result + 'f'
                                                  else @Result + 'p'
                                                  end
                                  when @str1 = 's'
                                      then case when substring(@str,@cnt,3) in ('sia','sio') or @str2 = 'sh'
                                                  then @Result + 'x'
                                                  else @Result + 's'
                                                  end
                                  when @str1 = 't'
                                      then case when substring(@str,@cnt,3) in ('tia','tio')
                                                  then @Result + 'x'
                                                  else case when @str2 = 'th'
                                                              then @Result + '0'
                                                              else case when substring(@str,@cnt,3) <> 'tch'
                                                                          then @Result + 't'
                                                                          else @Result
                                                                          end
                                                              end
                                                  end
                                  when @str1 = 'w'
                                      then case when @str2 not in('wa','we','wi','wo','wu')
                                                  then @Result + 'w'
                                                  else @Result
                                                  end
                                  when @str1 = 'y'
                                      then case when @str2 not in('ya','ye','yi','yo','yu')
                                                  then @Result + 'y'
                                                  else @Result
                                                  end
                                  else @Result
                                  end
          end
      
      return @Result
      
      end
      

      【讨论】:

        【解决方案4】:

        您在使用 Access 吗?如果是这样,请考虑不带引号的 '*' 字符。如果您使用的是 SQL Server,请使用“%”字符。然而,这真的不是模糊逻辑,它真的是 Like 运算符。如果您确实需要模糊逻辑,请将您的数据集导出到 Excel 并从下面的 URL 加载插件。

        https://www.microsoft.com/en-us/download/details.aspx?id=15011

        请仔细阅读说明。它确实有效,而且效果很好,但是您需要按照说明进行操作,而且它并不完全直观。第一次尝试时,我没有按照说明进行操作,浪费了很多时间试图让它工作。最后我弄明白了,效果很好!!

        【讨论】:

        • 问题是关于模糊匹配,而不是通配符。这是关于匹配This GuyThis uy
        【解决方案5】:

        您想使用 Levenshtein Distance 或其他字符串比较算法。您可能想看看 Codeplex 上的这个项目。

        http://fuzzystring.codeplex.com/

        【讨论】:

        • 我喜欢 Levenshtein Distance,但问题是它不是“模糊的”。 'CBC University' 和 'CBC Univer' 之间的距离是 4,即使它们可能是同一个地方。这个 SO 帖子有一个很好的“模糊”Levenshtein SQL Server Fuzzy Search with Percentage of match
        • @user918967 它模糊*。 CBC UniversityCBC Univer 的距离远大于 CBC Universit。链接中的答案显示了典型的 Levenshtein 实现。有趣的事情 - 我在问题 cmets 中介绍了替代方案!
        • @Yobik 那个项目很久以前就迁移到了github。正确的链接是github.com/kdjones/fuzzystring
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-17
        • 2011-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多