ElasticSearch exact match on text field with script

698 Views Asked by At

I'm trying to search with an ElasticSearch query documents that have exactly a certain value in a text field. I know that with a term query it could be possible if it were a keyword field. Unfortunately, I can't change the mapping.

{
  "my_index": {
    "aliases": {},
    "mappings": {
      "properties": {
        "my_field": {
          "type": "text"
        },
      }
    },
    "settings": {
      "index": {
        "max_ngram_diff": "60",
        "number_of_shards": "8",
        "blocks": {
          "read_only_allow_delete": "false",
          "write": "false"
        },
        "analysis": {...}
      }
    }
  }
}

I tried with this term query, but without results:

{
  "size": 10,
  "index": "my_index",
  "body": {
    "query": {
      "bool": {
        "should": [
          {
            "term": {
              "my_field":"MY_VALUE", //not working
            }
          }
        ],
        "must": [],
        "filter": [],
        "minimum_should_match": 1
      }
    }
  }
}

Since it seems impossible to find that value, I was trying with a script following this guide https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-filter-context.html. So I changed the term to match_phrase and I added the script to filters :


{
    "size": 10,
    "index": "my_index",
    "body": {
        "query": {
            "bool": {
                "should": [{
                    "match_phrase": {
                        "my_field": {
                            "query": "MY_VALUE",
                            "boost": 1.5,
                            "slop": 0
                        }
                    }
                }],
                "must": [],
                "filter": [{
                    "script": {
                        "script": {
                            "source": "doc['my_field'] == 'MY_VALUE'"
                        }
                    }
                }],
                "minimum_should_match": 1
            }
        }
    }
}

The output gives me this error:

body:
{
  "error": {
    "root_cause": [
      {
        "type": "script_exception",
        "reason": "runtime error",
        "script_stack": [
          "org.opensearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:101)",
          "org.opensearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:53)",
          "doc['my_field'] === 'MY_VALUE'",
          "    ^---- HERE"
        ],
        "script": "doc['my_field'] === 'MY_VALUE'",
        "lang": "painless",
        "position": {
          "offset": 4,
          "start": 0,
          "end": 30
        }
      }
    ],
    "type": "search_phase_execution_exception",
    "reason": "all shards failed",
    "phase": "query",
    "grouped": true,
    "failed_shards": [
      {
        "shard": 0,
        "index": "my_index",
        "node": "R99vOHeORlKsk9dnCzcMeA",
        "reason": {
          "type": "script_exception",
          "reason": "runtime error",
          "script_stack": [
            "org.opensearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:101)",
            "org.opensearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:53)",
            "doc['my_field'] === 'MY_VALUE'",
            "    ^---- HERE"
          ],
          "script": "doc['my_field'] === 'MY_VALUE'",
          "lang": "painless",
          "position": {
            "offset": 4,
            "start": 0,
            "end": 30
          },
          "caused_by": {
            "type": "illegal_argument_exception",
            "reason": "No field found for [my_field] in mapping with types []"
          }
        }
      }
    ]
  },
  "status": 400
}

I read online the it seems that doc doesn't contain text fields (same problem of term query). So I tried with params._source, after having read some other links:

"source": "params._source.my_field === 'MY_VALUE'" but whit this error:

"script": "params._source.my_field === 'MY_VALUE'",
          "lang": "painless",
          "position": {
            "offset": 14,
            "start": 0,
            "end": 38
          },
          "caused_by": {
            "type": "null_pointer_exception",
            "reason": null
          }

Which is the best solution? Also without a script...

1

There are 1 best solutions below

0
On

You might find the answer in this similar question useful.

I also started to use ElasticSearch/OpenSearch recently and got the same question as yours. My solution is, you may add a sub-field to the current mapping, or you may call it multi-field as mentioned in ElasticSearch doc

However I found that the result still shows scores like a fuzzy search, but it only contains the records exactly matched.

For your example, you might change the mapping into below:

{
    "my_index": {
      "aliases": {},
      "mappings": {
        "properties": {
          "my_field": {
            "type": "text",
            "fields": {
                "raw": { 
                  "type":  "keyword"
                }
            }
          },
        }
      },
    }
}

And you query of doing exact match would be:

{
  "size": 10,
  "index": "my_index",
  "body": {
    "query": {
      "bool": {
        "should": [
          {
            "term": {
              "my_field.raw":"MY_VALUE", // here
            }
          }
        ],
        "must": [],
        "filter": [],
        "minimum_should_match": 1
      }
    }
  }
}