I want to perform an Elasticsearch query which combinates two subqueries (AND operator), each one of those subqueries searching in different fields (OR operator).
For example, if I pass the "name" parameter it searches only in name fields (firstname + lastname), if I pass the "contact" parameter it searches in contact fields (ContactEmail + ContactTelephone).
The code below return :
searchQuery = searchQuery
.MinScore(minScore)
.Query(qu => qu
.Bool(b => b
.Must(m => m
.MultiMatch(mm=> mm
.Fields(fs=> fs
.Field(f => f.Firstname)
.Field(f => f.Lastname)
)
.Query(name)
.Operator(Operator.Or)
)
)
.Must(m => m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.ContactEmail)
.Field(f => f.ContactTelephone)
)
.Query(contact)
.Operator(Operator.Or)
)
)
)
);
I am using Must
because I want the associated score.
I think there are 2 issues: applying AND instead of OR and ignoring subquery if criteria is empty. Any idea?
The two queries in must clauses must be part of the same .Must()
call.
Given the following POCO
public class Person
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public string ContactEmail {get;set;}
public string ContactTelephone {get;set;}
}
The query should look like the following
var client = new ElasticClient(settings);
var minScore = 2;
string name = "name";
string contact = "contact";
var response = client.Search<Person>(s => s
.MinScore(minScore)
.Query(qu => qu
.Bool(b => b
.Must(m => m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.Firstname)
.Field(f => f.Lastname)
)
.Query(name)
.Operator(Operator.Or)
), m => m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.ContactEmail)
.Field(f => f.ContactTelephone)
)
.Query(contact)
.Operator(Operator.Or)
)
)
)
)
);
which produces the following
{
"min_score": 2.0,
"query": {
"bool": {
"must": [
{
"multi_match": {
"fields": [
"contactEmail",
"contactTelephone"
],
"operator": "or",
"query": "contact"
}
}
]
}
}
}
If either name
or contact
are null
, that query will be omitted. For example, setting name
to null
string name = null;
var response = client.Search<Person>(s => s
.MinScore(minScore)
.Query(qu => qu
.Bool(b => b
.Must(m => m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.Firstname)
.Field(f => f.Lastname)
)
.Query(name)
.Operator(Operator.Or)
), m => m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.ContactEmail)
.Field(f => f.ContactTelephone)
)
.Query(contact)
.Operator(Operator.Or)
)
)
)
)
);
yields
{
"min_score": 2.0,
"query": {
"bool": {
"must": [
{
"multi_match": {
"fields": [
"contactEmail",
"contactTelephone"
],
"operator": "or",
"query": "contact"
}
}
]
}
}
}
This takes advantage of a client feature called conditionless queries - a specific query is considered to be conditionless if a component of the query is empty when it should not be, in order to form a complete query. For example, for a multi_match
query, it is considered to be conditionless if the Query
is null
The intention with conditionless queries is that it makes writing more complex queries easier, and the behaviour can be bypassed by specifying Verbatim()
on the query. Since they go against the principle of least surprise however, there is an intention to remove them in the future. To perform the same query without relying on conditionless behaviour
var response = client.Search<Person>(s => s
.MinScore(minScore)
.Query(qu => qu
.Bool(b => b
.Must(m =>
{
if (name == null)
return m;
return m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.Firstname)
.Field(f => f.Lastname)
)
.Query(name)
.Operator(Operator.Or)
);
}, m =>
{
if (contact == null)
return m;
return m
.MultiMatch(mm => mm
.Fields(fs => fs
.Field(f => f.ContactEmail)
.Field(f => f.ContactTelephone)
)
.Query(contact)
.Operator(Operator.Or)
);
}
)
)
)
);