当前位置: 首页 > 知识库问答 >
问题:

如何明智地结合shingles和edgeNgram来提供灵活的全文搜索?

应俭
2023-03-14

我们有一个OData兼容的API,它将一些全文搜索需求委托给一个Elasticsearch集群。由于OData表达式可能会变得相当复杂,我们决定简单地将它们转换为等效的Lucene查询语法,并将其提供给query_string查询。

我们确实支持一些文本相关的OData过滤器表达式,比如:

  • 以(字段,“bla”)开始
  • EndsWith(字段,“bla”)
  • substringof('bla',field)
  • 名称eq'bla'

我们匹配的字段可以是analyzednot_analyzed或两者(即通过多字段)。所搜索的文本可以是单个令牌(例如)、仅是其一部分(例如标签)、或多个令牌(例如表1.表10等)。搜索必须不区分大小写。

下面是一些我们需要支持的行为示例:

  • startswith(name,'table1')必须与“table 1”、“table 100”、“table 1.5”、“table 112 uper level”相匹配
  • Endswith(name,'table1')必须与“Room 1,Table 1”、“subtable 1”、“Table 1”、“Jeff Table 1”相匹配
  • substringof(“Table 1”,name)必须与“big Table 1 back”、“Table 1”、“Table 1”、“small table12”相匹配
  • 名称eq“table 1”必须与“table 1”、“table 1”、“table 1”匹配

因此,基本上,我们接受用户输入(即传入的第2个参数startswith/endswith的内容,即substringof的第1个参数,即eq的右侧值),并尝试将其完全匹配,无论令牌完全匹配还是仅部分匹配。

现在,我们正在使用一个笨拙的解决方案,下面重点介绍了这个方案,它工作得很好,但远不是理想的方案。

在我们的query_string中,我们使用正则表达式语法与not_analyth字段进行匹配。由于字段是not_analyth并且搜索必须不区分大小写,因此我们在准备正则表达式以提供给查询的同时进行自己的标记化,以便得出类似的结果,即这相当于OData筛选器endswith(name,'table8')(=>匹配name以“table 8”结尾的所有文档)

  "query": {
    "query_string": {
      "query": "name.raw:/.*(T|t)(A|a)(B|b)(L|l)(E|e) 8/",
      "lowercase_expanded_terms": false,
      "analyze_wildcard": true
    }
  }

因此,尽管这个解决方案工作得很好,性能也不是太差(这是令人惊讶的),但我们希望采用不同的方法,充分利用分析器的能力,以便将所有的负担转移到索引时间而不是搜索时间。但是,由于重新索引我们所有的数据将花费数周的时间,我们想首先调查是否有一个令牌过滤器和分析器的良好组合,以帮助我们实现上面列举的相同搜索需求。

我的想法是,理想的解决方案将包含一些Whingle(即几个令牌在一起)和edge-nGram(即在令牌的开始或结束处匹配)的明智混合。但是,我不确定的是,是否可以使它们协同工作以匹配几个令牌,其中一个令牌可能没有完全由用户输入)。例如,如果索引的名称字段是“Big Table 123”,我需要substringof('table1',name)来匹配它,因此“Table”是完全匹配的令牌,而“1”只是下一个令牌的前缀。

提前感谢分享你们的想法。

更新1:测试Andrei的解决方案后

=>精确匹配(eq)和startswith完全工作。

A.端带故障

搜索substringof('table112',name)会产生107个文档。搜索更具体的情况,如endswith(name,'table112')会生成1525个文档,而它应该生成更少的文档(后缀匹配应该是子字符串匹配的子集)。在更深入的检查中,我发现了一些不匹配的地方,例如“Social Club,Table 12”(不包含“112”)或“Order 312”(既不包含“Table”也不包含“112”)。我猜是因为它们以“12”结尾,而这是令牌“112”的有效克数,因此匹配。

B.substringof故障

搜索substringof('table',name)与“Party Table”、“Alex on big Table”匹配,但与“Table 1”、“Table 112”等不匹配。搜索substringof('tabl',name)与任何内容都不匹配

更新2

这是一种暗示,但我忘记了明确地提到解决方案必须使用query_string查询,这主要是因为OData表达式(无论它们多么复杂)将不断被翻译成它们的Lucene等价物。我知道我们正在用Lucene的查询语法来权衡Elasticsearch查询DSL的功能,后者的功能稍差,表达能力也稍差,但这是我们无法真正改变的。不过,我们已经很接近了!

更新3(2019年6月25日):

ES7.2引入了一种新的数据类型,称为search_as_you_type,它在本地允许这种行为。欲了解更多信息,请访问:https://www.elastic.co/guide/en/elasticsearch/reference/7.2/search-as-you-type.html

共有1个答案

相德宇
2023-03-14

