Graph database lưu dữ liệu dưới dạng nodes (đỉnh) và relationships (cạnh). Khi quan hệ giữa các entities là quan trọng nhất — graph DB vượt trội hơn SQL rất nhiều.
Graph vs Relational#
Ví dụ: tìm bạn bè của bạn bè (friend-of-friend):
-- SQL — 5 JOIN
SELECT DISTINCT fof.*
FROM users u
JOIN friendships f1 ON u.id = f1.user_id
JOIN users f1u ON f1.friend_id = f1u.id
JOIN friendships f2 ON f1u.id = f2.user_id
JOIN users fof ON f2.friend_id = fof.id
WHERE u.id = 1 AND f2.friend_id != u.id;sql// Neo4j — 1 pattern
MATCH (u:User {id: 1})-[:FRIENDS_WITH*2]->(fof:User)
WHERE NOT (u)-[:FRIENDS_WITH]->(fof)
RETURN DISTINCT fofcypherGraph database: relationships first-class citizens. Trong SQL, relationship là foreign key + JOIN. Trong graph, relationship là thực thể riêng, có thể có properties.
Cypher Query Language#
Nodes & Relationships#
// Create node
CREATE (u:User {id: 1, name: 'Alice', age: 30})
// Create relationship
CREATE (u1:User {name: 'Alice'})-[:FRIENDS_WITH {since: 2024}]->(u2:User {name: 'Bob'})cypherMatch — Query#
// Find user
MATCH (u:User {name: 'Alice'})
RETURN u
// Friends of Alice
MATCH (alice:User {name: 'Alice'})-[:FRIENDS_WITH]->(friend:User)
RETURN friend.name, friend.age
// Friend-of-friend (2 levels deep)
MATCH (alice:User {name: 'Alice'})-[:FRIENDS_WITH*2]->(fof:User)
RETURN DISTINCT fof.name
// Variable length paths
MATCH path = (alice:User {name: 'Alice'})-[:FRIENDS_WITH*1..3]->(other)
RETURN pathcypherCreate & Update#
// Create with properties
CREATE (p:Post {
id: 101,
title: 'Graph DB Guide',
content: 'Deep dive into Neo4j...',
createdAt: datetime()
})
// Create relationship with properties
MATCH (u:User {id: 1}), (p:Post {id: 101})
CREATE (u)-[:POSTED {at: datetime()}]->(p)
// Like a post
MATCH (u:User {id: 2}), (p:Post {id: 101})
CREATE (u)-[:LIKED {at: datetime()}]->(p)
// Update
MATCH (u:User {name: 'Alice'})
SET u.age = 31cypherDelete#
// Delete relationship
MATCH (u:User {id: 1})-[r:LIKED]->()
DELETE r
// Delete node and relationships
MATCH (u:User {id: 1})
DETACH DELETE ucypherAggregation#
// Count friends per user
MATCH (u:User)-[:FRIENDS_WITH]->(friend)
RETURN u.name, COUNT(friend) AS friendCount
ORDER BY friendCount DESC
// Average age of friends
MATCH (u:User {name: 'Alice'})-[:FRIENDS_WITH]->(friend)
RETURN AVG(friend.age) AS avgFriendAge
// Most liked posts
MATCH (p:Post)<-[:LIKED]-()
RETURN p.title, COUNT(*) AS likes
ORDER BY likes DESC
LIMIT 10cypherRecommendation System#
// "Users who liked this post also liked..."
MATCH (target:Post {id: 101})<-[:LIKED]-(user:User)
MATCH (user)-[:LIKED]->(other:Post)
WHERE other.id != 101
RETURN other.title, COUNT(*) AS score
ORDER BY score DESC
LIMIT 5
// Product recommendation (collaborative filtering)
MATCH (customer:Customer {id: 1})-[:PURCHASED]->(:Product)<-[:PURCHASED]-(other:Customer)
MATCH (other)-[:PURCHASED]->(recommendation:Product)
WHERE NOT (customer)-[:PURCHASED]->(recommendation)
RETURN recommendation.name, COUNT(*) AS relevance
ORDER BY relevance DESC
LIMIT 10cypherSocial Graph#
// Mutual friends
MATCH (u:User {id: 1})-[:FRIENDS_WITH]->(mutual:User)<-[:FRIENDS_WITH]-(u2:User {id: 2})
RETURN mutual.name
// Suggested friends (friends of friends, not already friends)
MATCH (u:User {id: 1})-[:FRIENDS_WITH*2]->(suggestion:User)
WHERE NOT EXISTS((u)-[:FRIENDS_WITH]->(suggestion))
AND u.id <> suggestion.id
RETURN suggestion.name, COUNT(*) AS commonFriends
ORDER BY commonFriends DESC
LIMIT 10
// Shortest path between two users
MATCH p = shortestPath(
(alice:User {name: 'Alice'})-[:FRIENDS_WITH*]-(bob:User {name: 'Bob'})
)
RETURN [node IN nodes(p) | node.name] AS pathcypherNode.js với Neo4j#
import neo4j from 'neo4j-driver';
const driver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
);
const session = driver.session();
// Create
async function createUser(name: string, email: string) {
const result = await session.run(
'CREATE (u:User {id: randomUUID(), name: $name, email: $email, createdAt: datetime()}) RETURN u',
{ name, email }
);
return result.records[0].get('u').properties;
}
// Query friends
async function getFriends(userId: string) {
const result = await session.run(
`MATCH (u:User {id: $userId})-[:FRIENDS_WITH]->(friend:User)
RETURN friend.name AS name, friend.email AS email
ORDER BY friend.name`,
{ userId }
);
return result.records.map(r => ({
name: r.get('name'),
email: r.get('email'),
}));
}
// Shortest path
async function findConnection(user1Id: string, user2Id: string) {
const result = await session.run(
`MATCH p = shortestPath(
(u1:User {id: $user1Id})-[:FRIENDS_WITH*]-(u2:User {id: $user2Id})
)
RETURN [n IN nodes(p) | n.name] AS path,
length(p) AS degrees`,
{ user1Id, user2Id }
);
if (result.records.length === 0) return null;
return {
path: result.records[0].get('path'),
degrees: result.records[0].get('degrees'),
};
}
// Cleanup
await session.close();
await driver.close();typescriptUse Cases#
| Use Case | SQL | Graph |
|---|---|---|
| Friend-of-friend | Nhiều JOIN, chậm | 1 hop, nhanh |
| Recommendation | Khó modelling | Tự nhiên |
| Fraud detection | Pattern detection phức tạp | Pattern matching dễ |
| Network/IT | Nhiều JOIN | Graph traversal |
| Knowledge graph | Không phù hợp | Rất phù hợp |
Docker#
services:
neo4j:
image: neo4j:5-community
ports:
- "7474:7474" # Browser UI
- "7687:7687" # Bolt protocol
environment:
- NEO4J_AUTH=neo4j/password
- NEO4J_PLUGINS=["apoc"]
volumes:
- neo4j_data:/data
volumes:
neo4j_data:yamlPerformance#
// Index cho frequent lookups
CREATE INDEX user_id IF NOT EXISTS FOR (u:User) ON (u.id)
CREATE INDEX post_id IF NOT EXISTS FOR (p:Post) ON (p.id)
// Composite index
CREATE INDEX user_name_email IF NOT EXISTS FOR (u:User) ON (u.name, u.email)
// PROFILE — xem query plan
PROFILE
MATCH (u:User {id: 1})-[:FRIENDS_WITH]->(friend)
RETURN friend.namecypherKết Luận#
Graph database chiến thắng khi:
- Dữ liệu có nhiều quan hệ phức tạp
- Cần traversal nhiều levels (friend-of-friend)
- Recommendation, fraud detection
- Knowledge graph, network analysis
Không dùng graph cho: CRUD đơn giản, báo cáo tổng hợp, dữ liệu dạng bảng — SQL vẫn tốt hơn.
Neo4j + Cypher là lựa chọn tốt nhất cho graph database. Bắt đầu với Docker, học Cypher cơ bản, và dùng khi có vấn đề về connection.