Neo4j, Graph Database Hakkında

Mehmet Kürşad Yuca
TurkNet Technology
Published in
6 min readDec 4, 2023

Merhaba, bu yazımda sizlerle neo4j tanımından başlayarak, graph database avantajlarından, basit sorgulardan indexlemeye kadar konuya değineceğiz.

Neo4j Nedir?

Neo4j Java ve Scala ile yazılmış olan bir graph databasedir. Cypher adında bir sorgulama dili vardır. Neo4j açık kaynaklı, yüksek performanslı ve ölçeklenebilirdir. NASA, eBay, cisco gibi kuruluşlar tarafından kullanılmaktadır.

Graph Database Nedir?

İlişkisel veritabanlarından farklı olarak veri arasındaki ilişkileri vurgulamak için graph teorisine kullanan, node (düğüm) ve relationship (ilişkiler) olmak üzere iki temel unsurdan oluşan bir databasedir. Oracle NoSQL Database, OrientDB, HypherGraphDB, GraphBase, InfiniteGraph ve AllegroGraph gibi başka popüler graph databaseler de vardır.

Neden Graph Database Kullanmalıyız?

İlişkisel veritabanları daha çok bir defter gibidir. Satır ve sütünlardan oluşur. Verileri ilişkilendirmek için foreign keyler kullanırız ve bu da bize sorgularımızda join kullanmamıza, sonuç olarak veri fazlalığına ve kompleksliğine sebebiyet verir. İlişkilerin çok daha önemli olduğu bu gibi durumlarda graph databaseler daha önemli hale gelir.

SELECT p.ProductName
FROM Product AS p
JOIN ProductCategory pc ON (p.CategoryID = pc.CategoryID AND pc.CategoryName = "Dairy Products")

JOIN ProductCategory pc1 ON (p.CategoryID = pc1.CategoryID)
JOIN ProductCategory pc2 ON (pc1.ParentID = pc2.CategoryID AND pc2.CategoryName = "Dairy Products")

JOIN ProductCategory pc3 ON (p.CategoryID = pc3.CategoryID)
JOIN ProductCategory pc4 ON (pc3.ParentID = pc4.CategoryID)
JOIN ProductCategory pc5 ON (pc4.ParentID = pc5.CategoryID AND pc5.CategoryName = "Dairy Products");

Yukarıda ki gibi kompleks bir sorguyu basitçe yazabiliriz.

MATCH (p:Product)-[:CATEGORY]->(l:ProductCategory)-[:PARENT*0..]->(:ProductCategory {name:"Dairy Products"})
RETURN p.name

Birkaç örnekle devam edelim, aşağıda gördüğünüz gibi beyaz tahtada modellediğimiz bir ilişkiyi graph database’e olduğu gibi aktarabiliriz.

Whiteboard
Graph

Başka bir örnek;

Sally ve John adında iki kişi arkadaştırlar. Her ikisi de Graph Database kitabını okudu.

Bu senaryo için ilk öncelikle nodelarımızı (düğümlerimizi) belirleyelim.

Nodes

Nodelarımızı belirlediğimize göre artık onları tanımlayalım. (label)

Labels

Peki buradaki ilişkileri nasıl tanımlayacağız dediğinizi duyar gibiyim. İşte ilişkiler.

Relations

İlişkileri de tanımladıktan sonra burada Sally kaç yaşında, Graph database kitabının ortalama puanı kaç, Sally mi John mu daha büyük sorularına cevap verebilmek için property tanımlamalarını yapalım.

Property

Artık sorularımızın tamamına cevap verebilir hale geldi. Gördüğünüz gibi graph database esnekliği ve basitliği, kullanıcıların veri yapısını kolayca görüntüleyebilmesi, değişen ihtiyaçlara göre güncelleyebilmesine olanak tanır.

Relational Database’den Graph Database’e Geçiş

İlişkisel veri tabanlarında bir tabloyu başka bir tabloya bağlayabilmek için ancak join yapmamız gerektiğini unutmayalım. Birbirine bu şekilde bağlanan tablolar milyonlarca kayıt yer alan sorgulamalarda performans sorunu haline gelecektir. Aşağıdaki örnekte Alice’in hangi departmanlarda çalıştığını bulmak için örnek bir şema verilmiştir.