这是一个有趣的用例。以下是我的看法:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_ngram_analyzer": {
          "tokenizer": "my_ngram_tokenizer",
          "filter": ["lowercase"]
        },
        "my_edge_ngram_analyzer": {
          "tokenizer": "my_edge_ngram_tokenizer",
          "filter": ["lowercase"]
        },
        "my_reverse_edge_ngram_analyzer": {
          "tokenizer": "keyword",
          "filter" : ["lowercase","reverse","substring","reverse"]
        },
        "lowercase_keyword": {
          "type": "custom",
          "filter": ["lowercase"],
          "tokenizer": "keyword"
        }
      },
      "tokenizer": {
        "my_ngram_tokenizer": {
          "type": "nGram",
          "min_gram": "2",
          "max_gram": "25"
        },
        "my_edge_ngram_tokenizer": {
          "type": "edgeNGram",
          "min_gram": "2",
          "max_gram": "25"
        }
      },
      "filter": {
        "substring": {
          "type": "edgeNGram",
          "min_gram": 2,
          "max_gram": 25
        }
      }
    }
  },
  "mappings": {
    "test_type": {
      "properties": {
        "text": {
          "type": "string",
          "analyzer": "my_ngram_analyzer",
          "fields": {
            "starts_with": {
              "type": "string",
              "analyzer": "my_edge_ngram_analyzer"
            },
            "ends_with": {
              "type": "string",
              "analyzer": "my_reverse_edge_ngram_analyzer"
            },
            "exact_case_insensitive_match": {
              "type": "string",
              "analyzer": "lowercase_keyword"
            }
          }
        }
      }
    }
  }
}
  • my_ngram_analyzer用于将每个文本分割成小块,小块的大小取决于您的用例。出于测试目的,我选择了25个字符。使用lowercase是因为您说了不区分大小写。基本上,这是用于substringof('table1',name)的标记器。查询很简单:
{
  "query": {
    "term": {
      "text": {
        "value": "table 1"
      }
    }
  }
}
  • my_edge_ngram_analyzer用于从开头开始拆分文本,这特别用于startswith(name,'table1')用例。同样,查询很简单:
{
  "query": {
    "term": {
      "text.starts_with": {
        "value": "table 1"
      }
    }
  }
}
  • 我发现这是最棘手的部分-EndsWith(name,'table1')的部分。为此,我定义了my_reverse_edge_ngram_analyzer,它使用了关键字标记器,以及小写edgengram筛选器,前面和后面是反向筛选器。这个标记器的主要作用是拆分edgengram中的文本,但边缘是文本的结尾,而不是开头(就像常规的edgengram)。查询:
{
  "query": {
    "term": {
      "text.ends_with": {
        "value": "table 1"
      }
    }
  }
}
  • 对于名称eq“table 1”大小写,一个简单的关键字标记器和一个小写筛选器可以执行以下查询:
{
  "query": {
    "term": {
      "text.exact_case_insensitive_match": {
        "value": "table 1"
      }
    }
  }
}

关于query_string,这会稍微改变解决方案,因为我希望term不分析输入文本,并将其与索引中的一个术语精确匹配。

但如果为其指定了适当的分析器,则可以使用query_string“模拟”这种情况。

解决方案是如下所示的一组查询(始终使用该分析器,只更改字段名):

{
  "query": {
    "query_string": {
      "query": "text.starts_with:(\"table 1\")",
      "analyzer": "lowercase_keyword"
    }
  }
}
 类似资料:
  • 问题内容: 我们有一个与OData兼容的API,它将某些全文搜索需求委托给Elasticsearch集群。由于OData表达式可能变得非常复杂,因此我们决定将它们简单地转换为等效的Lucene查询语法,并将其输入查询中。 我们确实支持一些与文本相关的OData过滤器表达式,例如: 我们要匹配的字段可以是,也可以是两者(即通过多字段)。所搜索的文本可以是一个单一的令牌(例如),仅其一部分(例如),或

  • 我想使用Impex从表中删除一些项目。下面的示例不会抛出错误,但不会删除任何内容。 查询产生预期的结果。是REMOVE与灵活的搜索不兼容,还是我遗漏了什么? 问题是,我正在hotfolder上运行导入,我想事先删除所有现有项目。我们欢迎其他解决方案。

  • 我目前正在使用Spring security建立一个反向代理安全域,其想法是默认情况下要求所有请求上都有承载令牌,除了一些例外情况,如注册等。当前我的配置功能如下所示: Ant Matcher非常有用,但您必须单独传递所有URL。有没有办法让我传入一个字符串数组,这样我就可以将配置分开?

  • 我对灵活的查询有问题。这是我的疑问: 这是我执行时的错误: 有人能帮我吗?谢谢。

  • 如何获得所有A、B、C、D的pk 注意:使用字符串值C,D,我想使用灵活的搜索查询获取产品C,D以及A,B的pk 细节: 我有产品的清单。 在每个产品中都有一个名为“X”的属性,其中包含string类型的产品ID代码。 注意:“产品ID代码”是指产品列表中另一个产品的“产品ID”。 现在我想根据产品ID代码获得产品的pk?

  • 我为Jetty 9和Kafka使用ssl密钥库。我需要提供密钥库和密钥密码来访问密钥库和私钥。但是,我不想在配置文件中以明文形式提供这些密码。还有哪些其他选项可以安全地提供/加密密码?每种方法的优缺点是什么?