Negli articoli Elasticsearch: uso delle match query, Elasticsearch: uso delle term query e Elasticsearch: query compound abbiamo visto come interrogare sia i campi testuali che i dati strutturati imponendo la verifica di più condizioni contemporaneamente su un indice di Elasticsearch. Inoltre, nelle compound query abbiamo visto come modificare il calcolo dello score per adattarlo ai dati di ciascun documento. Queste query, però, non rispondono alla necessità di ricercare documenti che sono collegati tra di loro mediante relazioni di parentela. Si consideri che Elasticsearch è un database NoSQL e quindi le operazioni di join tra documenti non sono native e, seppur disponibili, sono limitate ed onerose.
In questo articolo vedremo come definire una gerarchia tra i documenti per utilizzarla nelle ricerche. Inoltre, vi forniremo alcune query “bonus” che potrebbero tornare utili per alcuni progetti.
Come nei tutorial precedenti, useremo lo stesso ambiente. Pertanto vi invitiamo a leggere attentamente le istruzioni per installare lo stack di Elasticsearch sul vostro PC mediante il repository Docker e come importare correttamente i dati.
Di seguito riportiamo i principali esempi di query trattati in questa guida, per un rapido riferimento:
Categoria | Tipo | Criterio di match | Query | Match | No Match |
---|---|---|---|---|---|
has_child | join | Interroga i documenti figli e restituisce i documenti genitori corrispondenti (dei figli corrispondenti). | N/A | N/A | N/A |
has_parent | join | Esegue una query sui documenti padre e restituisce i documenti padre corrispondenti (dei genitori corrispondenti). | N/A | N/A | N/A |
query_string | full-text | Query polivalente che "può fare da collettore" all'uso di altre query come "match", "multi-match", "regexp", "wildcard" ecc. Ha una formattazione rigorosa | (position:engineer) OR (salary:(>=10000 AND <=52000)) | i documenti con il testo "engineer" nel campo "position" OPPURE i documenti che hanno un intervallo di retribuzione compreso tra 10.000 e 52.000 (inclusi 10.000 e 52.000) | N/A |
simple_query_string | full-text | Come query_string, ma non rigoroso | (position:engineer) | (country:china) | Documenti con "engineer" nel campo "position" OPPURE china nel campo "country". | N/A |
Parent-Child Query
Le relazioni uno a molti possono essere gestite utilizzando il metodo genitore-figlio (ora chiamato operazione di join) in Elasticsearch. Dimostriamo questo con un esempio di scenario. Consideriamo di avere un forum, in cui chiunque può pubblicare qualsiasi argomento (diciamo i post). Gli utenti possono commentare i singoli post. Quindi, in questo scenario, possiamo considerare i singoli post come documenti padre e i commenti ad essi come figli. Questo è spiegato meglio nella figura seguente:

