Neo4j & Graph Databases — Cypher

Neo4j ও graph database — Cypher: যেখানে relationship-ই প্রথম শ্রেণির নাগরিক

Read: ~45 min Advanced 12 practice problems Cypher patterns

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.

মূল ধারণা: SQL-এ row-গুলো একটি table-এ থাকে; এদের মধ্যে সম্পর্ক বের করতে query চালানোর সময় 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.
চারটি জিনিস মনে রাখুন — Node (entity, যেমন Person), Relationship (connection, যেমন :KNOWS), Property (key-value, যেমন name: 'Arif') এবং Label (node-এর ধরন, যেমন :Person)। SQL-এর table, foreign key, column-এর তুলনায় এই model অনেক বেশি স্বাভাবিক, যখন আপনি real-world জিনিসপত্রের mutual সম্পর্ক নিয়ে কাজ করছেন।
Arif :Person Mim :Person Tanvir :Person Grameenphone :Company :KNOWS {since: 2019} :KNOWS {since: 2021} :WORKS_AT :WORKS_AT {role: 'SDE'} Figure 47.1 — Three :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 caseWhy graph winsProduction 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.
একটি বাস্তব উদাহরণ: LinkedIn-এ আপনি যখন কারো profile দেখেন, পাশে লেখা থাকে "2nd connection — 3 mutual connections"। এই গণনা SQL-এ করা গেলেও — তিন level recursive CTE লাগবে এবং লাখ-লাখ row scan করতে হবে। Neo4j-এ এটি পদের পর পদ pointer follow করার মতো — মিলিসেকেন্ডে শেষ। তাই LinkedIn তাদের social graph-এর জন্য ইতিহাসগতভাবে graph database ব্যবহার করেছে।

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 variable a.
  • (a:Person) — a :Person node bound to a.
  • (a {name: 'Arif'}) — a node whose name is 'Arif'.
  • -[:KNOWS]-> — a directed relationship of type :KNOWS.
  • (a)-[:KNOWS]->(b) — "a KNOWS b".
Cypher-এ pattern একটি ASCII ছবি — (a)-[:KNOWS]->(b) পড়ুন "a KNOWS b" হিসেবে। বৃত্তের ভেতরে node, square bracket-এর ভেতরে relationship। তীর-চিহ্ন দিক বোঝায়। এই ছোট grammar দিয়েই বিশাল-বিশাল query লেখা যায়।
create_graph.cypher
// 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.

basic_match.cypher
// 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

  • CREATE always 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's LEFT JOIN: try to match a pattern; if it doesn't exist, return NULL.
merge_optional.cypher
// 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;
Common mistake Using 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।
variable_length.cypher
// 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).

যে property দিয়ে আপনি প্রায়ই খোঁজেন (যেমন email, username) — তার ওপর অবশ্যই index তৈরি করুন। Neo4j-তে index ছাড়া বড় graph-এ MATCH ভয়াবহ ধীর হয়। SQL-এর মতোই — indexing এখানেও গুরুত্বপূর্ণ।
indexes.cypher
// 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:

apoc_examples.cypher
// 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-এ কয়েক লাইনে শেষ। SQL-এ একই কাজ করতে আপনাকে recursive CTE বা multiple self-join লিখতে হবে — এবং প্রতিটি hop-এর জন্য আলাদা JOIN। দূরত্ব বাড়লে SQL version আরো জটিল হয়, কিন্তু Cypher একই রকম সংক্ষিপ্ত থাকে।

✅ Cypher — 4 lines

fof.cypher
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

fof.sql
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.

When to use which? Use a graph database when relationships are queried as much as the entities. If your queries are mostly "give me all rows where X = Y", stick with PostgreSQL — graphs add operational complexity. Many real systems use both: Postgres for transactions, Neo4j for the recommendation/social/fraud layer.

8. Glossary (শব্দকোষ)

TermMeaningবাংলায়
NodeAn entity (Person, Movie). Drawn as a circle. May have many labels and properties.graph-এর একটি entity, যেমন একজন মানুষ বা একটি কোম্পানি।
RelationshipTyped, directed edge between two nodes. May carry properties.দুই node-এর মধ্যে directed connection, যার নিজস্ব type ও property থাকতে পারে।
LabelA "kind-of" tag on a node, like :Person.node-এর শ্রেণীবিভাগ, যেমন :Person।
CypherNeo4j'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।
shortestPathBuilt-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.

