如何使用正则表达式在 Elasticsearch 中搜索包含以空格分隔的段的字段

How to search with regex for fields containing whitespace-separated segments in Elasticsearch

我有一个文档,其中包含一个名为 info_list 的字段,它基本上是一个由 space 分隔的 9 段的字符串。

字段的映射是

"info_list": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }

源文件看起来像

"_source": {
      "id": "1234",
      "date": "1614556800000",
      "info_list": [
        "1234 2D 5678 8765 5678 1111 2222 3333 1"
      ]
    }

信息列表基本上由9个部分组成。出于疑问,我们可以说 a,b,c,d,e,f,g,h,i 是那 9 个片段。

info_list = a+ ' ' + b+ ' ' + c +' ' + d+ ' ' e + ' ' + f + ' ' + g + ' ' + h + ' ' + i

现在假设我想搜索值为 5678 的 c,当前实现使用 match_phrase 查询类似这样的东西

GET test/_search
{
  "query": {
    "match_phrase": {
      "info_list": "5678"
    }
  }
}

上述方法的问题在于,即使我希望搜索结果具有 c = 5678,现在如果 info_list 字符串中的任何段具有 5678,它将匹配它,从而导致错误的搜索结果。

我尝试使用类似

的正则表达式查询
GET /test/_search
{
  "query" : {
    "query_string" : 
    { "fields" : ["info_list"],
      "query" : ".* .* 5678 .*"
      
    }
    
  }
}

但这似乎并没有 work.Should 我更改字段的映射?任何帮助或建议将不胜感激,因为我是 Elastic 搜索的新手。

修复正则表达式

您必须让正则表达式引擎知道确切会有多少 前面的组。最重要的是,您需要使用默认情况下应用于 text 字段的 .keyword field because the standard analyzer 会将原始字符串拆分为空格并将每个标记转换为小写 - 这两种情况都应避免如果您打算与捕获组一起工作。

话虽如此,这是一个有效的 regexp query:

GET /test/_search
{
  "query": {
    "regexp": {
      "info_list.keyword": "( ?[a-zA-Z0-9]+){2} 5678 .*"
    }
  }
}

提取组 before 摄取

Should I change the mapping of the field?

我会说去吧。当您知道您将针对哪个组时,理想情况下,您应该在提取文档之前提取组

你看,对于你的情况我会做的如下:

  • 保留原来的info_list作为关键字以保持一致性
  • 用您选择的编程语言提取组,用键 ai 对它们进行注释(类似于您自然想到所述组的方式)。
  • 将它们存储在 nested field in order to guarantee that the connections between the keys and the values aren't lost due to array flattening.

具体来说:

  1. 设置映射
PUT extracted-groups-index
{
  "mappings": {
    "properties": {
      "info_list": {
        "type": "keyword"
      }, 
      "info_list_groups": {
        "type": "nested",
        "properties": {
          "group_key": {
            "type": "keyword"
          },
          "value": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
  1. 摄取文档
POST extracted-groups-index/_doc
{
  "info_list": "1234 2D 5678 8765 5678 1111 2222 3333 1",
  "info_list_groups": [
    {
      "group_key": "a",
      "value": "1234"
    },
    {
      "group_key": "b",
      "value": "2D"
    },
    {
      "group_key": "c",
      "value": "5678"
    },
    { ... } // omitted for brevity
  ]
}
  1. 利用一对nested term queries:
POST extracted-groups-index/_search
{
  "query": {
    "nested": {
      "path": "info_list_groups",
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "info_list_groups.group_key": "c"
              }
            },
            {
              "term": {
                "info_list_groups.value": "5678"
              }
            }
          ]
        }
      }
    }
  }
}

充分利用 Elasticsearch

nested 方法的缺点是它会增加您的索引大小。另外,查询往往会变得非常冗长和混乱。如果您不想走那条路,您可以利用所谓的 自定义分析器 .

此类分析器通常由以下部分组成:

  • a tokenizer(接收字符流并输出标记流——通常是单词)
  • 和一些 token filters 的作用是将令牌塑造成所需的形式。

具体来说,这里的目的是:

  1. 把字符串1234 2D 5678 8765 5678 1111 2222 3333 1作为一个整体

  2. 找到由空格分隔的各个组

    --> (1234) (2D) (5678) (8765) (5678) (1111) (2222) (3333) (1)

  3. 用其字母索引注释每个组

    --> a:1234 b:2D c:5678 d:8765 e:5678 f:1111 g:2222 h:3333 i:1

  4. 最后用空格拆分结果字符串,以便使用 a:1234c:5678

    等查询

所有这些都可以通过“noop”的组合来实现 keyword tokenizer, and pattern_replace + pattern_capture filters:

PUT power-of-patterns
{
  "mappings": {
    "properties": {
      "info_list": {
        "type": "text",
        "fields": {
          "annotated_groups": {
            "type": "text",
            "analyzer": "info_list_analyzer"
          }
        }
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "info_list_analyzer": {
          "type": "custom",
          "tokenizer": "keyword",
          "filter": ["pattern_grouper", "pattern_splitter"]
        }
      },
      "filter": {
        "pattern_grouper": {
          "type": "pattern_replace",
          "pattern": "((?<a>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<b>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<c>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<d>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<e>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<f>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<g>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<h>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<i>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))",
          "replacement": "a:${a}b:${b}c:${c}d:${d}e:${e}f:${f}g:${g}h:${h}i:${i}"
        },
        "pattern_splitter": {
          "type" : "pattern_capture",
           "preserve_original" : true,
           "patterns" : [
              "([a-i]\:[a-zA-Z0-9]+)"
           ]
        }
      }
    }
  }
}

注意friendly-looking regex from above is nothing more than a repetitive named group捕手

设置映射后,您可以摄取文档:

POST power-of-patterns/_doc
{
  "info_list": [
    "1234 2D 5678 8765 5678 1111 2222 3333 1"
  ]
}

然后以一种易于阅读的形式搜索所需的片段:

POST power-of-patterns/_search
{
  "query": {
    "term": {
      "info_list.annotated_groups": "c:5678"
    }
  }
}

将您的查询更改为:

"query" : "([^ ]+ ){2}5678( [^ ]){6}"

英语是“two terms-with-a-space,然后是你的搜索词,然后是 6 spaces-with-terms”