我們發現一些用戶經常編寫了一些非常冗長和復雜的查詢 - 在很多情況下,相同的查詢會一遍又一遍地執行,但是會有一些不同的值作為參數來查詢。在這種情況下,我們覺得使用一個search template(搜索模板)來做這樣的工作非常合適。搜索模板允許您使用可在執行時定義的參數定義查詢。
Search template的好處是:
- 避免在多個地方重復代碼
- 更容易測試和執行您的查詢
- 在應用程序間共享查詢
- 允許用戶只執行一些預定義的查詢
- 將搜索邏輯與應用程序邏輯分離
定義一個Search template
首先,我們來定義一個search template來看看它到底是什么東西。使用_scripts
端點將模板存儲在集群狀態中。在search template中使用的語言叫做mustache。(http://mustache.github.io/mustache.5.html)
POST _scripts/my_search_template
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"{{my_field}}": "{{my_value}}"
}
}
}
}
}
在這里,我們定義了一個叫做my_search_template的search template。如果我們想更新這個search template,我們可以直接進行修改,然后再次運行上面的命令即可。
在match的字段里,我們定義了兩個參數:my_field及my_value。下面,我們來首先建立一個叫做twitter的數據庫:
PUT twitter/_doc/1
{
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
PUT twitter/_doc/2
{
"user" : "虹橋-老吳",
"message" : "好友來了都今天我生日,好友來了,什么 birthday happy 就成!",
"uid" : 7,
"age" : 90,
"city" : "上海",
"province" : "上海",
"country" : "中國",
"address" : "中國上海市閔行區",
"location" : {
"lat" : "31.175927",
"lon" : "121.383328"
}
}
我們這里把上面的兩個文檔存於到twitter的index之中。我們現在可以使用我們剛才定義的search template來進行搜索:
GET twitter/_search/template
{
"id": "my_search_template",
"params": {
"my_field": "city",
"my_value": "北京"
}
}
顯示的結果是:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9808292,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.9808292,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
}
]
}
}
顯示它只顯示了我們的city為北京的一個文檔,另外一個上海的文檔沒有做任何的顯示。說明我們定義的search template是工作的。
條件判斷
在Mustache語言中,它沒有if/else這樣的判斷,但是你可以定section來跳過它如果那個變量是false還是沒有被定義:
{{#param1}}
"This section is skipped if param1 is null or false"
{{/param1}}
我們定義如下的一個search template:
POST _scripts/docs_from_beijing_and_age
{
"script": {
"lang": "mustache",
"source":
"""
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "{{search_term}}"
}
}
{{#search_age}}
,
{
"range": {
"age": {
"gte": {{search_age}}
}
}
}
{{/search_age}}
]
}
}
}
"""
}
}
在這里,我們同時定義了兩個變量:search_term及search_age。針對search_age,我們做了一個判斷,如果它有定義,及做一個range的查詢。如果沒有定義,就只用search_term。那么我們來做如下的實驗:
GET twitter/_search/template
{
"id": "docs_from_beijing_and_age",
"params": {
"search_term": "北京"
}
}
顯示的結果是:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9808292,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.9808292,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
}
]
}
}
顯然,city為北京的文檔已經被搜索到了。如果我們做如下的查詢:
GET twitter/_search/template
{
"id": "docs_from_beijing_and_age",
"params": {
"search_term": "北京",
"search_age": "30"
}
}
我們將搜索不到任何的結果,這是因為在這次查詢中search_age已經被啟用,而且在數據庫中沒有一個文檔是來自“北京”,並且年齡大於30的。我們可以做如下的查詢:
GET twitter/_search/template
{
"id": "docs_from_beijing_and_age",
"params": {
"search_term": "北京",
"search_age": "20"
}
}
那么這次的顯示結果為:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.9808292,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.9808292,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
}
]
}
}
顯然這次我們搜索到我們想要的結果。
查詢search template
GET _scripts/<templateid>
針對我們的情況:
GET _scripts/docs_from_beijing_and_age
顯示的結果為:
{
"_id" : "docs_from_beijing_and_age",
"found" : true,
"script" : {
"lang" : "mustache",
"source" : """
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "{{search_term}}"
}
}
{{#search_age}}
,
{
"range": {
"age": {
"gte": {{search_age}}
}
}
}
{{/search_age}}
]
}
}
}
"""
}
}
這個正是我們之前定義的一個search template。
刪除一個search template
我們可以通過如下的命令來刪除一個已經創建的search template:
DELETE _scripts/<templateid>
驗證search template
我們可以通過_render端點來驗證我們的search template。比如:
GET _render/template
{
"source": """
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "{{search_term}}"
}
}
{{#search_age}}
,
{
"range": {
"age": {
"gte": {{search_age}}
}
}
}
{{/search_age}}
]
}
}
}
""",
"params": {
"search_term": "北京",
"search_age": "20"
}
}
那么顯示的結果是:
{
"template_output" : {
"query" : {
"bool" : {
"must" : [
{
"match" : {
"city" : "北京"
}
},
{
"range" : {
"age" : {
"gte" : 20
}
}
}
]
}
}
}
}
顯然,這個就是我們想要的結果。
參考:
【1】https://www.elastic.co/guide/en/elasticsearch/reference/7.4/search-template.html