[neo4j] Cypher 알아보기 (1)

Lempickaa·2025년 9월 8일

graph

목록 보기
1/10

Building Knowledge Graphs - A Practitioner's guide Chapter 3를 참조하여 작성했습니다.

Cypher는 Neo4J에서 개발한 속성 그래프 쿼리 언어로, SQL, SPARQL과 달리 시각적인 구조를 갖는 쿼리 언어다. 초기에는 Neo4J의 속성 그래프에서만 사용할 수 있었지만, 최근에는 Cypher를 도입한 시스템이 늘어나고 있는 추세이다. 또한 현재 ISO에서 GQL로 표준화 작업이 진행되고 있다고 한다.

CREATE

쿼리 언어가 시각적이라는 말은, 쿼리 구조가 곧 그래프의 구조와 일치한다는 의미이다.

이런 구조의 Labeled Property Graph가 있다고 할 때, "Rosa lives in Berlin"이라는 문장을 그래프로 생성한다고 하면, 아래의 쿼리를 입력하면 된다.

CREATE (:Person {name: 'Rosa'})-[:LIVES_IN {since:2020}]->
(:Place {city:'Berlin', country:'DE'})

여기서 ()는 사람, 장소와 같이 레이블로 표현된 엔티티 노드를 표현할 때 사용한다. () 기호 안에 :Person, :Place는 엔티티의 레이블을 표현한다. 레이블은 같은 역할을 가진 노드를 그룹화 하며, 노드가 레이블로 생성될 때 해당 레이블이 인덱스에 추가된다.

Labeled Property Graph에서는 노드, 엣지 모두 key-value 형태의 속성값을 가질 수 있다는 특성이 있다. {name: 'Rosa'}, {city: 'Berlin', country: 'DE'}는 엔티티의 속성값으로, 노드는 한 개 이상의 속성을 가질 수 있다.

엣지는 -[:LIVES_IN {since: 2020}]->과 같이, 마치 실제 directed edge 모양처럼 '-' 기호와 '->' 기호의 조합으로 나타내며, 엣지의 레이블도 노드의 레이블과 마찬가지로 :LABEL_NAME 형식으로 표현된다. 여기서 엣지의 방향은 반드시 입력이 되어야 하며, <-[ ]- 와 같이 반대 방향으로 표현하는 것도 가능하다. 엣지는 속성을 아예 가지지 않을 수도 있으며, 한 개 이상의 속성을 갖는 경우 노드와 마찬가지로 {KEY : VALUE} 형식으로 표현한다.

실제로 이 노드와 엣지를 neo4j에서 생성해보자. 가장 먼저 Neo4j 데스크탑에서 Local Instance를 생성해야 한다.

위 사진처럼 로컬 인스턴스를 생성했으면, Tool > Query를 클릭해 쿼리 탭으로 이동한다.

쿼리를 입력하고 실행해보면 아래와 같이 쿼리 창이 변한다. 입력한 쿼리대로 2개의 노드와 1개의 관계가 잘 추가되었다고 한다.

MATCH

이제 추가된 그래프를 확인해보자. 그래프를 검색할 때는 MATCH 키워드를 사용한다. 전체 노드를 확인하고자 할 때에는 아래 쿼리를 입력한다.

MATCH (n) RETURN n

특정 레이블을 갖는 모든 노드를 검색하는 쿼리는 아래와 같이 표현할 수 있다.

MATCH (variable:LABEL) RETURN (variable)

ex)

MATCH (p:Person) RETURN p

위의 예시문은Person 레이블이 붙은 노드를 p이라는 변수에 매칭시키고, 이 p을 참조하라는 의미를 갖는다. (만일 노드 부분에서 변수명을 입력하지 않으면 에러가 뜬다.) 정상적으로 쿼리가 실행되면 아래와 같은 결과가 나타난다.

그래프로 보면 아래와 같은 결과가 나온다.

뭔가 이상하지 않은가? 우리가 원하는 그림은 Berlin 노드에 두 두 개의 인명이 매치되는 것인데, Berlin노드가 Person 노드에 대해 각각 생성되었다.

이는, Lisa 를 생성할 때 Create문을 사용하였기 때문이다. Create은 단순히 데이터를 새로 만드는 역할이므로 이미 생성된 노드에 대해 링크를 생성하지 못한다.

DELETE

다시 그래프를 생성하기 이전에, 현재 잘못 생성된 그래프를 모두 삭제하고 가야 한다. 그래프의 삭제에는 DELETEDETACH DELETE을 사용한다. DELETE은 해당하는 노드, 또는 간선만을 삭제한다. 노드를 삭제할 때, 만약 노드가 여전히 엣지로 연결되어 있는 경우 Dependent Object Error가 발생한다. 따라서, 위의 예시에서는 먼저 엣지를 삭제하고, 그 다음에 노드를 삭제하는 순서로 진행한다.

  1. 엣지 삭제
MATCH ()-[r:LIVES_IN ]-( ) DELETE r
  1. 노드 삭제
MATCH (n) DELETE n

DETACH DELETE는 해당하는 노드/간선과 연관된 모든 관계들을 삭제한다. DETACH DELETE는 특히 전체 그래프를 삭제할 때 효과적이다. 앞서 선언한 변수 n에 매치되는 노드들과, 노드와 연결된 모든 엣지를 한번에 삭제하는 쿼리는 아래와 같다.

