Home > Software engineering >  How to build a query with variable number of match() clauses using the ElasticSearch Java API
How to build a query with variable number of match() clauses using the ElasticSearch Java API

Time:01-09

I want to retrieve documents based on a combination of 3 field values:

  • canonicalForm
  • grammar
  • meaning

Here is how I do it now.

String canonicalForm = "tut";
String grammar = "verb";
String meaning = "to land";

BoolQuery bool = BoolQuery.of(q -> q
            .must(m -> m
                .match(mt -> mt
                    .field("descr.canonicalForm")
                        .query(canonicalForm)
                    )
            )
            .must(m -> m
                .match(mt -> mt
                    .field("descr.grammar")
                        .query(grammar)
                    )
            )
            .must(m -> m
                .match(mt -> mt
                    .field("descr.meaning")
                        .query(meaning)
                    )
            )
        );

This works as long as I provide a value for all three fields. But sometimes I want to search using only 1 or two of the fields.

I tried setting the "absent" field values to null, but that raises an exception.

I also tried setting the "absent" value to the empty string but that always returns 0 hits.

Another solution would be to only add a match() clause for a field if the provided value is not null, but I can't figure out how to insert this kind of conditionals in the fluent DSL builder pattern.

CodePudding user response:

I believe that you must use the should clausule.

 {
  "query": {
    "bool": {
      "minimum_should_match": 1, 
      "should": [
        {
          "bool": {
            "must": [
              {
                "exists": {
                  "field": "descr.grammar"
                }
              },
              {
                "bool": {
                  "must_not": [
                    {
                      "exists": {
                        "field": "descr.canonicalForm"
                      }
                    }
                  ]
                }
              },
              {
                "bool": {
                  "must_not": [
                    {
                      "exists": {
                        "field": "descr.meaning"
                      }
                    }
                  ]
                }
              },
              {
                "match": {
                  "descr.grammar": "verb"
                }
              }
            ]
          }
        },
        {
          "bool": {
            "must": [
              {
                "exists": {
                  "field": "descr.grammar"
                }
              },
              {
                "exists": {
                  "field": "descr.canonicalForm"
                }
              },
              {
                "bool": {
                  "must_not": [
                    {
                      "exists": {
                        "field": "descr.meaning"
                      }
                    }
                  ]
                }
              },
              {
                "match": {
                  "descr.grammar": "verb"
                }
              },
              {
                "match": {
                  "descr.canonicalForm": "tut"
                }
              }
            ]
          }
        },
        {
          "bool": {
            "must": [
              {
                "exists": {
                  "field": "descr.grammar"
                }
              },
              {
                "exists": {
                  "field": "descr.canonicalForm"
                }
              },
              {
                "exists": {
                  "field": "descr.meaning"
                }
              },
              {
                "match": {
                  "descr.grammar": "verb"
                }
              },
              {
                "match": {
                  "descr.canonicalForm": "tut"
                }
              },
              {
                "match": {
                  "descr.meaning": "meaning"
                }
              }
            ]
          }
        }
      ]
    }
  }
}

CodePudding user response:

I found a solution, but it's awkward as hell. If someone has a more fluent solution to suggest, please let me know.

The solution I came up with is to:

  • Create the query as a JSONObject
  • Transform that JSONObject to an InputStream
  • Feed that InputStream to .query(q -> q.withJson()) method

Here is an example below:

        // Say we have these input field values
        String canonicalForm = "tut";
        String grammar = "verb";
        String meaning = null; // This means we don't want to query on field 'meaning'

        // Build a JSONArray that will contain the "match" criteria for the non-null
        // input field values
        //
        JSONArray mustArr = new JSONArray();
        if (canonicalForm != null) {
            mustArr.put(new JSONObject()
                .put("match", new JSONObject()
                    .put("descr.canonicalForm", new JSONObject()
                        .put("query", canonicalForm)
                    )
                )
            );
        }
        if (grammar != null) {
            mustArr.put(new JSONObject()
                .put("match", new JSONObject()
                    .put("descr.grammar", new JSONObject()
                        .put("query", grammar)
                    )
                )
            );
        }
        if (meaning != null) {
            mustArr.put(new JSONObject()
                .put("match", new JSONObject()
                    .put("descr.meaning", new JSONObject()
                        .put("query", meaning)
                    )
                )
            );
        }

        // Build a "bool" query object, feeding it the "must" array.
        JSONObject queryJObj = new JSONObject()
            .put("bool", new JSONObject()
                .put("must", mustArr)
            )
        ;

        // Convert the "query" object to an InputStream
        String queryJsonStr = queryJObj.toString();
        InputStream queryIS = new ByteArrayInputStream(queryJsonStr.getBytes(StandardCharsets.UTF_8));

        SearchRequest sr = SearchRequest.of(s -> s
            .index("morphemes")
            .query(q -> q
                    .withJson(queryIS)
            )
        );

  • Related