🗄️ 데이터베이스

Cypher

Cypher Query Language

Neo4j의 그래프 쿼리 언어. ASCII 아트 스타일의 패턴 매칭으로 노드와 관계를 직관적으로 표현하고 탐색합니다.

📖 상세 설명

Cypher는 Neo4j에서 개발한 선언적 그래프 쿼리 언어로, 그래프 데이터를 패턴 매칭으로 조회하고 조작합니다. SQL이 테이블 기반 데이터를 다루는 것처럼, Cypher는 노드(Node)와 관계(Relationship)로 구성된 그래프 데이터를 다룹니다.

(person:User)-[:FOLLOWS]->(friend:User)

"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)
// 사용자 노드 생성 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 + WHERE + RETURN)
// 특정 사용자의 팔로워 조회 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 - 있으면 조회, 없으면 생성
// 사용자가 없으면 생성, 있으면 기존 노드 사용 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;
Python에서 Cypher 사용 (neo4j 드라이버)
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)

💬 현업 대화 예시

백엔드 개발자
"친구의 친구 추천 기능 구현해야 하는데, SQL로는 JOIN이 너무 복잡해져요."
시니어 개발자
"그런 관계 탐색은 Neo4j가 딱이야. Cypher로 MATCH (me)-[:FOLLOWS*2]->(fof) 이렇게 2홉 관계를 한 줄로 표현할 수 있거든. 성능도 수백만 노드에서 ms 단위야."
데이터 분석가
"사기 탐지를 위해 계좌 간 자금 흐름을 추적해야 해요. 5단계까지 연결된 계좌를 찾고 싶은데요."
DBA
"Cypher의 가변 길이 경로 탐색이 핵심이야. (a)-[:TRANSFER*1..5]->(b)로 경로 찾고, shortestPath()로 최단 경로도 바로 알 수 있어. 금융권에서 그래프 DB 많이 쓰는 이유야."
신입 개발자
"MERGE랑 CREATE 차이가 뭐예요? 둘 다 데이터 만드는 거 아닌가요?"
시니어 개발자
"CREATE는 무조건 새로 만들어. 중복 가능. MERGE는 '있으면 가져오고 없으면 만들어'야. ON CREATE, ON MATCH로 각 경우 처리도 가능하고. 대부분 MERGE를 더 많이 써."

⚠️ 주의사항

⚠️ 가변 길이 경로 성능

-[:REL*]->처럼 상한 없는 가변 길이 경로는 성능에 치명적입니다. 반드시 -[:REL*1..5]->처럼 최대 홉 수를 제한하세요. 그래프가 크면 지수적으로 탐색량이 증가합니다.

⚠️ 카테시안 곱 주의

관계 없는 MATCH 절을 여러 개 사용하면 카테시안 곱이 발생합니다. MATCH (a:User), (b:Post)는 모든 User × 모든 Post 조합을 생성해 성능이 급락합니다.

⚠️ 인덱스 필수

MATCH의 시작점이 되는 노드 속성에는 반드시 인덱스를 생성하세요. CREATE INDEX FOR (u:User) ON (u.email). 인덱스 없이 전체 노드 스캔하면 조회 속도가 급격히 느려집니다.

🔗 관련 용어

📚 더 배우기