MATCH (n) DETACH DELETE n

MERGE

CREATE는 항상 새로운 레코드를 생성한다는 특성이 있었다. MERGE 키워드는 제공된 패턴의 전체가 이미 존재하지 않는 경우에만 레코드를 삽입한다는 차이가 있다. 구문의 구조는 CREATE 와 동일하다.

MERGE (:Person {name: 'Karl', age : 64})
-[:LIVES_IN {since : 1980}]->
(:Place {city : "London", country:"UK"})

위의 쿼리를 입력하면, 아직 어떠한 패턴도 존재하지 않는 상태이기 때문에 CREATE을 입력했을 때와 같은 결과가 나타난다.

MERGE (:Person {name: "George", age: 25})
-[:LIVES_IN {since:2020}]->
(:Place {city:'London', country:"UK"})

위의 쿼리문에서 앞선 쿼리문과 중복되는 부분은 (:Place {city:'London', country:"UK"})이다.

다시 전체 그래프를 확인해보면, 방금과 CREATE를 사용했을 때와 동일한 결과가 나타난다.

이는 MERGE가 부분 일치-부분 패턴 생성을 하지 못하기 때문이다. MERGE는 MATCH, CREATE가 혼합되었다는 특성이 있어 전체 패턴이 일치할 때에만 작용하고, 부분일치에 대해서는 CREATE와 동일한 작업을 수행한다.

그렇다면, 어떻게 해야 단일한 영국-런던만을 생성하고, 이 노드에 LIVES_IN 엣지가 연결되게 할 수 있을까? 또, 캐나다 온타리오주에 위치한 동명의 도시 런던과도 식별되게 하는 방법이 있을까?

제약조건 설정하기

특정 속성들을 갖는 노드에 대해서 유니크하게 식별되도록, 즉 중복되는 노드가 생성되지 않도록 제약조건을 거는 방식으로 이 문제를 해결할 수 있다.
제약조건은 CREATE CONSTRAINT 키워드를 사용하여 설정하며, 복합 제약 조건은 Neo4j에서 제공한다.

CREATE CONSTRAINT no_duplicate_cities FOR (p:Place) 
REQUIRE (p.country, p.city) IS NODE KEY

위의 쿼리는 Place 노드가 city, country 속성 조합을 복합 키로 갖는다는 것을 선언한다.

다시 merge 키워드로 그래프를 생성해보면 아래와 같은 그래프를 확인할 수 있다.

Cypher는 실행 할 때 마다 독립적인 쿼리 컨텍스트를 사용한다 따라서 변수를 선언하고 재사용해야하는 경우에는 반드시 아래 사진과 같이 shift + enter 키로 줄바꿈하여 한 쿼리 컨텍스트에서 멀티라인으로 실행시켜야 한다. 그렇지 않으면 쿼리 컨텍스트 내에서 선언한 변수 정보가 다 초기화되어서 제대로 실행되지 않는다.

마지막으로 친구 관계까지 설정해준다. 그 전에, 그래프를 한번 초기화 하고 Person에 대해서도 중복 금지 제약을 걸어준다.

CREATE CONSTRAINT no_duplicate_people FOR (p:Person)
REQUIRE (p.name, p.age) IS NODE KEY

그리고 멀티라인으로 다음의 쿼리를 실행시키면

merge (london:Place {city:"London", country:"UK"})
merge (rome:Place {city:"Rome", country : "Italy"})
merge (karl:Person {name:"Karl", age:30})
merge (george:Person {name:"George", age: 24})
merge (kimi:Person {name:"Kimi", age: 20})
merge (karl)-[:LIVES_IN]->(london)
merge (george)-[:LIVES_IN]->(london)
merge (kimi)-[:LIVES_IN]->(rome)
merge (kimi)-[:FRIEND]->(george)
merge (george)-[:FRIEND]->(kimi)

아래와 같은 결과를 얻을 수 있다.

노드 속성 수정하기

이번엔 각 Person 엔티티마다 새로운 속성값인 DOB(Date Of Birth을 부여해야한다. 노드의 속성을 수정할 때에는 MATCH, WHERE 키워드로 조건에 해당하는 노드를 매칭시키고, SET 키워드로 새 속성값을 정의할 수 있다.

MATCH (p:Person) WHERE p.name="Kimi" and p.age=20
SET p.dob=20060825

위의 쿼리는 레이블이 'Person'인 노드 중, name 속성이 "Kimi" 이고, age 속성이 20인 노드의 dob를 20060825로 설정하도록 한다.

이 쿼리를 실행하고 다시 그래프를 확인했을 때, 해당 노드의 디테일이 바뀌어 있는 것을 확인할 수 있다.

만일 dob 속성을 다시 삭제하고자 한다면 REMOVE 키워드를 사용하면 된다.

MATCH (p:Person) WHERE p.name="Kimi" REMOVE p.dob

REMOVE로 노드의 레이블을 삭제할 수도 있다.

MATCH (p:Person) WHERE p.name="Kimi" Remove p:Person

노드의 레이블을 삭제하면, 해당 노드 자체는 존재하나 레이블이 공란이 된다.

레이블을 다시 부여하고 싶으면 다음의 쿼리를 입력하면 된다.

MATCH (n) WHERE size(labels(n)) = 0 SET n:Person
profile
무책임한 정보 추구

0개의 댓글