Cypher
Cypher Query Language
Neo4j의 그래프 쿼리 언어. ASCII 아트 스타일의 패턴 매칭으로 노드와 관계를 직관적으로 표현하고 탐색합니다.
Cypher Query Language
Neo4j의 그래프 쿼리 언어. ASCII 아트 스타일의 패턴 매칭으로 노드와 관계를 직관적으로 표현하고 탐색합니다.
Cypher는 Neo4j에서 개발한 선언적 그래프 쿼리 언어로, 그래프 데이터를 패턴 매칭으로 조회하고 조작합니다. SQL이 테이블 기반 데이터를 다루는 것처럼, Cypher는 노드(Node)와 관계(Relationship)로 구성된 그래프 데이터를 다룹니다.
"User 라벨의 person 노드가 FOLLOWS 관계로 friend 노드와 연결됨"
(variable:Label {property: value})-[variable:TYPE {property: value}]->--> (방향 있음), -- (방향 무관)p = (a)-[*1..3]->(b) (1~3 홉 경로)MATCH WHERE RETURN CREATE MERGE DELETE SET WITH ORDER BY LIMIT SKIP UNION UNWIND FOREACH OPTIONAL MATCH
// 사용자 노드 생성
CREATE (u:User {
id: 'user-001',
name: '김철수',
email: 'chulsoo@example.com',
created_at: datetime()
})
RETURN u;
// 여러 노드와 관계 한 번에 생성
CREATE (alice:User {name: 'Alice'})
CREATE (bob:User {name: 'Bob'})
CREATE (post:Post {title: 'Hello Graph!', content: '...'})
CREATE (alice)-[:FOLLOWS]->(bob)
CREATE (alice)-[:WROTE {at: datetime()}]->(post)
CREATE (bob)-[:LIKED]->(post)
RETURN alice, bob, post;
// 특정 사용자의 팔로워 조회
MATCH (u:User {name: 'Alice'})<-[:FOLLOWS]-(follower:User)
RETURN follower.name, follower.email;
// 조건을 WHERE 절로 분리
MATCH (u:User)-[:WROTE]->(p:Post)
WHERE u.created_at > datetime('2024-01-01')
AND p.likes > 10
RETURN u.name, p.title, p.likes
ORDER BY p.likes DESC
LIMIT 10;
// 친구의 친구 (2홉 관계)
MATCH (me:User {name: 'Alice'})-[:FOLLOWS*2]->(fof:User)
WHERE me <> fof // 자기 자신 제외
RETURN DISTINCT fof.name;
// 1~3 홉 사이의 연결된 사용자
MATCH path = (a:User {name: 'Alice'})-[:FOLLOWS*1..3]->(b:User)
RETURN b.name, length(path) AS distance
ORDER BY distance;
// 최단 경로 찾기
MATCH (start:User {name: 'Alice'}), (end:User {name: 'Charlie'})
MATCH path = shortestPath((start)-[:FOLLOWS*..10]->(end))
RETURN path, length(path) AS hops;
// 사용자별 팔로워 수
MATCH (u:User)<-[:FOLLOWS]-(follower)
RETURN u.name, count(follower) AS follower_count
ORDER BY follower_count DESC;
// 인기 게시물과 작성자
MATCH (u:User)-[:WROTE]->(p:Post)<-[:LIKED]-(liker)
WITH u, p, count(liker) AS like_count
WHERE like_count >= 5
RETURN u.name AS author, p.title, like_count
ORDER BY like_count DESC;
// COLLECT로 리스트 생성
MATCH (u:User)-[:FOLLOWS]->(friend:User)
RETURN u.name, collect(friend.name) AS friends;
// 사용자가 없으면 생성, 있으면 기존 노드 사용
MERGE (u:User {email: 'new@example.com'})
ON CREATE SET
u.name = 'New User',
u.created_at = datetime()
ON MATCH SET
u.last_login = datetime()
RETURN u;
// 관계 MERGE
MATCH (a:User {name: 'Alice'}), (b:User {name: 'Bob'})
MERGE (a)-[r:FOLLOWS]->(b)
ON CREATE SET r.since = date()
RETURN r;
// 친구가 좋아요한 게시물 추천 (내가 아직 안 본 것)
MATCH (me:User {name: 'Alice'})-[:FOLLOWS]->(friend:User)
MATCH (friend)-[:LIKED]->(post:Post)
WHERE NOT (me)-[:LIKED]->(post)
AND NOT (me)-[:WROTE]->(post)
WITH post, count(friend) AS friend_likes
ORDER BY friend_likes DESC
LIMIT 10
RETURN post.title, friend_likes;
// 공통 관심사 기반 사용자 추천
MATCH (me:User {name: 'Alice'})-[:INTERESTED_IN]->(topic:Topic)
MATCH (other:User)-[:INTERESTED_IN]->(topic)
WHERE me <> other
AND NOT (me)-[:FOLLOWS]->(other)
WITH other, count(topic) AS common_interests
WHERE common_interests >= 3
RETURN other.name, common_interests
ORDER BY common_interests DESC;
from neo4j import GraphDatabase
class Neo4jService:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def get_followers(self, username: str) -> list:
with self.driver.session() as session:
result = session.run("""
MATCH (u:User {name: $name})<-[:FOLLOWS]-(follower:User)
RETURN follower.name AS name, follower.email AS email
""", name=username)
return [dict(record) for record in result]
def create_follow_relationship(self, from_user: str, to_user: str):
with self.driver.session() as session:
session.run("""
MATCH (a:User {name: $from_user})
MATCH (b:User {name: $to_user})
MERGE (a)-[r:FOLLOWS]->(b)
ON CREATE SET r.since = date()
""", from_user=from_user, to_user=to_user)
def recommend_users(self, username: str, limit: int = 10):
with self.driver.session() as session:
result = session.run("""
MATCH (me:User {name: $name})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(fof)
WHERE me <> fof AND NOT (me)-[:FOLLOWS]->(fof)
WITH fof, count(friend) AS mutual_friends
RETURN fof.name AS name, mutual_friends
ORDER BY mutual_friends DESC
LIMIT $limit
""", name=username, limit=limit)
return [dict(record) for record in result]
# 사용
service = Neo4jService("neo4j://localhost:7687", "neo4j", "password")
followers = service.get_followers("Alice")
recommendations = service.recommend_users("Alice", 5)
-[:REL*]->처럼 상한 없는 가변 길이 경로는 성능에 치명적입니다.
반드시 -[:REL*1..5]->처럼 최대 홉 수를 제한하세요. 그래프가 크면 지수적으로 탐색량이 증가합니다.
관계 없는 MATCH 절을 여러 개 사용하면 카테시안 곱이 발생합니다.
MATCH (a:User), (b:Post)는 모든 User × 모든 Post 조합을 생성해 성능이 급락합니다.
MATCH의 시작점이 되는 노드 속성에는 반드시 인덱스를 생성하세요.
CREATE INDEX FOR (u:User) ON (u.email). 인덱스 없이 전체 노드 스캔하면 조회 속도가 급격히 느려집니다.