【问题标题】:q-gram approximate matching optimisationsq-gram 近似匹配优化
【发布时间】:2010-12-28 15:54:26
【问题描述】:

我有一个包含 300 万个人记录的表,我想使用 q-gram 对其进行模糊匹配(例如姓氏)。我已经创建了一个 2-gram 的表链接到这个,但是在这个数据量(大约 5 分钟)上搜索性能不是很好。

我基本上有两个问题: (1)您能否提出任何提高性能以避免表扫描的方法(即必须计算搜索字符串和 300 万个姓氏之间的常见 q-gram) (2) 对于q-gram,如果A与B相似,C与B相似,是否意味着C与A相似?

亲切的问候

彼得

【问题讨论】:

    标签: sql sql-server fuzzy-search fuzzy-comparison


    【解决方案1】:

    您肯定见过到处都有模糊文本搜索。例如,您键入“stck”,但实际上是“stack”!有没有想过这些东西是如何工作的?

    有很多算法可以进行模糊文本匹配,每种算法都有自己的优缺点。最著名的是编辑距离和qgram。我今天想专注于 qgrams 并实现一个示例。

    基本上qgrams是最适合关系数据库的模糊字符串匹配算法。这很简单。 qgram 中的“q”将被替换为 2-gram 或 3-gram 甚至 4-gram 之类的数字。

    2-gram 意味着每个单词都被分解为一组两个字符的gram。 "Stack" 将被分解为一组 {"st", "ta", "ac", "ck"} 或 "database" 将被分解为 {"da","at","ta","ab ","ba","as","se"}.

    一旦单词被分解成 2-gram,我们就可以在数据库中搜索一组值而不是一个字符串。例如,如果用户输入错误的“stck”,任何搜索“stck”都不会匹配“stack”,因为缺少“a”,但 2-gram set {"st","tc","ck"} 有 2 行与 2-gram 堆栈相同!宾果游戏我们发现了一个非常接近的匹配。它与 2-gram 的数据库集没有任何共同之处,而与 2-gram 的“stat”集只有 1 个共同点,因此我们可以很容易地向用户建议他要键入的内容:第一个“stack”或第二个“star” ”。

    现在让我们使用 Sql Server 来实现它:假设一个假设的单词数据集。您需要在 2grams 和单词之间建立多对多关系。

    CREATE TABLE Grams(twog char(2), wordId int, PRIMARY KEY (twog, wordId))
    

    Grams 表应该聚集在前 twog 上,然后是 wordId 以提高性能。当您查询一个单词(例如堆栈)时,您将克数放入临时表中。首先让我们创建几百万个虚拟记录。

    --make millions of 2grams
     DECLARE @i int =0
     WHILE (@i<5000000)
     BEGIN
    -- a random 2gram
     declare @rnum1 char = CHAR(CAST(RAND()*28 AS INT)+97)
     declare @rnum2 char = CHAR(CAST(RAND()*28 AS INT)+97)
     INS... INTO Grams (twog, wordId) VALUES ( @rnum1 + @rnum2, CAST(RAND()*100000 AS int))
     END
    

    现在让我们查询单词“stack”,它将被分解为:{'st','ta','ac','ck'} 两克。

    DECLARE @word TABLE(twog char(2)) -- 'stack'
     INS... INTO @word VALUES ('st'), ('ta'), ('ac'), ('ck')
    
    select wordId, count(*) from @word w inner join Grams g ON w.twog = g.twog
     GROUP BY wordId
    

    您应该确保 Sql Server 使用一组聚集索引查找(或锁定)来运行此查询。这应该是自然的选择,但有时统计数据可能会损坏或过时,SqlServer 可能会认为完全扫描更便宜。如果它不知道左侧表的基数,通常会发生这种情况,例如 SqlServer 可能会假设 @word 表很大,并且数百万次查找将比全索引扫描更昂贵。

    【讨论】:

      【解决方案2】:

      我最近一直在研究模糊字符串匹配,所以即使冒着回答被遗弃的问题的风险,这里也可以。希望你觉得这很有用。

      我想您只对编辑距离小于给定值的字符串感兴趣。你的 q-gram(或 n-gram)看起来像这样

      2-grams for "foobar": {"fo","oo","ob","ba","ar"}
      
      1. 你可以使用 positional q-grams:

        "foobar": {("fo",1),("oo",2),("ob",3),("ba",4),("ar",5)}
        

        位置信息可用于确定是否匹配 q-gram 真的是一个“很好的匹配”。

        例如,如果您正在搜索 具有最大编辑距离的“foobar” 2,这意味着你只是 对单词感兴趣的地方

        2-gram "fo" exists in with position from 1 to 3 or
        2-gram "oo" exists in with position from 2 to 4 or
        ... and so on
        

        字符串“barfoo”没有得到任何 匹配,因为 否则匹配的 2-gram 不同 3.

      2. 另外,它可能有用 编辑距离之间的关系 以及匹配 q-gram 的计数。 直觉是,因为

        一个字符串 s 有 len(s)-q+1 个 q-grams

        单个编辑操作最多可以影响 q 个 q-gram,

        我们可以推断出

        编辑距离 d 内的字符串 s1 和 s2 至少有 max(len(s1),len(s2))-q+1-qk 匹配非位置q-gram。

        如果您正在搜索“foobar” 最大编辑距离为 2,匹配 7 个字符的字符串(例如 "fotocar") 至少应包含 两个常见的 2-gram。

      3. 最后,显而易见的事情是 按长度过滤。编辑 两个字符串之间的距离在 至少长度的差异 的字符串。例如,如果您的 阈值为 2,您搜索 “foobar”、“foobarbar”不能 明显匹配。

      请参阅http://pages.stern.nyu.edu/~panos/publications/deb-dec2001.pdf 了解更多和一些伪 SQL。

      【讨论】:

        【解决方案3】:

        关于索引 DNA q-gram 的有趣论文,因此您不必扫描整个表:

        www.comp.nus.edu.sg/~atung/publication/qgram_edit.pdf

        【讨论】:

          【解决方案4】:

          我有一个简单的改进,它不会消除扫描,但如果您只使用 2 克或 3 克,它会加快扫描速度:用数字替换字母。大多数 SQL 引擎在比较数字时工作得更快。

          示例:我们的源表在一列中包含文本条目。 我们创建一个临时表,在其中使用

          将名称拆分为 2-gram
          SELECT SUBSTRING (column, 1,2) as gram, 1 as position FROM sourcetable
          UNION  
          SELECT SUBSTRING (column, 2,2) as gram, 2 as position FROM sourcetable
          UNION
          SELECT SUBSTRING (column, 3,2) as gram, 3 as position FROM sourcetable
          
          etc. 
          

          这应该在 i=0 和 j=源条目的最大大小的循环中运行。

          然后我们准备一个映射表,其中包含所有可能的 2 字母gram,并包含一个名为 gram_id 的 IDENTITY (1,1) 列。我们可以在英语词典中按频率对克进行排序,并消除最不常见的克(如“kk”或“wq”)——这种排序可能需要一些时间和研究,但它会将最小的数字分配给最频繁的克,这如果我们可以将克数限制为 255,那么将提高性能,因为我们可以为 gram_id 使用 tinyint 列。

          然后我们从第一个临时表重建另一个临时表,我们使用 gram_id 而不是 gram。这成为主表。我们在 gram_id 列和 position 列上创建索引。

          然后当我们要比较一个文本字符串到主表时,我们首先将文本字符串拆分为 2-gram,然后将 2-gram 替换为它们的 gram_id(使用映射表),并将它们与主表之一

          这进行了很多比较,但大多数都是 2 位整数,这非常快。

          【讨论】:

            猜你喜欢
            • 2010-11-21
            • 1970-01-01
            • 1970-01-01
            • 2011-05-11
            • 1970-01-01
            • 1970-01-01
            • 2020-07-09
            • 2013-07-10
            • 1970-01-01
            相关资源
            最近更新 更多