Neo4j: guida all’uso di un database a grafo

Neo4j è il leader dei database a grafo. Mediante le Cypher query è possibile gestire in modo ottimale una rappresentazione a grafo dei nostri dati e scoprire interessanti correlazioni tra di essi. Scopriamo come usarlo con alcuni semplici esempi di query e qualche suggerimento di caso d'uso avanzato.

Share

Reading time: 9 minutes

Il mondo dei database NoSQL è molto vasto. Nel libro Progettare con MongoDB abbiamo affrontato come modellare un database documentale per ottenere il massimo delle sue prestazioni. Non esistono però i database documentali quali Mongo. Tra le altre tipologie, quelli a grafo sono sicuramente molto interessanti. Essi si prestano molto bene per modellare rete sociali molto complesse o tracciare dati molto interconnessi tra di loro. Usando la teoria dei grafi è poi possibile effettuare analisi per l’individuazione di comportamenti anomali o anche solo semplicemente per raccomandare un oggetto di interesse. In questo articolo andremo a scoprire Neo4j, uno dei più utilizzati database a grafo.

Prima di iniziare a usare Neo4j, bisogna ricordare che questi tipi di database modellano i dati come un grafo, la cui struttura si basa su:

  • Nodi: rappresentano i record/dati. È possibile aggiungere zero o più etichette a un nodo.
  • Relazioni: rappresentano la connessione tra i nodi. Ogni connessione ha sempre una direzione.
  • Proprietà: rappresentano i valori dei dati nominati e possono essere associate sia ai nodi che alle relazioni.

Partiamo quindi con l’installazione.

Installazione

In questo tutorial ci baseremo sulla versione desktop installata su Ubuntu 20.04. Per prima cosa è necessario collegarsi al sito ufficiale di Neo4j. In base al vostro sistema operativo le istruzioni potrebbero variare da quelle che illustreremo di seguito.

Se siete sotto linux, una volta inseriti i vostri dati nel form apposito, inizierà il download dell’applicazione. Per lanciare l’installazione dovrete però rendere eseguibile il file con il seguente comando:

chmod +x FILE_NAME 

Eseguendo il file si aprirà la seguente finestra che richiede l’accettazione della licenza d’uso.

Una volta accettata, bisognerà inserire i vostri dati. Se avete fatto il download dal sito, e quindi già effettuato la registrazione, vi basterà riportare già precedentemente inseriti. Inoltre, dovrete copiare nell’apposito campo la chiave che era stata generata in precedenza.

Se tutti i campi sono stati correttamente inseriti, potrete avviare l’installazione cliccando sul pulsante “Activate”. A questo punto il software eseguirà una serie di passaggi per verificare che i requisiti di sistema siano soddisfatti e procedere così all’installazione dei vari componenti. In caso di errori o di aggiornamenti disponibili verranno visualizzate delle finestre apposite.

Quando il sistema è pronto vi verrà visualizzata la dashboard di Neo4j. Potrete notare che è stato già caricato un progetto d’esempio. Andremo ad utilizzare questo esempio per inziare a comprendere i concetti base di Neo4j.

CYPHER

Nel progetto di esempio, sono elencati due file. Noi andremo ad utilizzare il file about-movie.neo4j-browser-guide. Spostando il mouse sopra comparirà un bottone open che lancerà una nuova finestra relativa al nostro database. Questo è un tutorial per comprendere sia l’uso dell’interfaccia grafica, sia per iniziare ad utilizzare le CYPHER query.

Il database, chiamato Movie Graph, contiene gli attori e i registi di alcuni film. In particolare esistono 2 tipologie di nodi: Person e Movie. Questi nodi sono collegati tra loro da una serie di relazioni che rappresentano ad esempio se un attore ha recitato in un film o se la persona è stato il regista. Esistono anche delle relazioni sociali come ad esempio se una persona è un follower di un altra sui social networks. Per vedere tutte le tipologie di nodi e di relazioni, oltre alle loro possibili proprietà è sufficiente cliccare sull’icona del database in alto a sinistra.

