Я создал небольшой пример, чтобы продемонстрировать конкретную проблему, с которой я столкнулся. Вкратце, когда я создаю сопоставление с несколькими полями, используя тип поля «Текст» и анализатор ключевых слов, никакие документы не возвращаются из поискового запроса Elasticsearch Regexp, содержащего знаки препинания. Я использую тире в следующем примере, чтобы продемонстрировать проблему.
Я использую Elasticsearch 7.10.2. Индекс, на который я ориентируюсь, уже заполнен миллионами документов. Поле типа Text, где мне нужно запустить некоторые регулярные выражения, использует анализатор Standard (по умолчанию). Я так понимаю, поскольку анализатор Standard токенизирует поле, следующий запрос:
POST _analyze
{
"analyzer" : "default",
"text" : "The number is: 123-4576891-73.\n\n"
}
даст три слова: the, number, is и три группы чисел: 123, 4567891, 73. Очевидно, что регулярное выражение, основанное на знаках препинания, такое как это, которое содержит два буквальных дефиса:
"(.*[^a-z0-9_])?[0-9]{3}-[0-9]{7}-[0-9]{2}([^a-z0-9_].*)?"
не вернет результат. Обратите внимание: для тех, кто не знаком с этим, ярлыки регулярных выражений не работают для запросов Elasticsearch на основе Lucene (по крайней мере, пока). Вот ссылка: https://www.elastic.co/guide/en/elasticsearch/reference/current/regexp-syntax.html. Кроме того, использование границ слов, которые я показываю в своих примерах (.*[^a-z0-9_])?
и ([^a-z0-9_].*)?
, взято из этого сообщения: Word граница в регулярном выражении Lucene.
Чтобы убедиться в этом на примере, создайте и заполните такой индекс:
PUT /index-01
{
"settings": {
"number_of_shards": 1
},
"mappings": {
"properties": {
"text": { "type": "text" }
}
}
}
POST index-01/_doc/
{
"text": "The number is: 123-4576891-73.\n\n"
}
Следующий поисковый запрос Regexp ничего не вернет из-за проблемы с токенизацией, описанной ранее:
POST index-01/_search
{
"size": 1,
"query": {
"regexp": {
"text": {
"value": "(.*[^a-z0-9_])?[0-9]{3}-[0-9]{7}-[0-9]{2}([^a-z0-9_].*)?",
"flags": "ALL",
"case_insensitive": true,
"max_determinized_states": 100000
}
}
},
"_source": false,
"highlight": {
"fields": {
"text": {}
}
}
}
Большинство сообщений предполагают, что быстрое исправление будет состоять в том, чтобы настроить таргетинг на несколько полей типа ключевого слова вместо текстового поля. Поле с несколькими типами ключевых слов создается автоматически, как показано ниже:
GET index-01/_mapping/field/text
отклик:
{
"index-01" : {
"mappings" : {
"text" : {
"full_name" : "text",
"mapping" : {
"text" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}
Ориентируясь на поле ключевого слова, я получаю результаты для следующего поискового запроса Regexp:
POST index-01/_search
{
"size": 1,
"query": {
"regexp": {
"text.keyword": {
"value": "(.*[^a-z0-9_])?[0-9]{3}-[0-9]{7}-[0-9]{2}([^a-z0-9_].*)?",
"flags": "ALL",
"case_insensitive": true,
"max_determinized_states": 100000
}
}
},
"_source": false,
"highlight": {
"fields": {
"text.keyword": {}
}
}
}
вот часть результата, выделенная хитом:
...
"highlight" : {
"text.keyword" : [
"<em>This is my number 123-4576891-73. Thanks\n\n</em>"
]
}
...
Поскольку некоторые документы содержат большое количество текста, я отрегулировал размер поля text.keyword
параметром ignore_above
:
PUT /index-01/_mapping
{
"properties": {
"text": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 32766
}
}
}
}
}
Однако при этом будут пропущены некоторые документы, поскольку целевой индекс содержит текстовые поля большего размера, чем верхняя граница для ключевого слова типа поля. Кроме того, согласно документации Elasticsearch здесь: https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html этот тип поля действительно предназначен для структурированных данных, постоянных значений и запросов с подстановочными знаками.
Следуя этому руководству, я назначил анализатору ключевых слов новый тип поля Text (text.raw), внеся следующее обновление в сопоставление:
PUT /index-01/_mapping
{
"properties": {
"text": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 32766
},
"raw": {
"type": "text",
"analyzer": "keyword",
"index": true
}
}
}
}
}
Теперь вы можете увидеть дополнительное сопоставление text.raw
с этим запросом:
GET index-01/_mapping/field/text
отклик:
{
"index-01" : {
"mappings" : {
"text" : {
"full_name" : "text",
"mapping" : {
"text" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 32766
},
"raw" : {
"type" : "text",
"analyzer" : "keyword"
}
}
}
}
}
}
}
}
Затем я проверил, что данные на самом деле были сопоставлены с несколькими полями:
POST index-01/_search
{
"query":
{
"match_all": {}
},
"fields": ["text", "text.keyword", "text.raw"]
}
отклик:
...
"hits" : [
{
"_index" : "index-01",
"_type" : "_doc",
"_id" : "2R-OgncBn-TNB4PjXYAh",
"_score" : 1.0,
"_source" : {
"text" : "The number is: 123-4576891-73.\n\n"
},
"fields" : {
"text" : [
"The number is: 123-4576891-73.\n\n"
],
"text.keyword" : [
"The number is: 123-4576891-73.\n\n"
],
"text.raw" : [
"The number is: 123-4576891-73.\n\n"
]
}
}
]
...
Я также проверил, что анализатор ключевых слов, примененный к полю text.raw
, содержит один токен, как показано в следующем запросе:
POST _analyze
{
"analyzer" : "keyword",
"text" : "The number is: 123-4576891-73.\n\n"
}
отклик:
{
"tokens" : [
{
"token" : "The number is: 123-4576891-73.\n\n",
"start_offset" : 0,
"end_offset" : 32,
"type" : "word",
"position" : 0
}
]
}
Однако точно такой же поисковый запрос Regexp, нацеленный на поле text.raw
, ничего не возвращает:
POST index-01/_search
{
"size": 1,
"query": {
"bool": {
"must": [
{
"regexp": {
"text.raw": {
"value": "(.*[^a-z0-9_])?[0-9]{3}-[0-9]{7}-[0-9]{2}([^a-z0-9_].*)?",
"flags": "ALL",
"case_insensitive": true,
"max_determinized_states": 100000
}
}
}
]
}
},
"_source": false,
"highlight" : {
"fields" : {
"text.raw": {}
}
}
}
Пожалуйста, дайте мне знать, если вы знаете, почему я не получаю результат, используя тип поля Текст с анализатором ключевых слов.