Per questa operazione, verrà creato un indice separato, con una mappatura speciale (schema) applicata.
Creare l’indice con il tipo di dati join con la richiesta seguente
PUT post-comments
{
"mappings": {
"properties": {
"document_type": {
"type": "join",
"relations": {
"post": "comment"
}
}
}
}
}
Nello schema sopra riportato, si può notare la presenza di un tipo denominato “join”, che indica che questo indice avrà documenti correlati a genitori e figli. Inoltre, nell’oggetto “relations” sono definiti i nomi degli identificatori di genitore e figlio.
Cioè post:comment si riferisce alla relazione genitore:figlio. Ogni documento sarà composto da un campo chiamato “tipo_documento”, che avrà il valore “post” o “comment”. Il valore “post” indicherà che il documento è un genitore e il valore “comment” indicherà che il documento è un “figlio”.
Indicizziamo alcuni documenti:
PUT post-comments/_doc/1
{
"document_type": {
"name": "post"
},
"post_title" : "Angel Has Fallen"
}
PUT post-comments/_doc/2
{
"document_type": {
"name": "post"
},
"post_title" : "Beauty and the beast - a nice movie"
}
Indicizziamo ora dei documenti figli
PUT post-comments/_doc/A?routing=1
{
"document_type": {
"name": "comment",
"parent": "1"
},
"comment_author": "Neil Soans",
"comment_description": "'Angel has Fallen' has some redeeming qualities, but they're too few and far in between to justify its existence"
}
PUT post-comments/_doc/B?routing=1
{
"document_type": {
"name": "comment",
"parent": "1"
},
"comment_author": "Exiled Universe",
"comment_description": "Best in the trilogy! This movie wasn't better than the Rambo movie but it was very very close."
}
PUT post-comments/_doc/D?routing=1
{
"document_type": {
"name": "comment",
"parent": "2"
},
"comment_author": "Emma Cochrane",
"comment_description": "There's the sublime beauty of a forgotten world and the promise of happily-ever-after to draw you to one of your favourite fairy tales, once again. Give it an encore."
}
PUT post-comments/_doc/E?routing=1
{
"document_type": {
"name": "comment",
"parent": "2"
},
"comment_author": "Common Sense Media Editors",
"comment_description": "Stellar music, brisk storytelling, delightful animation, and compelling characters make this both a great animated feature for kids and a great movie for anyone"
}
Query has_child
Questa query interroga i documenti figlio e restituisce come risultati i genitori ad essi associati. Supponiamo di dover cercare il termine “musica” nel campo “commenti_descrizione” nei documenti figli e di dover ottenere i documenti genitori corrispondenti ai risultati della ricerca, possiamo usare la query has_child come segue:
GET post-comments/_search
{
"query": {
"has_child": {
"type": "comment",
"query": {
"match": {
"comment_description": "music"
}
}
}
}
}
Per la query di cui sopra, i documenti figli che corrispondono alla ricerca sono solo il documento con id=E, per il quale il genitore è il documento con id=2. Il risultato della ricerca ci porterà al documento padre come di seguito indicato:
{
"took": 46,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "post-comments",
"_id": "2",
"_score": 1,
"_source": {
"document_type": {
"name": "post"
},
"post_title": "Beauty and the beast - a nice movie"
}
}
]
}
}
Query has_parent
La query has_parent esegue l’opposto della query has_child, cioè restituisce i documenti figli dei documenti genitori che corrispondono alla query.
Cerchiamo la parola “Beauty” nel documento padre e restituiamo i documenti figli dei genitori corrispondenti. A tale scopo, possiamo utilizzare la seguente query
GET post-comments/_search
{
"query": {
"has_parent": {
"parent_type": "post",
"query": {
"match": {
"post_title": "Beauty"
}
}
}
}
}
Il documento genitore corrispondente alla query di cui sopra è quello con id documento =1. Come si può vedere dalla risposta sottostante, i documenti figli corrispondenti al documento id=1 vengono restituiti dalla query precedente:
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "post-comments",
"_id": "D",
"_score": 1,
"_routing": "1",
"_source": {
"document_type": {
"name": "comment",
"parent": "2"
},
"comment_author": "Emma Cochrane",
"comment_description": "There's the sublime beauty of a forgotten world and the promise of happily-ever-after to draw you to one of your favourite fairy tales, once again. Give it an encore."
}
},
{
"_index": "post-comments",
"_id": "E",
"_score": 1,
"_routing": "1",
"_source": {
"document_type": {
"name": "comment",
"parent": "2"
},
"comment_author": "Common Sense Media Editors",
"comment_description": "Stellar music, brisk storytelling, delightful animation, and compelling characters make this both a great animated feature for kids and a great movie for anyone"
}
}
]
}
}
Restituzione dei documenti child con i parent
A volte, nei risultati della ricerca è necessario visualizzare sia il documento padre che quello figlio. Ad esempio, se stiamo elencando i post, sarebbe bello visualizzare anche alcuni commenti al di sotto di essi.
Per ottenere questo risultato, utilizziamo la query has_child per restituire i genitori mediante il parametro “inner_hits” restituiamo anche i child.
GET post-comments/_search
{
"query": {
"has_child": {
"type": "comment",
"query": {
"match": {
"comment_description": "music"
}
},
"inner_hits": {}
}
}
}
Il risultato della query sarà il seguente:
{
"took": 43,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "post-comments",
"_id": "2",
"_score": 1,
"_source": {
"document_type": {
"name": "post"
},
"post_title": "Beauty and the beast - a nice movie"
},
"inner_hits": {
"comment": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.1829326,
"hits": [
{
"_index": "post-comments",
"_id": "E",
"_score": 1.1829326,
"_routing": "1",
"_source": {
"document_type": {
"name": "comment",
"parent": "2"
},
"comment_author": "Common Sense Media Editors",
"comment_description": "Stellar music, brisk storytelling, delightful animation, and compelling characters make this both a great animated feature for kids and a great movie for anyone"
}
}
]
}
}
}
}
]
}
}
Query bonus
Di seguito riportiamo alcune query che potrebbero tornare utile in alcuni contesti.
query_string
La query “query_string” è una query speciale multiuso, che può raggruppare l’uso di diverse altre query come “match”, “multi-match”, “wildcard”, regexp” ecc. La query “query_string” segue un formato rigoroso e la sua violazione produce messaggi di errore. Per questo motivo, nonostante le sue capacità, è raramente utilizzata per l’implementazione di caselle di ricerca rivolte all’utente.
Vediamo un esempio di query in azione:
POST employees/_search
{
"query": {
"query_string": {
"query": "(roots heuristic systems) OR (enigneer~) OR (salary:(>=10000 AND <=52000)) ",
"fields": [
"position",
"phrase^3"
]
}
}
}
La query di cui sopra cercherà le parole “radici” O “euristica” O “sistemi” O “ingegnere” (l’uso di ~ nella query indica l’uso di una query fuzzy) nei campi “posizione” e “frase” e restituirà i risultati. “phrase^3” indica che le corrispondenze trovate nel campo “phrase” devono essere incrementate di un fattore 3. Salario:(>10000 AND <=52000), indica di recuperare i documenti che hanno il valore del campo “salario” compreso tra 10000 e 52000.
La query simple_query_string
La query “simple_query_string” è una forma semplificata della query_string con due differenze principali
È più tollerante agli errori, il che significa che non restituisce errori se la sintassi è sbagliata. Anzi, ignora la parte difettosa della query. Questo lo rende più facile da usare per le caselle di ricerca dell’interfaccia utente.
Gli operatori AND/OR/NOT ecc. sono sostituiti da +/|/-.
Un semplice esempio potrebbe essere:
POST employees/_search
{
"query": {
"simple_query_string": {
"query": "(roots) | (resources manager) + (male) ",
"fields": [
"gender",
"position",
"phrase^3"
]
}
}
}
La query di cui sopra cercherebbe “roots” OR “resources” OR “manager” AND “male” in tutti i campi indicati nell’array “fields”.
Query nominative
Le query nominative, come suggerisce il nome, riguardano la denominazione delle query. In alcuni casi è utile per identificare quali parti della query corrispondono al documento. Elasticsearch ci fornisce proprio questa funzione, consentendoci di assegnare un nome alla query o a parti della query in modo da vedere questi nomi con i documenti corrispondenti.
Ad esempio sottomettiamo la seguente query
POST employees/_search
{
"query": {
"match": {
"phrase": {
"query": "roots" ,
"_name": "phrase_field_name" }
}
}
}
Il risultato ottenuto sarà il seguente
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.6785375,
"hits": [
{
"_index": "employees",
"_id": "2",
"_score": 0.6785375,
"_source": {
"id": 2,
"name": "Othilia Cathel",
"email": "[email protected]",
"gender": "female",
"ip_address": "3.164.153.228",
"date_of_birth": "22/07/1987",
"company": "Edgepulse",
"position": "Structural Engineer",
"experience": 11,
"country": "China",
"phrase": "Grass-roots heuristic help-desk",
"salary": 193530
},
"matched_queries": [
"phrase_field_name"
]
},
{
"_index": "employees",
"_id": "4",
"_score": 0.62577873,
"_source": {
"id": 4,
"name": "Alan Thomas",
"email": "[email protected]",
"gender": "male",
"ip_address": "200.47.210.95",
"date_of_birth": "11/12/1985",
"company": "Yamaha",
"position": "Resources Manager",
"experience": 12,
"country": "China",
"phrase": "Emulation of roots heuristic coherent systems",
"salary": 300000
},
"matched_queries": [
"phrase_field_name"
]
}
]
}
}
Nell’esempio precedente, la query match è fornita con un parametro “_name”, che ha il nome della query come “phrase_field_name”. Nei risultati, abbiamo i documenti che sono stati abbinati ai risultati con un campo array chiamato “matched_queries”, che contiene i nomi delle query abbinate (qui “phrase_field_name”).
L’esempio seguente mostra l’uso delle query nominative in una bool query, che è uno dei casi più comuni di utilizzo delle query nominative.
POST employees/_search
{
"query": {
"bool":{
"should": [
{"match": {
"phrase": {
"query": "roots",
"_name": "phrase_field_name"}
}
},
{"match": {
"gender": {
"query": "female" ,
"_name": "gender_field_name"}
}
}
]
}
}
}
Il risultato sarà il seguente:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.8825103,
"hits": [
{
"_index": "employees",
"_id": "2",
"_score": 1.8825103,
"_source": {
"id": 2,
"name": "Othilia Cathel",
"email": "[email protected]",
"gender": "female",
"ip_address": "3.164.153.228",
"date_of_birth": "22/07/1987",
"company": "Edgepulse",
"position": "Structural Engineer",
"experience": 11,
"country": "China",
"phrase": "Grass-roots heuristic help-desk",
"salary": 193530
},
"matched_queries": [
"phrase_field_name",
"gender_field_name"
]
},
{
"_index": "employees",
"_id": "4",
"_score": 0.62577873,
"_source": {
"id": 4,
"name": "Alan Thomas",
"email": "[email protected]",
"gender": "male",
"ip_address": "200.47.210.95",
"date_of_birth": "11/12/1985",
"company": "Yamaha",
"position": "Resources Manager",
"experience": 12,
"country": "China",
"phrase": "Emulation of roots heuristic coherent systems",
"salary": 300000
},
"matched_queries": [
"phrase_field_name"
]
}
]
}
}