Il linguaggio Cypher è usato per interrogare i dati in Neo4j. E’ un linguaggio altamente ottimizzato per individuare i nodi di interesse e navigare le relazioni tra di essi. Per scrivere una query in Cypher bisogna ricordarsi che non esistono tabella su cui si devono effettuare operazioni di join, ma solo nodi e relazioni. L’idea deve pertanto essere di individuare i nodi di interesse e da questi navigare le relazioni disponibili. Andiamo a scoprire le query più utilizzate.

MATCH

La clausola MATCH è usata per selezionare i nodi o le relazioni che soddisfano determinati criteri. E’ possibile filtrare gli oggetti in base alle loro etichette o alle proprietà ad esse associate. Nell’esempio riportato sotto vogliamo andare a visualizzare il nodo dell’attore Tom Hanks. Per rendere più efficiente la query andremo quindi a selezionare solo i nodi di tipo Person (a:Person) oltre ad imporre che l’oggetto dovrà avere la proprietà name uguale a Tom Hanks ({name:’Tom Hanks’}). 

Per visualizzare i dati è necessario sempre inserire il comando RETURN. Questo deve includere gli oggetti che vogliamo visualizzare. Il risultato verrà mostrato come un grafo. C’è la possibilità di visualizzarlo anche come tabella, testo o codice.

In questo caso viene solo visualizzato il nodo selezionato. Vedremo in seguito che la visualizzazione potrà essere anche molto più complessa in base alla query.

Il MATCH permette anche di individuare i nodi che hanno delle relazioni particolari con altri nodi. Ad esempio possiamo visualizzare tutti i film in cui Tom Hanks ha recitato. In questo caso partiamo dal selezionare il nodo di tipo Persona con la proprietà name uguale a Tom Hanks, come nell’esempio precedente. Dobbiamo poi imporre che il database dovrà “percorrere” solo le relazioni uscenti dal nodo selezionati e di tipo ACTED_IN. Per questioni di comprensione della query ma anche di efficienza, specifichiamo che il nodo collegato sarà di tipo Movie. La query risultante sarà:

 

MATCH (a:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m:Movie) RETURN a,m 

Inserendo nella RETURN sia il nodo di partenza che il nodo di destinazione ci verrà mostrato il grafo risultante. Cliccando su un nodo a scelta è anche possibile espandere il grafo, collassare le sue relazioni o rimuovere dalla vista il nodo stesso.

Nella funzione RETURN è possibile anche specificare solo gli attributi di interesse dei nodi ritornati. Supponiamo di voler visualizzare i nomi degli attori che hanno recitato insieme a Tom Hanks. Partendo dalla struttura della query precedente dovremo aggiungere una relazione ulteriore tra il nodo Movie e un nuovo nodo Person. Per visualizzare il nome sarà sufficiente indicare il nome dell’attributo mediante la dot notation. La query risultate è la seguente:

