Neo4j tutorial - podstawy grafowej bazy danych - część 2

W poprzedniej części kursu Neo4j skonfigurowaliśmy środowisko Neo4j do pracy i utworzyliśmy pierwsze obiekty - węzły w bazie danych do których dodaliśmy właściwości. Tę część zaczniemy od utworzenia relacji między nimi.

Pamiętasz, że w relacyjnej bazie danych przechowywalibyśmy klientów jednej tabeli, informacje o zakupionych przez nich produktach (lub po produkty) w drugiej. By uzyskać informacje jakie produkty kupił klient wraz ze szczegółami klienta i szczegółami produktu, użylibyśmy JOIN i wykorzystali klucze w tabelach (np. numer klienta). Im więcej JOIN'ów, im więcej informacji w tabelach - tym więcej czasu potrzebuje relacyjna baza danych na przygotowanie rezultatów zapytania.

W Neo4j wszystkie informacje mamy w jednym worku i tworzymy miedzy nimi relacje. Relacje w każdym momencie możemy utworzyć, zmienić ich właściwości, usunąć je.

Tworzymy relację między klientem a produktem

Język komunikacji z bazą danych Neo4j nazywa się Cypher (tak, z Matrix :-)). Polecenie które utworzy relację miedzy klientem a produktem ma składnię:

MATCH
  (k:Klient),
  (p:Produkt)
WHERE k.numer_klienta = 400230 AND p.numer_produktu = 100
CREATE (k)-[r:KUPIL {data_zakupu: '2021-09-23', cena: 56.90}]->(p)

Pierwsza część polecenia znajduje węzły klient i produkt. Przypisaliśmy im zmienne k (dla klienta) i p (dla produktu). Możesz nadać im inne litery jeśli chcesz. Szukamy konkretnych węzłów więc używamy klauzuli WHERE i wpisujemy jaki numer klienta nas interesuje w węźle "k" i jaki numer produktu w węźle "p". Ostatnia linijka to polecenie utworzenia relacji - tworzymy relację od "k" do "p". Informacje miedzy tymi literami to "r" oznaczająca relację i występująca po tej literze nazwa. W nawiasach "{}" mamy właściwości relacji; datę zakupu i cenę produktu (w momencie kiedy ten produkt był kupiony przez klienta).

Aktualizujemy węzeł - dodanie właściwości lub aktualizacja

Jeśli chcemy dodać nową właściwość do istniejącego węzła, np. dodać adres, wystarczy wykonać polecenie Cypher:

MATCH (k:Klient {numer_klienta: 400230})
SET k.adres = 'Kłodzko, Polna 23'
RETURN k

Jak widzisz, pierwsza linijka polecenia znajduje węzeł który chcemy aktualizować, druga linijka dodaje właściwość "adres" do węzła. Gdyby taka właściwość już istniała, jej wartość zostanie przepisana - możesz więc wykonać to samo polecenie by aktualizować węzeł.

Usuwamy właściwość z węzła

Możesz usunąć jedną lub wiele właściwości ustawiając wartość NULL dla właściwości lub usuwając właściwość poleceniem REMOVE:

//kasowanie używając REMOVE
MATCH (k:Klient {numer_klienta: 400230})
REMOVE k.adres

//kasowanie używając NULL
MATCH (k:Klient {numer_klienta: 400230})
SET n.adres = null

W Neo4j "null" nie oznacza że istnieje właściwość która nie ma wartości. Null w Neo4j oznacza że nie ma tam takiej właściwości. Inaczej niż jest to w relacyjnych bazach danych.

Aktualizujemy relację między węzłami

Poniższe polecenie spowoduje odszukanie relacji KUPIL między klientem o numerze 400230 a produktem o numerze 100 i dodanie właściwości "numer_promocji" do tej relacji. Jeśli taka właściwość już jest przypisana do relacji, zostanie ona zaktualizowana.

MATCH (k:Klient {numer_klienta: 400230})-[r:KUPIL]-(p:Produkt {numer_produktu: 100})
SET r.numer_promocji = 3311

W realnym świecie relacji KUPIL między klientem a produktem będziesz mieć więcej niż jedną - klient najczęściej kupuje wiele produktów. Wyszukując więc właściwą relację do aktualizacji, musiałbyś wskazać np. datę zakupu.

Wysłanie takiego samego zapytania z nową wartością właściwości relacji spowoduje aktualizację wartości. Usunięcie właściwości z relacji możemy wykonać ustawiając wartość NULL dla właściwości lub używając komendy REMOVE:

MATCH (k:Klient {numer_klienta: 400230})-[r:KUPIL]-(p:Produkt {numer_produktu: 100})
SET r.numer_promocji = NULL

MATCH (k:Klient {numer_klienta: 400230})-[r:KUPIL]-(p:Produkt {numer_produktu: 100})
REMOVE r.numer_promocji