Relation Database Örneği

Peki bunu graph database üzerinde nasıl yaparız?

Öncelikle sizlerle birkaç ipucu paylaşayım;


+----------------------------------+------------------------+
| İlişkisel Veritabanı | Graph |
+----------------------------------+------------------------+
| Tablo | Node Label |
| Satir | Node |
| Kolon | Node Property |
| Foreign Keys | Relationship |
+----------------------------------+------------------------+
Graph Model Örneği

Gördüğünüz gibi çok basit bir şekilde tanımlamalarını yapabildik. İlişkisel veritabanında ki tablolar node label’a, her bir kayıt graph model de bir node’a, bir kayıtın kolonları ise o node’a ait propertylere dönüştü.

Şimdi graph database sorgulama dili olan cypher’a bakalım.

Cypher vs SQL

SQL’e aşina olan herkes Cypher’ı kolaylıkla anlayacaktır. Detaylara girmeden önce her ikisinin de declarative query dilleri olduğunu belirtelim. Cypher özellikle graphlar için tasarlanmıştır. Her ikisininde clauseslar, keywordler, expressionlar, operatörler ve functionlar içerdiğini söyleyebiliriz. SQL’den farklı olarak Cypher graph pattern’i merkezine almıştır. Cypher ile de kompleks sorgular yazabiliriz. Cypher karışık okuma ve yazma operasyonları tek bir state üzerinden gerçekleştirir. Hatta Cypher ile büyük dataları, CSV bile yükleyerek işlem yapabilirsiniz.

Herkesin bildiği northwind model üzerinden örneklerle ilerleyelim.

Northwind Relational Database Diyagramı
Northwind Graph Modeli

Sorgularımızı basitten gelişmişe doğru ilerletelim.

Burada ‘Match’ ifadesiyle label’ı Product olan kayıtları getiriyoruz.

SELECT p.*
FROM products as p;
MATCH (p:Product)
RETURN p;

Bu örnekte ise SQL’e çok benzediğini bir kez daha görüyoruz. Unutulmamalıdır ki Cypher’da büyük küçük harf duyarlılığı vardır.

SELECT p.ProductName, p.UnitPrice
FROM products as p
ORDER BY p.UnitPrice DESC
LIMIT 10;
MATCH (p:Product)
RETURN p.productName, p.unitPrice
ORDER BY p.unitPrice DESC
LIMIT 10;

Cypher ile index oluşturalım.

CREATE INDEX Product_productName IF NOT EXISTS FOR (p:Product) ON p.productName;
CREATE INDEX Product_unitPrice IF NOT EXISTS FOR (p:Product) ON p.unitPrice;

LIKE operatörünü kullanalım. Cypher’da 3 farklı şekildedir. ‘START WITH’, ‘CONTAINS’ ve ‘END WITH’ olmak üzere.

SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName LIKE 'C%' AND p.UnitPrice > 100;
MATCH (p:Product)
WHERE p.productName STARTS WITH 'C' AND p.unitPrice > 100
RETURN p.productName, p.unitPrice;

Başka bir örnek ile devam edelim. ‘Chocolade’ adında bir Product’ı sipariş veren müşterinin CompanyName’ini görmek istersek;

SELECT DISTINCT c.CompanyName
FROM customers AS c
JOIN orders AS o ON (c.CustomerID = o.CustomerID)
JOIN order_details AS od ON (o.OrderID = od.OrderID)
JOIN products AS p ON (od.ProductID = p.ProductID)
WHERE p.ProductName = 'Chocolade';
MATCH (p:Product {productName:'Chocolade'})<-[:ORDERS]-(:Order)<-[:PURCHASED]-(c:Customer)
RETURN DISTINCT c.companyName;

Patternler

Yukarda da bahsettiğim gibi graph database’in gücü nodelardan ve relationshiplerden gelmektedir. Ancak graph database’i güçlü yapan nodeları ve relationshipleri çözümleme yeteneğidir.