আগে নিজে চেষ্টা করুন; তারপর উত্তর মিলিয়ে নিন। Cypher-এ সহজেই কাগজে এঁকে pattern বানিয়ে নেওয়া যায় — সেটিই সবচেয়ে কার্যকরী শেখার উপায়।
  1. Write a Cypher query to return the name and city of every :Person node, sorted by name.
    প্রতিটি :Person node-এর নাম ও শহর name অনুযায়ী sorted দেখান।
    ✨ Show Answer (উত্তর দেখুন)
    ans1.cypher
    MATCH (p:Person)
    RETURN p.name AS name, p.city AS city
    ORDER BY name;
  2. Find the direct friends (1 hop, outgoing :KNOWS) of 'Mim'.
    'Mim'-এর সরাসরি বন্ধুদের তালিকা।
    ✨ Show Answer
    ans2.cypher
    MATCH (:Person {name:'Mim'})-[:KNOWS]->(f:Person)
    RETURN f.name AS friend
    ORDER BY friend;
  3. For every person, count how many outgoing :KNOWS relationships they have. Include people with 0.
    প্রতিটি Person-এর outgoing :KNOWS সংখ্যা গণনা করুন (০-ও দেখান)।
    ✨ Show Answer
    ans3.cypher
    MATCH (p:Person)
    OPTIONAL MATCH (p)-[:KNOWS]->(f:Person)
    RETURN p.name AS person, count(f) AS friends_out
    ORDER BY friends_out DESC;
  4. Find every person reachable from Arif within exactly 2 KNOWS hops.
    Arif থেকে ঠিক 2 hop দূরে আছেন এমন person খুঁজে বের করুন।
    ✨ Show Answer
    ans4.cypher
    MATCH (:Person {name:'Arif'})-[:KNOWS*2]->(p:Person)
    RETURN DISTINCT p.name AS two_hops_away
    ORDER BY two_hops_away;
  5. Use shortestPath to find the shortest :KNOWS chain from Arif to Rakib.
    Arif থেকে Rakib পর্যন্ত সবচেয়ে ছোট :KNOWS path বের করুন।
    ✨ Show Answer
    ans5.cypher
    MATCH path = shortestPath(
        (:Person {name:'Arif'})-[:KNOWS*]-(:Person {name:'Rakib'})
    )
    RETURN [n IN nodes(path) | n.name] AS chain,
           length(path) AS hops;
  6. Suggest friends for Arif: people he doesn't know, but his friends do, ranked by mutual friend count.
    Arif-কে friend suggestion দিন — যাদের সে চেনে না কিন্তু তার বন্ধুরা চেনে।
    ✨ Show Answer
    ans6.cypher
    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;
  7. Find every pair of :Person nodes who work at the same company.
    কোন দুজন একই কোম্পানিতে কাজ করেন — তাদের জোড়া দেখান।
    ✨ Show Answer
    ans7.cypher
    MATCH (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) দিয়ে প্রতিটি জোড়া কেবল একবার দেখাচ্ছি।

  8. Use MERGE to add a new :KNOWS relationship from Tanvir to Nadia, but do not duplicate if it already exists.
    Tanvir থেকে Nadia-তে :KNOWS যোগ করুন, কিন্তু আগে থাকলে duplicate করবেন না।
    ✨ Show Answer
    ans8.cypher
    MATCH (t:Person {name:'Tanvir'}), (n:Person {name:'Nadia'})
    MERGE (t)-[r:KNOWS]->(n)
    ON CREATE SET r.since = 2024
    RETURN r;
  9. Create a uniqueness constraint on Person.email.
    Person.email-এর উপর uniqueness constraint তৈরি করুন।
    ✨ Show Answer
    ans9.cypher
    CREATE CONSTRAINT person_email_unique
    FOR (p:Person) REQUIRE p.email IS UNIQUE;
  10. 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-এর তুলনায় অনেক দ্রুত।

  11. 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 use shortestPath.

    উপর-সীমা না থাকলে engine অগণিত path generate করতে পারে, dense graph-এ এটি memory শেষ করে দেবে। সবসময় *1..4-এর মতো explicit সীমা দিন।

  12. 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.

Graph database-এ relationship-ই star — node ও relationship দুটোই নিজস্ব property বহন করে। Cypher আপনাকে একটি pattern আঁকতে দেয়, engine সেটি graph-এ খুঁজে দেয়। ২-৩ hop traversal, recommendation, fraud ring detection বা permission tree — এই ধরনের কাজে Cypher SQL-এর চেয়ে অনেক বেশি স্বচ্ছ ও দ্রুত। তবে সাধারণ CRUD-এর জন্য PostgreSQL/MySQL-ই থেকে যাবে।

Next Module → Elasticsearch & Search Engines — inverted index, Query DSL, Bangla analyzers, BM25 vs vector retrieval.