Pamiętaj że wykonując operacje na węzłach lub relacjach między nimi należy wskazywać precyzyjnie właściwe węzły lub relacje gdyż ma to wpływ na prędkość wykonania poleceń.

Usuwanie węzłów i relacji między węzłami

Usunięcie konkretnego węzła wykonasz poleceniem:

MATCH (k:Klient { numer_klienta: 400230 })
DELETE k

Jeśli jednak wykonasz takie polecenie dla węzła który ma relacje z innymi węzłami, Neo4j zwróci błąd: "Cannot delete node<0>, because it still has relationships. To delete this node, you must first delete its relationships." Neo4j posiada polecenie DETACH które użyte razem z DELETE skasuje sam węzeł jak i odchodzące od niego relacje:

MATCH (k:Klient { numer_klienta: 400230 })
DETACH DELETE k

Usuwanie konkretnej relacji KUPIL, wskazując na dokładną wartość "daty_zakupu", wykonasz poleceniem:

MATCH (k:Klient {numer_klienta: 400230})-[r:KUPIL { data_zakupu: '2021-09-23' }]-(p:Produkt {numer_produktu: 100})
DELETE r

Listowanie danych

Podczas gdy relacyjne bazy danych używają SQL jako języka zapytań, w Neo4j posługujemy się językiem Cypher. Cypher został zaprojektowany dla Neo4j ale od 2015 jest projektem opensource - OpenCypher.

Wybór danych - wyświetlenie węzłów i ich właściwości

Odpowiednikiem SELECT w SQL jest w Neo4j komenda MATCH. Możesz znaleźć wszystkie etykiety węzłów w bazie danych, wyszukać konkretny węzeł, znaleźć wszystkie węzły z określoną relacją, szukać wzorców węzłów i relacji oraz wiele więcej za pomocą funkcji MATCH. 

Poleceniem "MATCH (n) RETURN (n)" wyświetlasz zawartość bazy w formie grafu. Jeśli chcesz uzyskać rezultat w formie listy, odwołujesz się do właściwości obiektu. Np. by wyświetlić nazwiska klientów:

MATCH (k:Klient)
RETURN k.nazwisko_klienta

lub by otrzymać rezultat w formie tabelarycznej:

MATCH (k:Klient)
RETURN k.nazwisko_klienta,k.numer_klienta,k.adres

Wybór danych - wyświetlenie węzłów i relacji

W relacyjnej bazie danych łączymy informacje z różnych tabel za pomocą JOIN - np:

"SELECT klienci.nr_klienta, produkty.nr_produktu 
FROM kliencji
LEFT JOIN produkty
ON klienci.nr_klienta = produkty.nr_klienta"

W Neo4j przechowujemy relacje razem z węzłami. Wezeł "Klient" jest połączony relacją z węzłem "Produkt". By zatem wyświetlić produkty klienta zapytanie Cypher ma postać:

MATCH (k:Klient {numer_klienta:81048793})-[r:KUPIL]->(p:Produkt) RETURN k, p

W tej składni fragment kodu "-[]-" określa połączenie/relacja z innym węzłem. Jak widzisz wskazujemy tam nazwę relacji z innym węzłem. W naszej bazie do produktu prowadza też relacje z innych węzłów Produkt (wielu klientów mogło kupić ten sam produtu... mogli kupić go kilkukrotnie; za każdym razem powstawała nowa relacja KUPIL z inną wartością właściwości "data_zakupu"). Jeśli więc chcielibyśmy zobaczyć kto jeszcze kupił dany produkt, możemy dodać następny "JOIN" wiodący od innych klinetów do naszego produktu:

MATCH (k:Klient {numer_klienta:11111})-[r:KUPIL]->(p:Produkt{numer_produktu: 22222})<-[r2:KUPIL]-(k2:Klient) RETURN k, p, k2

Nasze zmienne "r" (r1 i r2) umożliwiają nam odwołanie się do konkretnych relacji gdybyśmy chcieli wizualizować dane.

Podstawowe operacje matematyczne na węzłach i relacjach

Zacznijmy od zliczenia ilości węzłów w bazie danych poleceniem COUNT:

MATCH (n)
RETURN count(n)

Zliczmy ilość relacji odchodzących od naszego klienta:

MATCH (k:Klient {numer_klienta: 400230})-[r:KUPIL]-(p)
RETURN count(r)

Zsumujmy kwoty zakupu wszystkich produktów klienta. Każda relacja KUPIL ma właściwość "cena" - zsumuj wartości tych właściwości:

MATCH (k:Klient {numer_klienta: 400230})-[r:KUPIL]-(p)
RETURN sum(r.cena)

To koniec naszego krótkiego kursu podstaw Neo4j. W następnym artykule dowiesz się jak załadować dane do Neo4j. Na naszym blogu szukaj informacji jak zbudować silnik rekomendacji, jak zrobić z Neo4j silnik wyszukiwarki i do czego jeszcze użyć Neo4j w Twojej firmie. Good luck!