MATCH (a:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(c) RETURN c.name 

In questo caso non verrà visualizzato il grafo risultante, ma solamente una tabella le cui colonne sono le proprietà richieste mentre le righe sono i valori restituiti.

In modo analogo è possibile visualizzare le tipologie di relazioni che collegano due nodi e la struttura del nodo stesso. Supponiamo di voler visualizzare i ruoli delle persone che hanno recitato, diretto e revisionato il film Cloud Atlas. Partendo da un nodo generico di tipo Person imposteremo una relazione non direzionale con il film di interesse. Nella RETURN selezioneremo il nome del nodo Person, la tipologia della relazione e infine le informazioni contenute nella relazione. La query corrispondente è la seguente:

MATCH (people:Person)-[relatedTo]-(:Movie {title:'Cloud Atlas'}) 
RETURN people.name, type(relatedTo), relatedTo 

Il risultato sarà mostrato di default come una tabella. In questo caso però i dati associati alla relazione saranno mostrati in formato json.

WHERE

Nell’esempio precedente abbiamo cercato una corrispondenza perfetta. In molti casi è necessario imporri dei vincoli più laschi. Ciò si può ottenere mediante la clausola WHERE. I predicati posso essere molti complessi ed includere condizioni booleane (AND, OR e NOT), intervalli di valori e/o corrispondenze parziali di stringhe. Ad esempio la query per selezionare tutti gli attori il cui nome inizia per Tom avrà la seguente sintassi:

MATCH (a:Person) WHERE a.name STARTS WITH 'Tom' RETURN a 

Se invece volessimo tutti i film usciti negli anni Novanta la query sarebbe:

MATCH (a:Movie) WHERE a.released > 1990 AND a.released < 2000 RETURN a 

CREATE

L’istruzione CREATE serve per inserire i dati all’interno del nostro database. Come per la clausola MATCH, anche qui vengono usati i pattern per associare le etichette e le proprietà ai nodi. Ad esempio per creare un nodo di una persona possiamo scrivere la seguente query:

CREATE (a:Person {name:'Brie Larson', born:1989}) RETURN a 

Questa istruzione creerà un modo di tipo Person con le proprietà name uguale a Brie Larson e una data di nascita (attributo born) uguale a 1989. Il comando RETURN serve solo per visualizzare il nodo appena creato. In modo analogo possiamo creare un nodo di tipo Movie.

CREATE (a:Movie {title:'Captain Marvel', released:2019, 
tagline:'Everything begins with a (her)o.'}) RETURN a 

Attenzione

Le query precedentemente scritte non verificano l'esistenza di un nodo con gli stessi dati. Bisogna quindi prestare attenzione alla duplicazione dei dati o mediante il codice applicativo o la creazione di indici univoci all'interno del database.

DELETE

Il comando DELETE permette di rimuovere i nodi che soddisfano determinati requisiti. Il comando permette solamente di rimuovere i nodi che non hanno relazioni. Se si vuole rimuovere anche le relazioni in cui sono coinvolti, bisogna aggiungere la clausola DETACH. Ad esempio se vogliamo rimuovere il film Captain Marvel, e tutte le relazioni ad esso collegate, bisgnerà selezionarlo mediante un MATCH e successivamente cancellare le relazioni e il nodo stesso. La query per eseguire questa operazione sarà:

MATCH (a:Movie {title:'Captain Marvel'}) DETACH DELETE a 

MERGE

Per evitare i duplicati si può usare l’istruzione MERGE. Questo comando prima cerca il nodo che soddisfa i requisiti inseriti. Mediante la clausola ON CREATE si definiscono le proprietà e il loro corrispettivi valori che dovranno essere impostati. Diversamente, con la clausola ON MATCH si possono impostare i valori di altre proprietà. 

Supponiamo, ad esempio, di voler creare il nodo dell’attore Brie Larson. Nel caso non sia ancora presente nel grafo, imposteremo la sua data di nascita uguale a 1989. Diversamente,  incrementeremo il valore della proprietà stars. Poiché la proprietà stars potrebbe non essere ancora stata settata, possiamo utilizzare, come riportato nella query seguente, la funzione COALESCE che restituisce il primo valore non nullo di un insieme di valori. 

MERGE (a:Person {name:'Brie Larson'}) 
ON CREATE SET a.born = 1989 
ON MATCH SET a.stars = COALESCE(a.stars, 0) + 1
RETURN a 

L’istruzione MERGE può essere anche utilizzata per creare le relazioni tra due nodi esistente ed evitare che si creino duplicati. Supponiamo di voler inserire il ruolo di Brie Larson nel film Captain Marvel. Mediante l’istruzione MATCH selezioneremo i nodi Person e Movie corrispondenti, mentre con la clausola MERGE creeremo la relazione ACTED_IN se e solo questa non è già stata definita. La query finale sarà:

MATCH (a:Person {name:'Brie Larson'}), (b:Movie {title:'Captain Marvel'})
MERGE (a)-[r:ACTED_IN]->(b) SET r.roles = ['Carol Danvers']
RETURN a,r,b 

Casi d’uso avanzati

Mediante l’uso di Cypher è possibile creare query anche molto complesse. Il linguaggio di interrogazione, ad esempio, permette anche l’uso delle list comprehension. Questo può risultare utile per estendere i valori di una lista dei valori attuali di una proprietà. 

Ad esempio, se volessimo inserire nella lista dei ruoli di un attore un nuovo valore dovremmo garantire che questo non sia già presente. L’uso delle comprehension list risolve questo problema. Un esempio di query è riportata di seguito.

MATCH (a:Person {name:'Brie Larson'}), (b:Movie {title:'Captain Marvel'})
MERGE (a)-[r:ACTED_IN]->(b) 
SET r.roles = [x in r.roles WHERE x <> 'Captain Marvel'] + ['Captain Marvel']
RETURN a,r,b 

Una rappresentazione a grafo dei dati permette anche di fare delle analisi complesse che nei database relazionali sarebbero molto difficili o impossibili da effettuare. Un esempio è di trovare le persone che anche indirettamente hanno un legame con una persona specifica. In sociologia questo tipo di analisi è molto utile ed è alla base delle valutazione dei gradi separazione.  Nel nostro esempio vogliamo conoscere tutte le persone che hanno un legame con Kevin Bacon fino al quarto grado. Impostando una condizione sul numero di relazioni che possono essere percorse, il risultato verrà mostrato in pochissimi secondi.

MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..4]-(hollywood)
RETURN DISTINCT hollywood 

