【问题标题】:How do you use T-SQL Full-Text Search to get results like Google?您如何使用 T-SQL 全文搜索来获得类似 Google 的结果?
【发布时间】:2014-07-12 00:50:07
【问题描述】:

我需要什么

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

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

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

我看到FREETEXT function 可以将整个短语分解成“有用”的单词(忽略“and”、“or”、“the”等单词),然后返回匹配行的列表非常快,即使是复杂的搜索词。但是当您尝试使用它时,您可能会注意到,它似乎只搜索OR,而不是搜索每个术语的AND。也许有办法改变它的行为,但我没有发现任何有用的东西。

然后是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')

返回“詹姆斯柯克”和“詹姆斯卡梅隆”。好吧,让我们缩小范围...

查询 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”、“for”和“cameron”的索引结果的交集,而“for”这个词将没有任何结果(它是一个停用词和所有),那么与该结果的任何交集的结果也是空的。它是否真的像那样起作用与我无关,因为这是 CONTAINS 函数每次我用停用词进行布尔搜索时的可观察行为。

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

来了NEAR

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

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

不。没有结果。删除单词“for”,您将再次获得所有三个结果。 :(

现在怎么办?

【问题讨论】:

标签: sql sql-server


【解决方案1】:

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

这是类似于 Google 的全文搜索的source code for the latest version

【讨论】:

【解决方案2】:

您是否考虑过在 SQL Server 2012 中使用语义索引函数?

它们建立在全文索引的基础上,但对其进行了扩展以包含有关词频的详细信息。我最近用它们构建了一个词云,真的很棒。

互联网上有一些不错的文章,您还可以在文档中搜索彼此“接近”的单词。我在 2 个 nvarchar 列中设置了全文索引,然后启用了语义索引。

这些链接可以帮助您入门,但我认为它会为您提供所需的内容。

Setting up Sematic indexes

Some good info

【讨论】:

  • 我要搞砸这个,看看它是否符合我的要求。谢谢你的信息!
  • 好看。我也将进行更深入的了解。享受:-)
【解决方案3】:

我将 ISAbout 与 THESAURUSINFLECTIONAL通配符
结合使用 优点是
1 - 搜索字符串中的单词顺序无关紧要
2 - 搜索相似词 (THESAURUS)
3 - 将跑步、跑步、跑步、跑步视为相同(影响
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];

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

【讨论】:

    【解决方案4】:

    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)
    

    这就是它的作用

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

    结果

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

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

    【讨论】:

    • 有更好的方法吗?这似乎是一种非常 hacky 的方法。
    猜你喜欢
    • 1970-01-01
    • 2011-02-21
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多