Neo4j & Graph Databases — Cypher
Neo4j ও graph database — Cypher: যেখানে relationship-ই প্রথম শ্রেণির নাগরিক
1. The Problem Relational Databases Quietly Hate
Imagine LinkedIn. You want to ask a deceptively simple question: "Find people who are friends of my friends but whom I don't know yet, who work in companies my friends used to work in." In SQL this is a tangled mess of self-joins, recursive CTEs, and pain. In a graph database, it is two lines of Cypher.
A relational database stores facts as rows. Connecting them requires a JOIN at
query time, and the cost grows fast with depth. A graph database stores the connections
themselves on disk — relationships are first-class citizens, with their own type, properties, and direct
pointers in storage. Walking three steps in a social graph is constant work per hop, regardless of how
many billions of users exist.
JOIN করতে হয়। কিন্তু graph database-এ সম্পর্ক নিজেই disk-এ আগে থেকে save করা থাকে —
যেমন "Arif KNOWS Mim", "Mim WORKS_AT Grameenphone"। তাই "বন্ধুর বন্ধু" বা "তিন hop দূরের connection"
খুঁজে বের করা graph database-এ অনেক দ্রুত হয়।
In this module you will learn the graph data model (nodes, relationships, properties, labels), where graphs beat SQL, the Cypher query language, variable-length and shortest-path matching, indexes and constraints, a quick word on APOC, and finally a head-to-head: the same friend-of-friend query in Cypher vs a SQL recursive CTE.
2. The Graph Data Model — Nodes, Relationships, Properties, Labels
The labelled-property graph used by Neo4j has just four building blocks:
- Node — an entity (a Person, a Company, a Movie). Drawn as a circle.
- Relationship — a typed, directed connection between two nodes
(
:KNOWS,:WORKS_AT,:RATED). Drawn as an arrow. - Property — key-value pairs on either nodes or relationships
(
name: 'Arif',since: 2019). - Label — a tag on a node, like a "type" (
:Person,:Company). A node may have several labels.
:KNOWS), Property (key-value, যেমন
name: 'Arif') এবং Label (node-এর ধরন, যেমন :Person)। SQL-এর
table, foreign key, column-এর তুলনায় এই model অনেক বেশি স্বাভাবিক, যখন আপনি real-world জিনিসপত্রের
mutual সম্পর্ক নিয়ে কাজ করছেন।
:Person nodes connected by :KNOWS and a :Company node connected by :WORKS_AT. Properties live on both nodes and relationships.
3. When a Graph Beats SQL — Four Killer Use Cases
Graphs do not replace relational databases. They complement them, in workloads where the answer depends on traversing many connections. Four real-world wins:
| Use case | Why graph wins | Production example |
|---|---|---|
| Social networks | Friend-of-friend, mutual connections, "People You May Know" — all are 2-3 hop traversals. | LinkedIn's "Degrees of Separation" feature, Facebook's social graph. |
| Recommendations | Collaborative filtering — "users who watched X also watched Y" — is two hops in a graph. | Netflix-like systems, e-commerce "frequently bought together". |
| Fraud detection | Detect rings: "card → device → account → IP → another card → another device". Cycle and ring detection are native graph operations. | PayPal, banking AML systems, telco SIM fraud rings. |
| IAM & access control | "Does user U have permission P on resource R?" — walk role / group inheritance trees. SQL needs deep recursion; graph does it in one query. | Google Zanzibar, AWS IAM policy evaluation. |
4. Cypher — Pattern Matching for Graphs
Cypher is Neo4j's query language. The trick is brilliant: you draw an ASCII picture of the pattern you want, and Cypher finds every place that pattern appears in your graph.
(a)— a node, optionally bound to variablea.(a:Person)— a:Personnode bound toa.(a {name: 'Arif'})— a node whosenameis'Arif'.-[:KNOWS]->— a directed relationship of type:KNOWS.(a)-[:KNOWS]->(b)— "aKNOWSb".
(a)-[:KNOWS]->(b) পড়ুন "a KNOWS b" হিসেবে। বৃত্তের
ভেতরে node, square bracket-এর ভেতরে relationship। তীর-চিহ্ন দিক বোঝায়। এই ছোট grammar দিয়েই
বিশাল-বিশাল query লেখা যায়।
// Create a tiny social graph: 5 people, some friendships, two companies
CREATE (arif:Person {name: 'Arif', city: 'Dhaka'}),
(mim:Person {name: 'Mim', city: 'Dhaka'}),
(tanvir:Person {name: 'Tanvir', city: 'Chittagong'}),
(nadia:Person {name: 'Nadia', city: 'Dhaka'}),
(rakib:Person {name: 'Rakib', city: 'Sylhet'}),
(gp:Company {name: 'Grameenphone'}),
(bk:Company {name: 'bKash'}),
(arif)-[:KNOWS {since: 2019}]->(mim),
(mim)-[:KNOWS {since: 2021}]->(tanvir),
(mim)-[:KNOWS {since: 2020}]->(nadia),
(nadia)-[:KNOWS {since: 2022}]->(rakib),
(arif)-[:WORKS_AT {role: 'SDE'}]->(gp),
(mim)-[:WORKS_AT {role: 'PM'}]->(gp),
(tanvir)-[:WORKS_AT {role: 'SDE'}]->(bk),
(nadia)-[:WORKS_AT {role: 'Designer'}]->(bk);
MATCH, WHERE, RETURN — the basic trio
MATCH finds patterns. WHERE filters them. RETURN projects results.
The mental model is identical to SQL's FROM / WHERE / SELECT, but the FROM is now
a picture.
// 1. All people who live in Dhaka
MATCH (p:Person)
WHERE p.city = 'Dhaka'
RETURN p.name AS name ORDER BY name;
// 2. Direct friends of Arif (1 hop)
MATCH (a:Person {name: 'Arif'})-[:KNOWS]->(friend:Person)
RETURN friend.name AS friend;
// 3. Who works with Arif at the same company?
MATCH (a:Person {name: 'Arif'})-[:WORKS_AT]->(c:Company)<-[:WORKS_AT]-(co:Person)
WHERE co.name <> 'Arif'
RETURN c.name AS company, co.name AS coworker;
CREATE, MERGE and OPTIONAL MATCH
CREATEalways inserts. Use it when you know the data is new.MERGE= "MATCH or CREATE" — find the pattern, or create it if it doesn't exist. The graph world's upsert.OPTIONAL MATCH— like SQL'sLEFT JOIN: try to match a pattern; if it doesn't exist, returnNULL.
// MERGE — idempotent. Running twice does NOT create duplicates.
MERGE (a:Person {name: 'Arif'})
MERGE (m:Person {name: 'Mim'})
MERGE (a)-[r:KNOWS]->(m)
ON CREATE SET r.since = 2019
RETURN a, m, r;
// OPTIONAL MATCH — list every person, even those without friends.
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]->(f:Person)
RETURN p.name AS person, count(f) AS friends_out
ORDER BY friends_out DESC;
CREATE in an import script that may be re-run will produce duplicate nodes and
duplicate relationships. Almost always you want MERGE for ingestion, with explicit
ON CREATE SET / ON MATCH SET to control which properties get set when.
5. Variable-Length Paths and Shortest Path
Here is where graphs really earn their keep. The pattern (a)-[:KNOWS*1..3]->(b) means
"a path of between one and three :KNOWS relationships". Try writing that with
SQL JOINs — you would need to write three queries and UNION them, or use a
recursive CTE. In Cypher it is one expression.
[:KNOWS*1..3] = ১ থেকে ৩ hop পর্যন্ত যেকোনো দূরত্ব। [:KNOWS*] = যেকোনো
দৈর্ঘ্যের পথ (সাবধান, বিশাল graph-এ ধীর হতে পারে)। [:KNOWS*2] = ঠিক ২ hop। এই syntax
graph database-এর সবচেয়ে শক্তিশালী feature।
// All people reachable from Arif within 1 to 3 KNOWS hops
MATCH path = (a:Person {name: 'Arif'})-[:KNOWS*1..3]->(b:Person)
RETURN b.name AS reachable,
length(path) AS hops
ORDER BY hops, reachable;
// Shortest path between two people, regardless of length
MATCH path = shortestPath(
(a:Person {name: 'Arif'})-[:KNOWS*]-(b:Person {name: 'Rakib'})
)
RETURN [n IN nodes(path) | n.name] AS chain,
length(path) AS hops;
shortestPath() is built into Cypher and uses a bidirectional BFS internally — it is
efficient even on graphs with hundreds of millions of nodes. allShortestPaths() returns
every path of the minimum length, useful for "how many ways to reach X?".
6. Indexes, Uniqueness Constraints, and APOC
Without indexes, the very first MATCH (p:Person {name: 'Arif'}) has to scan every Person
node. Neo4j supports B-tree indexes on properties, full-text indexes, and uniqueness constraints (which
implicitly create indexes).
email, username) — তার ওপর
অবশ্যই index তৈরি করুন। Neo4j-তে index ছাড়া বড় graph-এ MATCH ভয়াবহ ধীর হয়। SQL-এর মতোই — indexing
এখানেও গুরুত্বপূর্ণ।
// Index on Person.name for fast lookups
CREATE INDEX person_name_idx FOR (p:Person) ON (p.name);
// Uniqueness constraint — also creates an index, and rejects duplicates
CREATE CONSTRAINT person_email_unique
FOR (p:Person) REQUIRE p.email IS UNIQUE;
// Existence constraint — every Person must have a name
CREATE CONSTRAINT person_name_exists
FOR (p:Person) REQUIRE p.name IS NOT NULL;
APOC — the Swiss army knife
APOC ("Awesome Procedures On Cypher") is Neo4j's de-facto standard library — over 450 stored procedures and functions for things vanilla Cypher cannot do: bulk JSON/CSV import, calling REST APIs, advanced graph algorithms, periodic background jobs, dynamic Cypher generation. A taste:
// Load a CSV from a URL into nodes
CALL apoc.load.csv('https://example.com/people.csv') YIELD map
MERGE (p:Person {email: map.email})
SET p.name = map.name, p.city = map.city;
// Periodic batched delete — process 10k rows at a time
CALL apoc.periodic.iterate(
'MATCH (n:OldLog) RETURN n',
'DETACH DELETE n',
{batchSize: 10000, parallel: false}
);
7. Friend-of-Friend — Cypher vs SQL Recursive CTE
Let's settle the debate with the same query in both worlds. The question: "Find people Arif does not yet know, who are friends of his friends, ranked by how many of his friends know them."
✅ Cypher — 4 lines
MATCH (me:Person {name:'Arif'})-[:KNOWS]->(f)-[:KNOWS]->(fof)
WHERE NOT (me)-[:KNOWS]->(fof)
AND me <> fof
RETURN fof.name AS suggestion,
count(DISTINCT f) AS mutual_friends
ORDER BY mutual_friends DESC;
⚠️ SQL — recursive CTE + filters
WITH RECURSIVE friends(person_id, depth) AS (
SELECT friend_id, 1
FROM friendship
WHERE person_id = 1 -- Arif
UNION ALL
SELECT f.friend_id, fr.depth + 1
FROM friendship f
JOIN friends fr ON fr.person_id = f.person_id
WHERE fr.depth < 2
)
SELECT p.name, COUNT(*) AS mutual
FROM friends fr
JOIN person p ON p.id = fr.person_id
WHERE fr.depth = 2
AND fr.person_id <> 1
AND fr.person_id NOT IN (
SELECT friend_id FROM friendship
WHERE person_id = 1)
GROUP BY p.name
ORDER BY mutual DESC;
Both queries return the right answer. But notice — in Cypher, the pattern is the query. In
SQL, the pattern is hidden inside CTEs and subqueries. As traversal depth grows (3 hops, 4 hops, 5
hops), the SQL version balloons; the Cypher version simply changes *1..2 to
*1..5.
8. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Node | An entity (Person, Movie). Drawn as a circle. May have many labels and properties. | graph-এর একটি entity, যেমন একজন মানুষ বা একটি কোম্পানি। |
| Relationship | Typed, directed edge between two nodes. May carry properties. | দুই node-এর মধ্যে directed connection, যার নিজস্ব type ও property থাকতে পারে। |
| Label | A "kind-of" tag on a node, like :Person. | node-এর শ্রেণীবিভাগ, যেমন :Person। |
| Cypher | Neo4j's declarative query language; uses ASCII-art patterns. | Neo4j-এর query ভাষা, যা ASCII pattern দিয়ে graph match করে। |
| MERGE | "MATCH or CREATE" — idempotent insert. | "আছে কিনা দেখো, না থাকলে তৈরি করো" — Cypher-এর upsert। |
| Variable-length path | [:R*1..3] — relationships of varying length matched in one expression. | একই query-তে ১–৩ hop path ম্যাচ করার syntax। |
| shortestPath | Built-in Cypher function returning the shortest path between two nodes. | দুই node-এর মাঝে সবচেয়ে ছোট পথ বের করার built-in function। |
| APOC | "Awesome Procedures On Cypher" — Neo4j's de-facto extension library. | Neo4j-এর জনপ্রিয় extension library, যাতে CSV import, batch job ইত্যাদি প্রচুর procedure আছে। |
9. Practice Problems
All problems use the social graph from §4 (Arif, Mim, Tanvir, Nadia, Rakib + Grameenphone, bKash). For each, write or read a Cypher query. Click Show Answer after attempting.
-
Write a Cypher query to return the name and city of every
:Personnode, sorted by name.প্রতিটি:Personnode-এর নাম ও শহর name অনুযায়ী sorted দেখান।✨ Show Answer (উত্তর দেখুন)
ans1.cypherMATCH (p:Person) RETURN p.name AS name, p.city AS city ORDER BY name; -
Find the direct friends (1 hop, outgoing
:KNOWS) of'Mim'.'Mim'-এর সরাসরি বন্ধুদের তালিকা।✨ Show Answer
ans2.cypherMATCH (:Person {name:'Mim'})-[:KNOWS]->(f:Person) RETURN f.name AS friend ORDER BY friend; -
For every person, count how many outgoing
:KNOWSrelationships they have. Include people with 0.প্রতিটি Person-এর outgoing:KNOWSসংখ্যা গণনা করুন (০-ও দেখান)।✨ Show Answer
ans3.cypherMATCH (p:Person) OPTIONAL MATCH (p)-[:KNOWS]->(f:Person) RETURN p.name AS person, count(f) AS friends_out ORDER BY friends_out DESC; -
Find every person reachable from Arif within exactly 2 KNOWS hops.Arif থেকে ঠিক 2 hop দূরে আছেন এমন person খুঁজে বের করুন।
✨ Show Answer
ans4.cypherMATCH (:Person {name:'Arif'})-[:KNOWS*2]->(p:Person) RETURN DISTINCT p.name AS two_hops_away ORDER BY two_hops_away; -
Use
shortestPathto find the shortest:KNOWSchain from Arif to Rakib.Arif থেকে Rakib পর্যন্ত সবচেয়ে ছোট:KNOWSpath বের করুন।✨ Show Answer
ans5.cypherMATCH path = shortestPath( (:Person {name:'Arif'})-[:KNOWS*]-(:Person {name:'Rakib'}) ) RETURN [n IN nodes(path) | n.name] AS chain, length(path) AS hops; -
Suggest friends for Arif: people he doesn't know, but his friends do, ranked by mutual friend count.Arif-কে friend suggestion দিন — যাদের সে চেনে না কিন্তু তার বন্ধুরা চেনে।
✨ Show Answer
ans6.cypherMATCH (me:Person {name:'Arif'})-[:KNOWS]->(f)-[:KNOWS]->(fof) WHERE NOT (me)-[:KNOWS]->(fof) AND me <> fof RETURN fof.name AS suggestion, count(DISTINCT f) AS mutual_friends ORDER BY mutual_friends DESC; -
Find every pair of
:Personnodes who work at the same company.কোন দুজন একই কোম্পানিতে কাজ করেন — তাদের জোড়া দেখান।✨ Show Answer
ans7.cypherMATCH (a:Person)-[:WORKS_AT]->(c:Company)<-[:WORKS_AT]-(b:Person) WHERE id(a) < id(b) RETURN a.name AS p1, b.name AS p2, c.name AS company;আমরা
id(a) < id(b)দিয়ে প্রতিটি জোড়া কেবল একবার দেখাচ্ছি। -
Use
MERGEto add a new:KNOWSrelationship from Tanvir to Nadia, but do not duplicate if it already exists.Tanvir থেকে Nadia-তে:KNOWSযোগ করুন, কিন্তু আগে থাকলে duplicate করবেন না।✨ Show Answer
ans8.cypherMATCH (t:Person {name:'Tanvir'}), (n:Person {name:'Nadia'}) MERGE (t)-[r:KNOWS]->(n) ON CREATE SET r.since = 2024 RETURN r; -
Create a uniqueness constraint on
Person.email.Person.email-এর উপর uniqueness constraint তৈরি করুন।✨ Show Answer
ans9.cypherCREATE CONSTRAINT person_email_unique FOR (p:Person) REQUIRE p.email IS UNIQUE; -
In one sentence, when does a graph database beat a relational database?এক বাক্যে বলুন — graph database কখন SQL-এর চেয়ে ভালো?
✨ Show Answer
Answer: When the queries you ask depend on traversing many relationships (2+ hops, recommendations, ring detection, permission inheritance), a graph database stores those connections as direct pointers and walks them in constant cost per hop, while SQL would need a JOIN per hop and slow down dramatically with depth.
যখন query-এর উত্তর পেতে অনেক hop traversal দরকার (২+ hop, recommendation, fraud ring, permission inheritance) — তখন graph database সরাসরি pointer follow করে, SQL-এর JOIN-এর তুলনায় অনেক দ্রুত।
-
Why is variable-length matching like
[:KNOWS*]dangerous in production?[:KNOWS*]production-এ কেন বিপজ্জনক?✨ Show Answer
Answer: Without an upper bound, the engine may try to walk paths of arbitrary length, which on a dense graph can produce billions of intermediate paths and exhaust memory. Always pin an explicit upper limit such as
*1..4, or useshortestPath.উপর-সীমা না থাকলে engine অগণিত path generate করতে পারে, dense graph-এ এটি memory শেষ করে দেবে। সবসময়
*1..4-এর মতো explicit সীমা দিন। -
Describe one real fraud-detection pattern that a graph database can match easily.একটি বাস্তব fraud-detection pattern বর্ণনা করুন যেটি graph-এ সহজে match করা যায়।
✨ Show Answer
Answer: A "fraud ring" — multiple seemingly-unrelated accounts that share an underlying identifier such as a device, IP address, or recovery phone number. In Cypher:
MATCH (a1:Account)-[:USES]->(d:Device)<-[:USES]-(a2:Account) WHERE a1 <> a2 RETURN ...— instantly returns every pair of accounts sharing a device. Add more nodes (IP, card BIN, address) to detect bigger rings."Fraud ring" — আপাতদৃষ্টিতে আলাদা একাধিক account যারা একটি device, IP বা phone number শেয়ার করে। graph-এ এক pattern match-ই সবগুলো ring বের করে দেয়।
Summary — Module 47
A graph database stores nodes, typed relationships and properties as first-class
citizens, with direct pointers between connected nodes on disk. Cypher queries draw
ASCII patterns of the shape you want to find — (a)-[:KNOWS]->(b) — and the engine
returns every match. MATCH, WHERE, RETURN, CREATE,
MERGE and OPTIONAL MATCH form the core. Variable-length paths
([:KNOWS*1..3]) and shortestPath turn what is a recursive CTE in SQL into a
single line. Use graphs when relationships are the question — social networks, recommendations, fraud
rings, IAM trees — and stick with relational for everyday CRUD.