你如何使用T-SQL全文搜索来获得像谷歌这样的结果?

时间:2014-05-22 20:00:04

标签: sql sql-server

我需要什么

我有一个数据库,其中的字段可以包含长词。我希望能够在这些列中快速搜索关键字或词组,但在搜索词组时,我希望能够像Google那样搜索词组,返回包含所有指定词的所有行,但不是特别的订单或"亲近"对彼此。此时不需要按相关性对结果进行排名。

在阅读了SQL Server的Full-Text Search之后,我认为这正是我所需要的:基于文本列中每个单词的可搜索索引。我的最终目标是安全地接受用户输入并将其转换为利用全文搜索速度的查询,同时保持用户的易用性。

问题:全文搜索功能不像Google那样搜索

我看到FREETEXT function可以使用整个短语,将其分解为"有用的"单词(忽略像'和','或',''等),然后非常快速地返回匹配行列表,即使是复杂的搜索词。但是当您尝试使用它时,您可能会注意到,而不是AND搜索每个术语,它似乎只进行OR搜索。也许有一种方法可以改变它的行为,但我还没有发现任何有用的东西。

然后有CONTAINS,它可以接受布尔查询短语,但有时会产生奇怪的结果。

查看此表中的以下查询:

数据

PKID    Name
-----   -----
1       James Kirk
2       James Cameron
3       Kirk Cameron
4       Kirk For Cameron

查询

Q1: SELECT Name FROM tblName WHERE FREETEXT(Name, 'james')
Q2: SELECT Name FROM tblName WHERE FREETEXT(Name, 'james kirk')
Q3: SELECT Name FROM tblName WHERE FREETEXT(Name, 'kirk for cameron')
Q4: SELECT Name FROM tblName WHERE CONTAINS(Name, 'james')
Q5: SELECT Name FROM tblName WHERE CONTAINS(Name, '"james kirk"')
Q6: SELECT Name FROM tblName WHERE CONTAINS(Name, '"kirk james"')
Q7: SELECT Name FROM tblName WHERE CONTAINS(Name, 'james AND kirk')
Q8: SELECT Name FROM tblName WHERE CONTAINS(Name, 'kirk AND for AND cameron')

查询1:

SELECT Name FROM tblName WHERE FREETEXT(Name, 'james')

返回" James Kirk"和詹姆斯卡梅隆"。好吧,让我们把它缩小......

查询2:

SELECT Name FROM tblName WHERE FREETEXT(Name, 'james kirk')

猜猜是什么。现在,您将获得James Kirk"," James Cameron"以及" Kirk For Cameron"。 查询3 也是如此,所以让我们跳过它。

查询4:

SELECT Name FROM tblName WHERE CONTAINS(Name, 'james')

与查询1的结果相同。好的。缩小结果可能......?

查询5:

SELECT Name FROM tblName WHERE CONTAINS(Name, '"james kirk"')

发现如果有空格需要将字符串括在双引号中,我发现这个查询在这个特定的数据集上对我想要的结果很有效!只有" James Kirk"退回。精彩!或者是......

查询6:

SELECT Name FROM tblName WHERE CONTAINS(Name, '"kirk james"')

垃圾。不,它匹配那个确切的短语。嗯...在检查syntax for T-SQL's CONTAINS function之后,我看到你可以在那里抛出布尔关键字,看起来这可能就是答案。我们来看看......

查询7:

SELECT Name FROM tblName WHERE CONTAINS(Name, 'james AND kirk')

纯。正如所料,我得到了所有三个结果。现在我只写一个函数来填充所有单词之间的单词AND。做完了吧?那现在......

查询8:

SELECT Name FROM tblName WHERE CONTAINS(Name, 'kirk AND for AND cameron')

此查询确切地知道它正在寻找什么,除了某些原因,没有结果。为什么?在阅读了Stopwords and Stoplists之后,我会做出有根据的猜测并说出来,因为我要求" kirk",""的索引结果的交叉点;和"卡梅伦",和#34;用于"将不会有任何结果(它是一个停用词和所有结果),那么与该结果的任何交集的结果也是空的。它实际上是否具有这样的功能与我无关,因为每当我在那里进行带有停用词的布尔搜索时,这就是CONTAINS函数的可观察行为。

所以我需要一个新的解决方案。

NEAR

看起来很有希望。如果我可以接受用户查询并在其之间添加逗号,那么这将...等待这与在AND查询中使用布尔CONTAINS相同。但它是否正确地忽略了停用词?

SELECT Name FROM tblName WHERE CONTAINS(Name, 'NEAR(kirk, for, cameron)')