Neo4j offre però anche alcuni algoritmi relativi alla teoria dei grafi. Ad esempio è possibile individuare il percorso minimo tra due nodi. Supponiamo di voler visualizzare come Kevin Bacon potrebbe conoscere direttamente o indirettamente Al Pacino. Mediante la funzione shortestPath demandiamo a Neo4j il calcolo, migliorando le performance del nostro applicativo. La query del nostro esempio sarà la seguente.

MATCH p=shortestPath(
              (bacon:Person {name:"Kevin Bacon"})-[*]-(a:Person {name:'Al Pacino'})
            )
RETURN p 

Il risultato che otterremo sarà il seguente.

Un altro uso avanzato di Cypher è la raccomandazione di potenziali nodi di interesse. Questo aspetto è molto utile nelle relazioni sociali in generale, ma anche in quelle lavorative. Un approccio di raccomandazione di base è quello di trovare le connessioni a partire dagli immediati vicini e che siano esse stesse ben connesse. 

Ad esempio potremmo individuare potenziali attori che potrebbero lavorare bene con Tom Hanks in quanto hanno lavorato già con dei suo collaboratori. Possiamo dare anche un peso in base al numero di path che sono stati ritornati per quell’attore. La query risulterebbe la seguente.

MATCH (a:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
      (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors)
WHERE NOT (a)-[:ACTED_IN]->()<-[:ACTED_IN]-(cocoActors) AND a <> cocoActors
RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC 

In modo analogo possiamo individuare la persona migliore che potrebbe far conoscere Tom Cruise a Tom Hanks in quanto ha lavorato con entrambi. Una possibile soluzione al problema potrebbe essere la seguente.

MATCH (a:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
(coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(other:Person {name:'Tom Cruise'})
RETURN a, m, coActors, m2, other 

Questi sono solo alcuni delle possibili applicazioni di Neo4j. Potete trovare altri casi di studio e altra documentazioni sul sito ufficiale.

Letture consigliate

More To Explore

Intelligenza artificiale

Gradio: applicazioni web in python per AI [parte2]

Gradio è una libraria python che ci permette di creare applicazioni web in modo veloce e intuitivo per i nostri modelli di machine learning e AI. Le nostre applicazioni richiedono sempre un’interazione con l’utente e una personalizzazione del layout. Scopriamo, mediante degli esempi, come migliorare le nostre applicazioni.

Intelligenza artificiale

Gradio: applicazioni web in python per AI [parte1]

Scrivere applicazioni web per i nostri modelli di machine learning e/o di intelligenza artificiale può richiedere molto tempo e competenze che non sono in nostro possesso. Per snellire e velocizzare questo compito ci viene in aiuto Gradio, una libreria Python pensata per creare applicazioni web con poche righe di codice. Scopriamo le sue funzionalità base con alcuni esempi.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Design with MongoDB

Design with MongoDB!!!

Buy the new book that will help you to use MongoDB correctly for your applications. Available now on Amazon!