q-gram近似匹配优化

时间:2009-12-21 07:28:06

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

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

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

亲切的问候

彼得

4 个答案:

答案 0 :(得分:6)

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

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

2-grams for "foobar": {"fo","oo","ob","ba","ar"}
  1. 您可以使用位置 q-gram:

    "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克相差不同 3。

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

    字符串s有len(s)-q + 1 q-gram

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

    我们可以推断出

    d的编辑距离内的

    字符串s1和s2至少有 max(len(s1),len(s2)) - q + 1-qk匹配非位置q-gram。

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

  3. 最后,显而易见的事情是 按长度过滤。编辑 两根弦之间的距离是 至少差异的长度 的字符串。例如,如果你的 阈值是2,你搜索 “foobar”,“foobarbar”不能 显然是匹配。
  4. 有关更多信息和一些伪SQL,请参阅http://pages.stern.nyu.edu/~panos/publications/deb-dec2001.pdf

答案 1 :(得分:5)

您确实看到过各处的模糊文本搜索。例如,您键入“stck”,但实际上您的意思是“堆栈”!曾经想知道这些东西是如何运作的?

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

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

2克意味着每个单词都被分成一组两个字符克。 “堆栈”将被分成一组{“st”,“ta”,“ac”,“ck”}或“数据库”将分为{“da”,“at”,“ta”,“ba” ”, “如”, “SE”}。

一旦单词被分成2克,我们就可以在数据库中搜索一组值而不是一个字符串。例如,如果用户输入错误“stck”,则任何搜索“stck”将不匹配“stack”,因为“a”缺失,但2-gram set {“st”,“tc”,“ck”}有2行与2克堆栈一样! Bingo我们找到了一个非常接近的比赛。它与2-gram数据库集没有任何共同之处,与2-gram“stat”集合只有1个共同点,因此我们可以轻松地向用户建议他打算输入:第一个“堆栈”或第二个,“明星” ”

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

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使用一堆聚簇索引搜索(或loockups)来运行此查询。它应该是自然的选择,但有时统计信息可能已损坏或过时,SqlServer可能会认为完整扫描更便宜。如果它不知道左侧表的基数,通常会发生这种情况,例如SqlServer可能会认为@word表是庞大的,并且数百万个loockup将比完整索引扫描更昂贵。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

我有一个简单的改进,不会消除扫描,但如果你只使用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_id的IDENTITY(1,1)列。我们可以在英语词典中按频率对克进行排序,并消除最不常见的克(如'kk'或'wq') - 这种排序可能需要一些时间和研究,但它会将最小的数字分配给最常见的克数,如果我们可以将克数限制为255,那么将提高性能,因为我们可以使用tinyint列作为gram_id。

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

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

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