不。没有结果。删除单词" for",然后再次获得所有三个结果。 :(

现在怎么办?

4 个答案:

答案 0 :(得分:3)

我发现another question on here处理同一主题。实际上,详细说明该方法的帖子甚至标题为“A Google-like Full Text Search”。它使用一个名为Irony的开源库来解析用户输入的搜索字符串,并将其转换为与FTS兼容的查询。

以下是类似Google的全文搜索的source code for the latest version

答案 1 :(得分:2)

我正在使用ISAbout结合 THESAURUS INFLECTIONAL 通配符
优点是
1 - 搜索字符串中的单词顺序无关紧要 2 - 搜索类似的词语( THESAURUS
3 - 对待运行,运行,运行,运行相同( INFLECTIONAL
4 - 如果搜索字符串中只有一个元素不在结果字符串中,则Near不返回结果,但ISAbout将始终返回最理想的结果
5 - 您可以设置不同单词的权重,这将进一步帮助您优化结果的正确性

SELECT   K.RANK, name, Description
FROM      Diagnosis AS C
INNER JOIN
CONTAINSTABLE(diagnosis,name,<br> 'isAbout(FORMSOF (THESAURUS, "CHRONIC") weight(1.0),FORMSOF (INFLECTIONAL, "CHRONIC") weight(1.0),CHRONIC* weight(1.0)
FORMSOF (THESAURUS, "FAILURE") weight(1.0),FORMSOF (INFLECTIONAL, "FAILURE") weight(1.0),FAILURE* weight(1.0),
FORMSOF (THESAURUS, "DIASTOLIC") weight(1.0),FORMSOF (INFLECTIONAL, "DIASTOLIC") weight(1.0),DIASTOLIC* weight(1.0))')
AS K
ON C.ID = K.[KEY];

我仍在寻找优化方法。
注意:我以编程方式从搜索字符串中删除停用词。

答案 2 :(得分:1)

您是否考虑过使用SQL Server 2012中的语义索引功能?

它们建立在全文索引之上,但扩展它们以包含有关字频的详细信息。我最近用它们构建了一个词云,它非常好。

在互联网上可以找到一些好的文章,你也可以在文档中搜索彼此“接近”的单词。我在2个nvarchar列中设置了全文索引,然后启用了sematic索引。

这些链接可以帮助您入门,但我认为它可以满足您的需求。

Setting up Sematic indexes

Some good info

答案 3 :(得分:0)

sys.dm_fts_parser

注意:要运行此功能需要服务器管理员权限,因此它可能不适用于生产环境。

我已经了解了全文搜索如何查找sys.dm_fts_parser调试查询的功能。它将向您展示全文搜索查询功能将如何查看您的输入。它将搜索项逐字拆分(根据指定的停止列表),然后为每个单词分配信息,例如查询中的位置,单词是否在停止列表中等等。

所以,我想我有个主意。如果我使用这个调试功能来做我以前做的事情怎么办,但是这次忽略了停止列表上的文字呢?

看看这个完美无缺的查询,但看起来像是一个绝对的噩梦:

DECLARE @input AS varchar(1000) = 'kirk for cameron',
   @query AS varchar(1000),
   @inputQuery AS varchar(1000),
   @tokens AS TABLE (display_term varchar(255))

INSERT INTO @tokens
SELECT display_term
FROM sys.dm_fts_parser('"' + @input + '"', 1033, 0, 0) -- double quotes are required around the input search term
WHERE special_term = 'Exact Match' -- filters out stopwords

SET @query = STUFF(
(SELECT ' AND ' + display_term
FROM @tokens
FOR XML PATH ('')), 1, 5, '')

SET @inputQuery = '"' + @input + '"'

PRINT @query -- 'kirk AND cameron'
PRINT @inputQuery -- '"kirk for cameron"'

SELECT * FROM tblName WHERE CONTAINS(Name, @inputQuery)
UNION
SELECT * FROM tblName WHERE CONTAINS(Name, @query)

这是它正在做什么

  • 要求全文搜索解析器返回查询中其索引中的任何单词,并忽略它不会索引的单词
  • 使用tricky implementation of FOR XML PATH
  • 在所有优秀搜索字词之间插入布尔关键字AND
  • 在原始输入短语周围加上双引号,以查询确切的短语
  • PRINT变量,以便我可以在调试时检查它们的值
  • 使用我构建的两个查询运行CONTAINS搜索,包括结果集顶部的完全匹配

结果

返回两个结果!用户可能会因其他结果而暂时感到困惑,但请将其输入Google,我敢打赌它会以相同的方式返回结果。

PKID    Name
-----   -----
4       Kirk For Cameron
3       Kirk Cameron