Örneğin bir kişi bir şehirde yaşar. Bir şehir ise bir ülkenin parçasıdır.

(:Person) -[:LIVES_IN]-> (:City) -[:PART_OF]-> (:Country)

Node Tanımlamaları

Nodelar parantezlerin arasına alarak yazılmaktadır. Aşağıda sizler için birkaç örnek derledim.

()
(matrix)
(:Movie)
(matrix:Movie)
(matrix:Movie {title: 'The Matrix'})
(matrix:Movie {title: 'The Matrix', released: 1997})

Relationship Tanımlamaları

İlişkiler ‘--‘şeklinde tanımlanır. Ancak ilişkilerin yönünü belirlemek gereklidir.

-->
-[role]->
-[:ACTED_IN]->
-[role:ACTED_IN]->
-[role:ACTED_IN {roles: ['Neo']}]->

Variable Tanımlamaları

Modülerliğin artmasına ve tekrarı azaltmak için Cypher değişkenlerin atanmasına izin verir.

acted_in = (:Person)-[:ACTED_IN]->(:Movie)

Patternlerin Pratikte Uygulanması

Basit bir ‘CREATE’ işlemi yapalım. Bizim için Movie labelinde bir node’u title’ı ‘Matrix’ released’ı ‘1997’ propertyleri ile üretsin.

CREATE (:Movie {title: 'The Matrix', released: 1997})

Sorgularımızı biraz daha kompleksleştirelim. Bizim için ‘Person’ labelinde bir node oluştursun. Bu node ‘name’ ve ‘born’ propertylerini alsın. ‘Movie’ label’ı ile ‘ACTED_IN’ relation’ı içerisinde olsun.

CREATE (a:Person {name: 'Tom Hanks', born: 1956})-[r:ACTED_IN {roles: ['Forrest']}]->(m:Movie {title: 'Forrest Gump', released: 1994})
CREATE (d:Person {name: 'Robert Zemeckis', born: 1951})-[:DIRECTED]->(m)
RETURN a, d, r, m

Year node’u oluşturalım ve 2 farklı node’dan oluşturduğumuz node’a ilişki tanımlayalım.

CREATE (y:Year {year: 2014})
MERGE (y)<-[:IN_YEAR]-(m10:Month {month: 10})
MERGE (y)<-[:IN_YEAR]-(m11:Month {month: 11})
RETURN y, m10, m11

‘{}’ ile subqueryler oluşturabilirsiniz.

MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE exists((p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'}))
RETURN p, r, friend

WHERE clause’ında ‘EXIST’ kullanımına da izin verilmektedir.

MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE EXISTS {
MATCH (p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'})
}
RETURN p, r, friend

.NET Geliştiriciler için Neo4j

Neo4j ve Cypher’ın çalışma mantığını anladığımıza göre .NET ve Neo4j ile bir proje nasıl geliştirilir, inceleyelim.

Öncelikle Neo4j .NET Driver’ının kurulumunu tamamlamalısınız.

PM> Install-Package Neo4j.Driver-5.15.0
public async Task<Movie> FindByTitle(string title)
{
if (title == "favicon.ico")
return null;

await using var session = _driver.AsyncSession(WithDatabase);

return await session.ExecuteReadAsync(async transaction =>
{
var cursor = await transaction.RunAsync(@"
MATCH (movie:Movie {title:$title})
OPTIONAL MATCH (movie)<-[r]-(person:Person)
RETURN movie.title AS title,
collect({
name:person.name,
job: head(split(toLower(type(r)),'_')),
role: reduce(acc = '', role IN r.roles | acc + CASE WHEN acc='' THEN '' ELSE ', ' END + role)}
) AS cast",
new {title}
);

return await cursor.SingleAsync(record => new Movie(
record["title"].As<string>(),
MapCast(record["cast"].As<List<IDictionary<string, object>>>())
));
});
}

https://github.com/neo4j-examples?q=dotnet&type=all&language=&sort= Adresinden kendi kullandığınız platforma özel örnekleri bulabilirsiniz.

--

--