Finally, if none of the function_score
's built-in functions suffice, you can
implement the logic that you need with a script, using the script_score
function.
For an example, let’s say that we want to factor our profit margin into the relevance calculation. In our business, the profit margin depends on three factors:
-
The
price
per night of the vacation home. -
The user’s membership level—some levels get a percentage
discount
above a certain price per nightthreshold
. -
The negotiated
margin
as a percentage of the price-per-night, after user discounts.
The algorithm that we will use to calculate the profit for each home is as follows:
if (price < threshold) {
profit = price * margin
} else {
profit = price * (1 - discount) * margin;
}
We probably don’t want to use the absolute profit as a score; it would
overwhelm the other factors like location, popularity and features. Instead,
we can express the profit as a percentage of our target
profit. A profit
margin above our target will have a positive score (greater than 1.0
), and a profit margin below our target will have a negative score (less than
1.0
):
if (price < threshold) {
profit = price * margin
} else {
profit = price * (1 - discount) * margin
}
return profit / target
The default scripting language in Elasticsearch is Groovy, which for the most part looks a lot like JavaScript. The preceding algorithm as a Groovy script would look like this:
price = doc['price'].value (1)
margin = doc['margin'].value (1)
if (price < threshold) { (2)
return price * margin / target
}
return price * (1 - discount) * margin / target (2)
-
The
price
andmargin
variables are extracted from theprice
andmargin
fields in the document. -
The
threshold
,discount
, andtarget
variables we will pass in asparams
.
Finally, we can add our script_score
function to the list of other functions
that we are already using:
GET /_search
{
"function_score": {
"functions": [
{ ...location clause... }, (1)
{ ...price clause... }, (1)
{
"script_score": {
"params": { (2)
"threshold": 80,
"discount": 0.1,
"target": 10
},
"script": "price = doc['price'].value; margin = doc['margin'].value;
if (price < threshold) { return price * margin / target };
return price * (1 - discount) * margin / target;" (3)
}
}
]
}
}
-
The
location
andprice
clauses refer to the example explained in [decay-functions]. -
By passing in these variables as
params
, we can change their values every time we run this query without having to recompile the script. -
JSON cannot include embedded newline characters. Newline characters in the script should either be escaped as
\n
or replaced with semicolons.
This query would return the documents that best satisfy the user’s requirements for location and price, while still factoring in our need to make a profit.
Tip
|
The That said, scripts can have a performance impact. If you do find that your scripts are not quite fast enough, you have three options:
|