On Github hjc1710 / es-intro-pres
Created by Hayden Chudy / @hjc1710 Open Notes
# latest on Ubuntu $ apt-get install openjdk-7-jdk $ wget -qO- http://packages.elasticsearch.org/GPG-KEY-elasticsearch - | apt-key add - $ echo "deb http://packages.elasticsearch.org/elasticsearch/1.7/debian stable main" > /etc/apt/sources.list.d/elasticsearch.list $ apt-get update $ apt-get install elasticsearch # OSX with Brew $ brew install elasticsearch
hayden@beardtop ~> curl -XGET localhost:9200 { "status" : 200, "name" : "Madam Slay", "cluster_name" : "elasticsearch", "version" : { "number" : "1.7.1", "build_hash" : "b88f43fc40b0bcd7f173a1f9ee2e97816de80b19", "build_timestamp" : "2015-07-29T09:54:16Z", "build_snapshot" : false, "lucene_version" : "4.10.4" }, "tagline" : "You Know, for Search" }
hayden@beardtop ~> curl -XPOST "localhost:9200/croscon/employees/1" -d '{ "name": "Tom Sawyer", "id": 1, "specialties": ["javascript", "php"] }' {"_index":"croscon","_type":"employees","_id":"1","_version":1,"created":true} hayden@beardtop ~> curl -XGET "localhost:9200/croscon/employees/1" {"_index":"croscon","_type":"employees","_id":"1","_version":1,"found":true,"_source":{ "name": "Tom Sawyer", "id": 1, "specialties": ["javascript", "php"] }}
hayden@beardtop ~> curl -XPUT "localhost:9200/croscon/employees/1" -d '{ "name": "Tom Sawyer", "id": 1, "specialties": ["javascript", "php"], "date_of_birth": "1990-12-10" }' {"_index":"croscon","_type":"employees","_id":"1","_version":2,"created":false} hayden@beardtop ~> curl -XGET "localhost:9200/croscon/employees/1" {"_index":"croscon","_type":"employees","_id":"1","_version":2,"found":true,"_source":{ "name": "Tom Sawyer", "id": 1, "specialties": ["javascript", "php"], "date_of_birth": "1990-12-10" }}Let's add a new date of birth field. To update an item, you merely PUT to the route by ID with the new, complete document. Deleting of fields is done by omission. Brand new fields are automatically mapped into the index, parsed, and saved. Known fields are parsed as described by their mappings, and saved. Some more employee data: { "name": "Ben Rogers", "id": 2, "specialties": ["css", "less", "magic"], "date_of_birth": "1987-08-10" } { "name": "Huck Finn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17" } { "name": "Pap Finn", "id": 4, "specialties": ["php", "python", "devops", "magic"], "date_of_birth": "1984-07-16" }
hayden@beardtop ~> curl -XGET 'localhost:9200/croscon/employees/_search?pretty' { "took" : 3, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 4, "max_score" : 1.0, "hits" : [ { "_index" : "croscon", "_type" : "employees", "_id" : "4", "_score" : 1.0, "_source":{ "name": "Pap Finn", "id": 4, "specialties": ["php", "python", "devops", "magic"], "date_of_birth": "1984-07-16" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "1", "_score" : 1.0, "_source":{"name": "Tom Sawyer","id": 1,"specialties": ["javascript", "php"],"date_of_birth": "1990-12-10"} }, { "_index" : "croscon", "_type" : "employees", "_id" : "2", "_score" : 1.0, "_source":{ "name": "Ben Rogers", "id": 2, "specialties": ["css", "less", "magic"], "date_of_birth": "1987-08-10" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "3", "_score" : 1.0, "_source":{ "name": "Huck Finn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17" } } ] } }The presence of that `_search` is integral. Running a search against just an index and a type will produce an error along the lines of: `No endpoint found for $TYPENAME`. If you'll notice, these aren't in any real order, and every document returned has a score of: 1.0. When searching, `?pretty` is your friend, otherwise, all your results are returned minified.
hayden@beardtop ~> curl -XGET 'localhost:9200/croscon/employees/_search?pretty' -d ' { "query": { "term": { "specialties": "php" } } }' { "hits" : { "total" : 3, "max_score" : 0.19178301, "hits" : [ { "_index" : "croscon", "_type" : "employees", "_id" : "1", "_score" : 0.19178301, "_source":{ "name": "Tom Sawyer","id": 1,"specialties": ["javascript", "php"],"date_of_birth": "1990-12-10" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "4", "_score" : 0.15342641, "_source":{ "name": "Pap Finn", "id": 4, "specialties": ["php", "python", "devops", "magic"], "date_of_birth": "1984-07-16" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "3", "_score" : 0.15342641, "_source":{ "name": "Huck Finn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17" } } ] } }And everyone but Ben is returned, as expected. If you'll notice, we searched for a string in an array rather easily, and ES will naturally make all of its queries just work with arrays. If you notice, the score has changed! Tom Sawyer wins because he has the least specialties!
hayden@beardtop ~> curl -XGET 'localhost:9200/croscon/employees/_search' -d ' { "query": { "range": { "date_of_birth": { "gte": "1990-01-01", "lte": "now" } } } }' { "hits" : { "total" : 2, "max_score" : 1.0, "hits" : [ { "_index" : "croscon", "_type" : "employees", "_id" : "1", "_score" : 1.0, "_source":{ "name": "Tom Sawyer", "id": 1, "specialties": ["javascript", "php"],"date_of_birth": "1990-12-10" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "3", "_score" : 1.0, "_source":{ "name": "Huck Finn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17" } } ] } }The `lte` for now is optional, but it is included to show you that ES has some strings with special meaning. This one turns into the current DateTime. You'll notice there's still no score here. That's because scoring a range query is hard and elasticsearch doesn't do everything These are all just very light examples, and all of the options given can be further customized to support boosting, multiple date formats, timeszones, etc. Segue into mapping with a note about: "But, wait... how did that date range search work? We just gave it a string? There were no explicit dates involved!"
hayden@beardtop ~> curl -XGET 'localhost:9200/_mapping?pretty' { "croscon" : { "mappings" : { "employees" : { "properties" : { "date_of_birth" : { "type" : "date", "format" : "dateOptionalTime" }, "id" : { "type" : "long" }, "name" : { "type" : "string" }, "specialties" : { "type" : "string" } } } } } }
hayden@beardtop ~> curl -XPUT 'localhost:9200/croscon/_mapping/projects' -d ' { "projects": { "_id": { "index": "not_analyzed", "path": "id", "type": "long" }, "properties": { "name": { "type": "string", "store": true, "index": "analyzed", "fields": { "raw": { "type": "string", "index": "not_analyzed" } } }, "due_date": { "type": "date" }, "id": { "type": "long" }, "client": { "type": "object", "properties": { "id": { "type": "long" }, "name": { "type": "string" } } } } } }'
hayden@beardtop ~> curl -XPOST 'localhost:9200/croscon/employees' -d ' { "name": "Ben Rogers", "id": 2, "specialties": ["css", "less", "magic"], "date_of_birth": "1987-XX-YY" }' {"error":"MapperParsingException[failed to parse [date_of_birth]]; nested: MapperParsingException[failed to parse date field [1987-XX-YY], tried both date format [dateOptionalTime], and timestamp number with locale []]; nested: IllegalArgumentException[Invalid format: \"1987-XX-YY\" is malformed at \"-XX-YY\"]; ","status":400}I will point out though, that this is a very useful error message.
hayden@beardtop ~> curl -XGET 'localhost:9200/croscon/projects/_search' -d ' { "query": { "match_all": {} }, "sort": [ { "due_date": { "order": "asc", "missing": "_last" } }, { "name.raw": { "order": "desc" } }, "_score" ] }' { "hits" : { "total" : 3, "max_score" : null, "hits" : [ { "_index" : "croscon", "_type" : "projects", "_id" : "1", "_score" : 1.0, "_source":{"name": "MC3 Rearch", "due_date": "2015-09-08", "id": 1, "client": { "name": "HFA", "id": 1 } }, "sort" : [ 1441670400000, "MC3 Rearch", 1.0 ] }, { "_index" : "croscon", "_type" : "projects", "_id" : "2", "_score" : 1.0, "_source":{"name": "MC4", "due_date": "2016-12-21", "id": 2, "client": { "name": "Croscon", "id": 2 } }, "sort" : [ 1482278400000, "MC4", 1.0 ] }, { "_index" : "croscon", "_type" : "projects", "_id" : "3", "_score" : 1.0, "_source":{"name": "MC5", "due_date": null, "id": 3, "client": null }, "sort" : [ 9223372036854775807, "MC5", 1.0 ] } ] } }
curl -XGET 'localhost:9200/croscon/projects/_search?pretty' -d ' { "query": { "match_all": {} }, "sort": [ { "due_date": { "order": "asc", "missing": "_first" } }, { "name.raw": { "order": "desc" } }, "_score" ] }' { "hits" : { "total" : 3, "max_score" : null, "hits" : [ { "_index" : "croscon", "_type" : "projects", "_id" : "3", "_score" : 1.0, "_source":{"name": "MC5", "due_date": null, "id": 3, "client": null }, "sort" : [ -9223372036854775808, "MC5", 1.0 ] }, { "_index" : "croscon", "_type" : "projects", "_id" : "1", "_score" : 1.0, "_source":{"name": "MC3 Rearch", "due_date": "2015-09-08", "id": 1, "client": { "name": "HFA", "id": 1 } }, "sort" : [ 1441670400000, "MC3 Rearch", 1.0 ] }, { "_index" : "croscon", "_type" : "projects", "_id" : "2", "_score" : 1.0, "_source":{"name": "MC4", "due_date": "2016-12-21", "id": 2, "client": { "name": "Croscon", "id": 2 } }, "sort" : [ 1482278400000, "MC4", 1.0 ] } ] } }We can also make missing fields come first.s
curl -XGET 'localhost:9200/croscon/projects/_search?pretty' -d ' { "query": { "match_all": {} }, "sort": [ { "due_date": { "order": "asc", "missing": 1444455518000 } }, { "name.raw": { "order": "desc" } }, "_score" ] }' { "hits" : { "total" : 3, "max_score" : null, "hits" : [ { "_index" : "croscon", "_type" : "projects", "_id" : "1", "_score" : 1.0, "_source":{"name": "MC3 Rearch", "due_date": "2015-09-08", "id": 1, "client": { "name": "HFA", "id": 1 } }, "sort" : [ 1441670400000, "MC3 Rearch", 1.0 ] }, { "_index" : "croscon", "_type" : "projects", "_id" : "3", "_score" : 1.0, "_source":{"name": "MC5", "due_date": null, "id": 3, "client": null }, "sort" : [ 1444455518000, "MC5", 1.0 ] }, { "_index" : "croscon", "_type" : "projects", "_id" : "2", "_score" : 1.0, "_source":{"name": "MC4", "due_date": "2016-12-21", "id": 2, "client": { "name": "Croscon", "id": 2 } }, "sort" : [ 1482278400000, "MC4", 1.0 ] } ] } }Or we can give it a specific value. In this example, we've given it the UNIX timestamp that corresponds to: 2015-10-10, putting it right in the middle
hayden@beardtop ~> curl -XGET 'localhost:9200/tweets/tweet/_search?pretty' -d ' { "query": { "match": { "tweet": "taylorswift13" } } }' { "hits" : { "total" : 2, "max_score" : 0.095891505, "hits" : [ { "_index" : "tweets", "_type" : "tweet", "_id" : "2", "_score" : **0.095891505**, "_source":{"user": "@hjc1710", "tweet": "Not convinced @carlyraejepsen is better than @taylorswift13 though."} }, { "_index" : "tweets", "_type" : "tweet", "_id" : "4", "_score" : **0.076713204**, "_source":{"user": "@hjc1710", "tweet": "You'll always have my heart @taylorswift13, no matter what @carlyraejepsen does."} } ] } }
hayden@beardtop ~> curl -XGET 'localhost:9200/tweets/tweet/_search?pretty&explain' -d ' { "query": { "match": { "tweet": "carlyraejepsen" } } }' { "hits" : { "total" : 4, "max_score" : 0.11506981, "hits" : [ { "_shard" : 2, "_node" : "08j3yVwyRaCJ3PQrQBcy3A", "_index" : "tweets", "_type" : "tweet", "_id" : "1", "_score" : 0.11506981, "_source":{"user": "@hjc1710", "tweet": "The new @carlyraejepsen album is top notch"}, "_explanation" : { "value" : 0.11506981, "description" : "weight(tweet:carlyraejepsen in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.11506981, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.375, "description" : "fieldNorm(doc=0)" } ] } ] } }, { "_source": {"user": "@hjc1710", "tweet": "Not convinced @carlyraejepsen is better than @taylorswift13 though."}, "_explanation" : { "value" : 0.095891505, "description" : "weight(tweet:carlyraejepsen in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.095891505, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.3125, "description" : "fieldNorm(doc=0)" } ] } ] ...
hayden@beardtop ~> curl -XGET 'localhost:9200/tweets/tweet/_search?pretty&explain' -d ' { "query": { "bool": { "should": [ { "match": { "tweet": { "query": "taylorswift13", "boost": 2 } } }, { "match": { "tweet": "carlyraejepsen" } } ] } } }' { "hits" : { "total" : 4, "max_score" : 0.12865195, "hits" : [ { "_shard" : 3, "_node" : "08j3yVwyRaCJ3PQrQBcy3A", "_index" : "tweets", "_type" : "tweet", "_id" : "2", "_score" : 0.12865195, "_source":{"user": "@hjc1710", "tweet": "Not convinced @carlyraejepsen is better than @taylorswift13 though."}, "_explanation" : { "value" : 0.12865196, "description" : "sum of:", "details" : [ { "value" : 0.08576798, "description" : "weight(tweet:taylorswift13^2.0 in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.08576798, "description" : "score(doc=0,freq=1.0), product of:", "details" : [ { "value" : 0.89442724, "description" : "queryWeight, product of:", "details" : [ { "value" : 2.0, "description" : "boost" }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 1.4574206, "description" : "queryNorm" } ] }, { "value" : 0.095891505, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.3125, "description" : "fieldNorm(doc=0)" } ] } ] } ] }, { "value" : 0.04288399, "description" : "weight(tweet:carlyraejepsen in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.04288399, "description" : "score(doc=0,freq=1.0), product of:", "details" : [ { "value" : 0.44721362, "description" : "queryWeight, product of:", "details" : [ { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 1.4574206, "description" : "queryNorm" } ] }, { "value" : 0.095891505, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.3125, "description" : "fieldNorm(doc=0)" } ] } ] } ] } ] } }, { "_shard" : 0, "_node" : "08j3yVwyRaCJ3PQrQBcy3A", "_index" : "tweets", "_type" : "tweet", "_id" : "4", "_score" : 0.10292157, "_source":{"user": "@hjc1710", "tweet": "You'll always have my heart @taylorswift13, no matter what @carlyraejepsen does."}, "_explanation" : { "value" : 0.10292157, "description" : "sum of:", "details" : [ { "value" : 0.06861438, "description" : "weight(tweet:taylorswift13^2.0 in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.06861438, "description" : "score(doc=0,freq=1.0), product of:", "details" : [ { "value" : 0.89442724, "description" : "queryWeight, product of:", "details" : [ { "value" : 2.0, "description" : "boost" }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 1.4574206, "description" : "queryNorm" } ] }, { "value" : 0.076713204, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.25, "description" : "fieldNorm(doc=0)" } ] } ] } ] }, { "value" : 0.03430719, "description" : "weight(tweet:carlyraejepsen in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.03430719, "description" : "score(doc=0,freq=1.0), product of:", "details" : [ { "value" : 0.44721362, "description" : "queryWeight, product of:", "details" : [ { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 1.4574206, "description" : "queryNorm" } ] }, { "value" : 0.076713204, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.25, "description" : "fieldNorm(doc=0)" } ] } ] } ] } ] } }, { "_shard" : 4, "_node" : "08j3yVwyRaCJ3PQrQBcy3A", "_index" : "tweets", "_type" : "tweet", "_id" : "3", "_score" : 0.005816851, "_source":{"user": "@hjc1710", "tweet": "Yea, I think I like 1989 more than anything @carlyraejepsen has done."}, "_explanation" : { "value" : 0.005816851, "description" : "product of:", "details" : [ { "value" : 0.011633702, "description" : "sum of:", "details" : [ { "value" : 0.011633702, "description" : "weight(tweet:carlyraejepsen in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.011633702, "description" : "score(doc=0,freq=1.0), product of:", "details" : [ { "value" : 0.15165187, "description" : "queryWeight, product of:", "details" : [ { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.49421698, "description" : "queryNorm" } ] }, { "value" : 0.076713204, "description" : "fieldWeight in 0, product of:", "details" : [ { "value" : 1.0, "description" : "tf(freq=1.0), with freq of:", "details" : [ { "value" : 1.0, "description" : "termFreq=1.0" } ] }, { "value" : 0.30685282, "description" : "idf(docFreq=1, maxDocs=1)" }, { "value" : 0.25, "description" : "fieldNorm(doc=0)" } ] } ] } ] } ] }, { "value" : 0.5, "description" : "coord(1/2)" } ] } } ] } }Legit, just walk through this. This is a truncated resultset too.
ngram("croscon", max=5, min=4) == [ "cros", "crosc", "rosc", "rosco", "osco", "oscon", "scon" ]
['The', 'fox', 'is', 'brown', 'and', 'warm'] -> ['fox', 'brown', 'warm']
doc = create_doc(id) doc.top_specialty = specialities[0] index_doc(doc)
hayden@beardtop ~> curl -XGET localhost:9200/croscon/employees/_search -d ' { "query": { "term": { "top_specialty": "php" } } }' { "hits" : { "total" : 2, "max_score" : 1.0, "hits" : [ { "_index" : "croscon", "_type" : "employees", "_id" : "3", "_score" : 1.0, "_source":{ "name": "Huck Finn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17", "top_specialty": "php" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "4", "_score" : 0.30685282, "_source":{ "name": "Pap Finn", "id": 4, "specialties": ["php", "python", "devops", "magic"], "date_of_birth": "1984-07-16", "top_specialty": "php" } } ] } }But wait... why the score difference? While the IDF value was the same earlier, as I said, doc count is calculated PER SHARD and not PER INDEX. In this example, Huck lives on a shard that has two items in it, and Pap lives on a shard that has one item in it (just Pap), since IDF is the total number of documents divded by the frequency of a term, the score goes up as there are more documents! In reality, the distinction that document count is per shard and not per index has little meaning. You generally have so many items, and ES does a fine enough job of spreading them out, that this all evens out.
hayden@beardtop ~> curl -XGET 'localhost:9200/croscon/employees/_search?pretty' -d ' { "query": { "filtered": { "query": { "match_all": {} }, "filter": { "term": { "specialties": "php" } } } } }' { "hits" : { "total" : 3, "max_score" : 1.0, "hits" : [ { "_index" : "croscon", "_type" : "employees", "_id" : "4", "_score" : 1.0, "_source":{ "name": "Pap Finn", "id": 4, "specialties": ["php", "python", "devops", "magic"], "date_of_birth": "1984-07-16", "top_specialty": "php" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "1", "_score" : 1.0, "_source":{ "name": "Tom Sawyer", "id": 1, "specialties": ["javascript", "php"], "date_of_birth": "1990-12-10" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "3", "_score" : 1.0, "_source":{ "name": "Huck Finn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17", "top_specialty": "php" } } ] } }
{ "query": { "filtered": { "query": { "match_all": {} }, "filter": { "query": { "match": { "name": "Finn" } } } } } }
hayden@beardtop ~> curl -XGET 'localhost:9200/croscon/employees/_search?pretty' -d ' { "query": { "filtered": { "filter": { "bool": { "must": { "query": { "bool": { "should": [ { "match": { "specialties": "css" } }, { "match": { "specialties": "php" } }, { "match": { "specialties": "javascript" } }, { "match": { "specialties": "python" } } ], "minimum_should_match": 2 } } }, "should": [ { "exists": { "field": "date_of_birth" } }, { "missing": { "field": "field_that_dne" } } ], "must_not": { "ids": { "type": "employees", "values": [1] } } } } } } }' { "hits" : { "total" : 2, "max_score" : 1.0, "hits" : [ { "_index" : "croscon", "_type" : "employees", "_id" : "4", "_score" : 1.0, "_source":{ "name": "Pap Finn", "id": 4, "specialties": ["php", "python", "devops", "magic"], "date_of_birth": "1984-07-16", "top_specialty": "php" } }, { "_index" : "croscon", "_type" : "employees", "_id" : "3", "_score" : 1.0, "_source":{ "name": "HuckFinn", "id": 3, "specialties": ["php", "python", "devops"], "date_of_birth": "1990-10-17", "top_specialty": "php" } } ] } }
hayden@beardtop ~> curl -XGET localhost:9200/inventory/products,services/_search { "sort": [{ # list services over products "product_id": { "order": "desc", "missing": 9223372036854775806 } }, # sort by natural score and then name "_score", { "name.raw": { "order": "asc" } }], "query": { "filtered": { "query": { "function_score": { "query": { "function_score": { "score_mode": "max", "boost_mode": "replace", "query": { "bool": { # create an OR on products for a vendor and unfrozen services "should": [{ "bool": { "must": [{ "term": { # only return products for this vendor "vendor_id": { "value": 160, "boost": 1 } } }, { "term": { "_type": { # services don't have this field "value": "products", "boost": 1 } } }], "must_not": [{ "term": { # must be in stock "out_of_stock": { "value": true, "boost": 1 } } }] } }, { # only return unfrozen services "bool": { "must": [{ "term": { "_type": { "value": "services", "boost": 1 } } }], # should not have frozen as true "must_not": [{ "term": { "frozen": { "value": true, "boost": 1 } } }] } }], "minimum_number_should_match": 1 } }, # weight by category "functions": [{ "weight": 13, "filter": { "term": { "category_id": "18" } } }, { "weight": 12, "filter": { "term": { "category_id": "17" } } }] } }, # take the first score from a function, and # then multiply the that with the score from the query "score_mode": "first", "boost_mode": "multiply", "functions": [{ # if your stock is 0, we drop your score to 0 "weight": 0, "filter": { "term": { "stock": 0 } } }, {