Hi @Beda_Bogdan,
you're right. Range queries solve only one half of the problem, my bad. I can think of two options for your case:
- You have a periodic job that sets the date of the next birthday (which is a separate field from your birthday field!) of each person (e.g. someone is born on 5th October 1950, then your job would set it to 5th October 2017). You can then use a simple range query to solve that.
- You can also use script queries. Note that if you use Elasticsearch 5, Painless is the default scripting language and it does not support a concept of "now" at the moment (as "now" refers to server time, not client time). Hence you need to pass "now" as a parameter from the client.
It basically boils down to a trade-off between storing additional information at index time (solution 1) and slower queries (solution 2).
An example query for solution 1. is:
GET /people/_search
{
"query": {
"range": {
"next_birthday": {
"gte": "now",
"lte": "now+10d/d"
}
}
}
}
This query returns all people with a birthday within the next 10 days.
If you want to solve this with the script queries, consider this example (needs Elasticsearch 5.0 or better):
DELETE /people
PUT /people
{
"mappings": {
"type": {
"properties": {
"birthday": {
"type": "date"
},
"name": {
"type": "keyword"
}
}
}
}
}
POST /people/type
{
"name": "Fred",
"birthday": "1990-01-12"
}
GET /people/_search
{
"query": {
"bool": {
"must": {
"script": {
"script": {
"inline": "def today = LocalDate.parse(params.today); def birthday = Instant.ofEpochMilli(doc['birthday'].value).atZone(ZoneId.systemDefault()).toLocalDate(); def thisYearsBirthday = birthday.withYear(today.get(ChronoField.YEAR)); def nextBirthday; if (thisYearsBirthday.isBefore(today)) { nextBirthday = birthday.withYear(today.get(ChronoField.YEAR) + 1); } else { nextBirthday = thisYearsBirthday;} def inNDays = today.plusDays(params.daysFuture); (nextBirthday.isEqual(today) || nextBirthday.isAfter(today)) && (nextBirthday.isEqual(inNDays) || nextBirthday.isBefore(inNDays))",
"lang": "painless",
"params": {
"today": "2017-01-09",
"daysFuture": 10
}
}
}
}
}
}
}
The script is an inline script so it is not easily readable. So you should define this as a stored script in production:
def birthday = Instant.ofEpochMilli(doc['birthday'].value).atZone(ZoneId.systemDefault()).toLocalDate();
def today = LocalDate.parse(params.today);
def thisYearsBirthday = birthday.withYear(today.get(ChronoField.YEAR));
def nextBirthday;
if (thisYearsBirthday.isBefore(today)) {
nextBirthday = birthday.withYear(today.get(ChronoField.YEAR) + 1);
} else {
nextBirthday = thisYearsBirthday;
}
def inNDays = today.plusDays(params.daysFuture);
return (nextBirthday.isEqual(today) || nextBirthday.isAfter(today))
&& (nextBirthday.isEqual(inNDays) || nextBirthday.isBefore(inNDays));
Obviously the first solution is much faster from a performance perspective, so if you have a lot of data to query I suggest you use solution 1, if you don't want the additional complexity of an update job and can live with the performance, you can use solution 2.
Don't forget to think how you need to handle birthdays on February 29th. This may depend on your business requirements. In solution 2, you also need to think about time-zones (I just used ZoneId.systemDefault()
in my example).
I hope this additional explanation helps you solve your problem.
Daniel