我有一张包含300万人记录的表格,我希望使用q-gram(例如姓氏)进行模糊匹配。我已经创建了一个2-gram的表格,但是这个数据量的搜索性能不是很好(大约5分钟)。
我基本上有两个问题: (1)你能否提出任何提高性能的方法来避免表扫描(即必须计算搜索字符串和300万个姓氏之间的常见q-gram) (2)对于q-gram,如果A类似于B而C类似于B,那么它是否意味着C类似于A?
亲切的问候
彼得
答案 0 :(得分:6)
我最近一直在研究模糊字符串匹配,所以即使冒着回答废弃问题的风险,也就是这样。希望你觉得这很有用。
我想你只对编辑距离小于给定值的字符串感兴趣。你的q-gram(或n-gram)看起来像这样
2-grams for "foobar": {"fo","oo","ob","ba","ar"}
您可以使用位置 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。
此外,可能有用 编辑距离之间的关系 以及匹配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克。
有关更多信息和一些伪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-gramSELECT 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位整数,这非常快。