Search code examples
c#elasticsearchnest

Matching multiple phrases to one field in Elastic Search


I have built an application in C# which searches my Elasticsearch documents and this all works fine using this code....

List<AuditLog> resultsList = Client.SearchAsync<AuditLog>(s => s
    .From(0)
    .Take(noRows)
    .Query(q => q
        .Bool(b => b
            .Must(mu => mu.MatchPhrase(mp => mp.Field("audit_Event").Query(auditEvents)),
                mu => mu.Match(ma => ma.Field(field).Query(value))
             )
          )
     )).Result.Documents.ToList();

This all works fine but there is a new requirement to search the audit_Event field against multiple phrases, such as "User Login", "Add Employee" etc

I have found reference to multi_match but this appears to be more related to searching across multiple fields rather than multiple values in one field.

Following discussions below, TermQuery would seem to do the job but doesn't return any values. Any ideas?

QueryContainer queryAnd = new TermQuery() { Field = "audit_Application", Value = "MyApplication" };
QueryContainer queryOr = new TermQuery() { Field = "audit_Event", Value = "Employee Inserted" };
queryOr |= new TermQuery() { Field = "audit_Event", Value = "Employee Updated" }; 
QueryContainer queryMain = queryAnd & queryOr;

resultsList = Client.SearchAsync<AuditLog>(s => s
    .From(0)
    .Take(noRows)
    .Query(q => q
        .Bool(b => b
            .Must(queryMain)
        )
    )).Result.Documents.ToList();

I've also checked the two queries using Kibana, the first one returns data but the second one doesn't, and am now wondering if it a problem with how the data is indexed....

GET auditlog/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "audit_Event": "Employee Updated"
          }
        },
        {
          "match_phrase": {
            "audit_Event": "Employee Inserted"
          }
        }
      ]
    }
  }
}

GET auditlog/_search
{
  "query" : {
    "terms": {
        "audit_Event": ["Employee Updated","Employee Inserted"]
    }
  }
}

Solution

  • Based on your statement that the OR query works as expected, below is a way you can build the query using NEST. You may make any specific modifications as needed.

    This snippet of code is responsible for triggering the search call

    var searchDescriptor = new SearchDescriptor<AuditLog>()
        .Query(q => Blah(auditEventsList))
        .From(0)
        .Take(noRows);
        // other methods you want to chain here
    
    var response = Client.searchAsync<AuditLog>(searchDescriptor);
    

    The search query is generated here. Notice the use of SHOULD and the call to another method that is responsible for dynamically generating the match_phrase queries.

    private static QueryContainer Blah(List<string> auditEventsList)
    {
        return new QueryContainerDescriptor<AuditLog>().Bool(
            b => b.Should(
                InnerBlah(auditEventsList)
            )
        );
    }
    

    This method generates an array of match_phrase queries that are passed to should in the above snippet. These are generated by iterating over all the audit events that are received.

    private static QueryContainer[] InnerBlah(List<string> auditEventsList)
    {
        QueryContainer orQuery = null;
        List<QueryContainer> queryContainerList = new List<QueryContainer>();
        foreach(var item in auditEventsList)
        {
            orQuery = new MatchPhraseQuery { Field = "audit_Event", Query = item };
            queryContainerList.Add(orQuery);
        }
        return queryContainerList.ToArray();
    }