您肯定见过到处都有模糊文本搜索。例如,您键入“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 表很大,并且数百万次查找将比全索引扫描更昂贵。