Neo4j - chapter 6 - creating data.

7 분 소요

Intro

  • Neo4j에서 데이터를 업데이트하는 방법을 정리하였습니다. 또한 해당 내용은 neo4j - graphacademy - part 6를 참고하여 번역하였습니다.
  • 지금까지는 모두 그래프로부터 필요한 데이터를 가져오기 위해 어떻게 Query를 작성해야 하는지, MATCH를 중심으로 배웠습니다. 특히, 노드와 엣지들의 프로퍼티, 유형, label을 어떻게 필터링하고, 동시에 중간에 발생한 중간 값을 WITH 등을 통해 처리하곤 했죠.
  • 이 챕터에서는 다음의 내용들을 정리합니다. Node, Relationship을 비롯한 새로운 개체, 개체의 값들 등을 추가하거나 업데이트하는 것, 지우는 것, 합치는 것 등을 배웁니다.

Creating Nodes

  • 노드를 만들기 위한 간단한 문법은 다음과 같습니다. 이전에 Node는 ()로 표현된다고 한 것처럼, 이 안에 관련된 정보들을 넣어서 생성하면 됩니다. variable, label이 들어가지만 선택사항이고(즉 꼭 써야 하는 것은 아니죠)
CREATE (optionalVariable:optionalLabels {optionalProperties})
CREATE ({})
CREATE({title: 'Batman Begins'})
CREATE (:Movie {title: 'Batman Begins'})
CREATE (:Movie:Action {title: 'Batman Begins'})
  • 만들고 바로 만든 값들을 리턴할 수도 있는데요, 이를 위해서는 아래와 같이 변수명도 작성해주어야 합니다.
CREATE (m:Movie:Action {title: ' Batman Begins'})
RETURN m.title

Creating Multiple Nodes

  • 여러 노드를 한번에 만들고 싶을 때는 다음처럼 하면 됩니다.
CREATE (:Person {name: 'Michael Caine', born: 1933}),
       (:Person {name: 'Liam Neeson', born: 1952}),
       (:Person {name: 'Katie Holmes', born: 1978}),
       (:Person {name: 'Benjamin Melniker', born: 1913})
  • 다만 실습과정에서 위 쿼리를 여러번 실행할 뒤 MATCH (p:Person) RETURN p를 수행해보면, 같은 값을 가지는 노드가 중복으로 아주 많이 있음을 알 수 있습니다. 이러한 중복은 다음 2가지 방법으로 해결할 수 있습니다만, 지금은 간략하게 말하고, 이후에 자세히 설명하도록 하겠습니다.
    • CREATE 대신 MERGE를 사용함으로써, 새로운 노드를 만들기보다는 병합해서 처리할 수 있다.
    • Graph에 Constraint를 추가해줄 수 있다.

Adding labels to a node

  • 이미 만들어진 노드에 label만 추가하기 위해서는 SET 명령어를 사용합니다.
SET x:Label         // adding one label to node referenced by the variable x
SET x:Label1:Label2 // adding two labels to node referenced by the variable x
  • 또한, 원하는 노드를 찾은 다음, 그 노드들의 라벨을 설정하고 싶을 때는 쿼리를 다음과 같이 작성해줍니다.
  • 아래 쿼리는, ‘Batman Begins’라는 property를 가진 Movie라는 label을 가진 개체를 모두 찾아서 m이라는 변수에 넣어주고, m의 모든 개체에게 새로운 라벨인 Action을 추가해주는 것입니다. 앞서 말한 것처럼 label은 일종의 tag처럼 동시에 여러 개가 들어갈 수 있으니까요
MATCH (m:Movie)
WHERE m.title = 'Batman Begins'
SET m:Action 
RETURN labels(m)

Removing labels from a node

  • 또한, 당연하지만, label을 없애는 것 또한 가능합니다. SET대신에 REMOVE를 넣어주면 되죠.
REMOVE x:Label    // remove the label from the node referenced by the variable x
MATCH (m:Movie:Action)
WHERE m.title = 'Batman Begins'
REMOVE m:Action
RETURN labels(m)

Adding properties to a node

  • 앞서 사용한 SETREMOVE는 label을 추가/삭제할 때뿐만 아니라, property를 추가, 변경, 삭제할때도 마찬가지로 사용됩니다. 기본적인 문법은 다음과 동일합니다.
  • 또한, 어느 정도 편의상, property는 python의 자료구조인 dictionary와 동일하게 흘러간다고 보셔도 되는데요, 이미 key가 있을 경우에는 그 값을 변경하고 없을 경우에는 새로운 값이 들어온다고 보시면 됩니다. 그리고, null을 넣어주면 그 프로퍼티의 값은 삭제됩니다(프로퍼티의 키가 삭제되는 것이 아니라, 값만 삭제된다는 것을 유의해야 합니다).
  • 그리고 아래에서 보는 것과 같이 JSON의 형태로 값을 넣어줄 수도 있으며, dictionary를 합치는 식으로 +=를 사용할 수 있습니다.
SET x.propertyName = value
SET x.propertyName1 = value1    , x.propertyName2 = value2
SET x = {propertyName1: value1, propertyName2: value2}
SET x += {propertyName1: value1, propertyName2: value2}
  • 조금 더 자세하게, 사용한다면 다음과 같은 형태 등으로 쿼리를 정의할 수 있습니다.
MATCH (m:Movie)
WHERE m.title = 'Batman Begins'
SET m.released = 2005, m.lengthInMinutes = 140
RETURN m
MATCH (m:Movie)
WHERE m.title = 'Batman Begins'
SET  m = {title: 'Batman Begins',
          released: 2005,
          lengthInMinutes: 140,
          videoFormat: 'DVD',
          grossMillions: 206.5}
RETURN m
MATCH (m:Movie)
WHERE m.title = 'Batman Begins'
SET  m += { grossMillions: 300,
            awards: 66}
RETURN m
  • property`의 데이터 타입은 강제되지 않습니다. 즉, 스트링 값이어야 하는 자리에 수치 값이 들어올 수도 있다는 것이죠.
  • 또한, 어떤 노드에게라도 한번 property가 추가되면, 이 property는 graph에게 종속됩니다. 가령, 단 한 노드에만 AAA라는 프로퍼티가 잠시 있다면, 제거하기 전까지는 CALL db.propertyKeys()와 같이 DB의 schema를 파악해 보면, 값이 없는 property라도 계속 남아있는 것을 파악할 수 있다는 것이죠.
  • 이 두 가지가, 저에게는 굉장히, 큰 문제점으로 받아들여집니다. SQL과 같은 DB에서는 Constraint를 통해서 데이터의 형태 등을 강제할 수 있습니다. 이는 데이터를 쿼리할 때 일관성이 없으면 문제가 심각해지기 때문이죠. 만약 매번 year=='2000' or year==2000라는 식으로 조건문을 짜야 한다면 이거 얼마나 귀찮아집니까?

Removing properties from a node

  • Property는 REMOVE를 사용해서 없앨 수 있습니다. 다시 말하지만, 해당 값이 null일지라도, property는 그대로 남아 있을 수 있습니다. 따라서, 만약, 해당 property를 이제 전혀 쓰지 않는다면 그대로 없애주는 것이 훨씬 효율적입니다.
REMOVE x.propertyName
SET x.propertyName = null
  • ‘Batman Begins’른 title이라는 property로 가지는 모든 m을 찾아서, m.grossMillions = null으로, m.videoFormat라는 프로퍼티는 아예 삭제하는 쿼리입니다.
MATCH (m:Movie)
WHERE m.title = 'Batman Begins'
SET m.grossMillions = null
REMOVE m.videoFormat
RETURN m

Creating relationships

  • relationship을 추가하는 것도 기본적으로는 node와 동일하다. 기본적인 문법구조는 다음과 같다.
  • 다만, 반드시 ‘방향’이 있어야 한다는 것을 강조하고 있습니다. 다시 말하면, 만약 ‘양방향’일 경우에는 쿼리를 두 번 하라는 말로 들리네요. 어떤 일관성을 위해서 이렇게 처리하는 것처럼 느껴지기는 하는데 흠.
CREATE (x)-[:REL_TYPE]->(y)
CREATE (x)<-[:REL_TYPE]-(y)
  • 그리고 그래프를 만들 때 보통 node를 먼저 만들고 이후 edge를 만들게 됩니다. 따라서, 아래의 코드와 같이, 이미 만들어진 노드들을 일괄 쿼리하여, 여기서부터 바로 관계를 주입할 수도 있죠.
MATCH (a:Person), (m:Movie)
WHERE a.name = 'Michael Caine' AND m.title = 'Batman Begins'
CREATE (a)-[:ACTED_IN]->(m)
RETURN a, m
  • 그리고, 위 코드를 다시 한번 보면, MATCH ~ WHERE ~에서 PersonMovie 사이에 cartesian product가 발생하는 것을 알 수 있습니다. 즉, 각각 100, 100개라면 100 * 100번의 체크가 필요하다는 것이죠. 이런 경우에는 DB에 성능상의 부하가 걸릴 수 있습니다. 이후에는 uniqueness constraint를 사용함으로써 이러한 성능상에서의 문제를 방지할 수 있습니다만, 일단 여기서는 그냥 넘어가도록 합니다.

  • 또한, 다음처럼 node-edge-node의 형태로 한번에 집어넣어도 문제가 없죠.

MATCH (a:Person), (m:Movie), (p:Person)
WHERE a.name = 'Liam Neeson' AND
      m.title = 'Batman Begins' AND
      p.name = 'Benjamin Melniker'
CREATE (a)-[:ACTED_IN]->(m)<-[:PRODUCED]-(p)
RETURN a, m, p

Adding properties to relationships

  • relationship도 node와 마찬가지로 SET을 통해 property를 추가할 수 있습니다. 구조적으로 완전히 동일하므로, 자세한 이야기는 생략하겠습니다.
SET r.propertyName = value
SET r.propertyName1 = value1    , r.propertyName2 = value2
SET r = {propertyName1: value1, propertyName2: value2}
SET r += {propertyName1: value1, propertyName2: value2}
  • 쿼리 예는 다음과 같습니다. 아래 쿼리에서는 검색 -> 노드와 엣지 생성 -> 엣지 성질 추가 -> 출력 의 형태로 진행이 되었는데, 이를 그냥 검색 -> 노드와 엣지를 성질과 함께 추가 -> 출력으로 간소화할 수도 있죠.
MATCH (a:Person), (m:Movie)
WHERE a.name = 'Christian Bale' AND m.title = 'Batman Begins'
CREATE (a)-[rel:ACTED_IN]->(m)
SET rel.roles = ['Bruce Wayne','Batman']
RETURN a, m
MATCH (a:Person), (m:Movie)
WHERE a.name = 'Christian Bale' AND m.title = 'Batman Begins'
CREATE (a)-[:ACTED_IN {roles: ['Bruce Wayne', 'Batman']}]->(m)
RETURN a, m
MATCH (a:Person),(m:Movie)
WHERE a.name = 'Christian Bale' AND
      m.title = 'Batman Begins' AND
      NOT exists((a)-[:ACTED_IN]->(m))
CREATE (a)-[rel:ACTED_IN]->(m)
SET rel.roles = ['Bruce Wayne','Batman']
RETURN a, rel, m

Removing properties from a relationship

  • 아래와 같이, REMOVE를 사용해서 처리해주거나, SET rel.roles = null로 처리하여 relationship에서 필요없는 property는 삭제해줄 수 있습니다.
MATCH (a:Person)-[rel:ACTED_IN]->(m:Movie)
WHERE a.name = 'Christian Bale' AND m.title = 'Batman Begins'
REMOVE rel.roles // set rel.roles=NULL
RETURN a, rel, m

Deleting nodes and relationships

  • REMOVE의 경우 필요없는 label, property를 삭제해주기 위해 사용하고, DELETE는 노드나 relationship 자체를 삭제하기 위해서 사용할 수 있습니다.

Deleting relationships

  • 앞서 나온 SET, REMOVE와 전개 과정이 동일합니다. 조건에 맞는 것들을 찾고 뒤에서 DELETE를 통해 찾은 노드를 지워주죠.
MATCH (a:Person)-[rel:ACTED_IN]->(m:Movie)
WHERE a.name = 'Christian Bale' AND m.title = 'Batman Begins'
DELETE rel
RETURN a, m
  • 그리고, 아래처럼 node와 relationship을 한번에 삭제해주는 것 또한 가능합니다.
  • 여기서 유의해야 하는 것은 반드시! relationshipnode보다 먼저 삭제해줘야 한다는 것이죠
MATCH (p:Person)-[rel:PRODUCED]->(:Movie)
WHERE p.name = 'Benjamin Melniker'
DELETE rel, p

Deleting nodes and relationships

  • 그리고, 해당 node가 포함된 모든 relationship을 한번에 없애고 싶으면, 아래처럼 처리하면 됩니다. 해당 node는 물론, node와 연결된 모든 edge까지 한번에 없애주죠.
MATCH (p:Person)
WHERE p.name = 'Liam Neeson'
DETACH DELETE  p

Merging data in the graph

  • 지금까지는 모두 CREATE 를 사용해서 node와 relationship을 만들어줬습니다. 그러나, neo4j는 노드간 같은 값을 가지더라도 중복을 허용하기 때문에 같은 값에 대해서 반복적으로 CREATE를 사용할 경우 같은 값의 여러 노드와 엣지가 중복으로 존재하게 됩니다.
  • 당연하지만, 이는 데이터의 일관성 관리 측면에서 크리티컬한 손해를 발생시키죠. 따라서, 이미 정확히 같은 값의 노드나 엣지가 있는 경우에는 여기에 덮어씌우는 것이 적합합니다. 이는 MERGE라는 명령어를 사용해서 처리하게 됩니다. 물론, 같은 값의 노드가 없을 경우에는 CREATE와 동일하게 작동하죠.

  • 우선 CREATE의 경우 다음과 같습니다.
    • Node: 정확히 같은 property 값을 가지는 노드가 있을 경우, 중복되는 새로운 노드를 생성
    • Label: Node에 이미 Label이 존재하는 경우, 노드는 변경되지 않음.
    • Property: 이미 property가 존재하는 경우에는 덮어 씌움(update)
    • Relationship: 정확히 같은 property 값을 가지는 엣지가 있을 경우, 중복되는 새로운 엣지를 생성
  • 따라서, Cypher에서 MERGE는 label과 property의 key를 기반으로 데이터의 중복성을 체크하고, 같다고 판단될 경우에는 데이터를 업데이트한다.

Using MERGE to create nodes

  • MERGE를 사용하여, node, relationship등을 업데이트하는 방법은 CREATE와 유사하다.
MERGE (a:Actor {name: 'Michael Caine'})
SET a.born = 1933
RETURN a

Specifying creation behavior when merging

  • 다만, 현재 중복되는 노드나 엣지가 있을 때 혹은 없을 때를 구분해서, “있을 때는 어떤 동작을 수행하고, 없을 때는 어떤 동작을 수행한다”와 같은 식으로 명령문을 작성할 수 있습니다. 이는 다음과 같이 작성할 수 있죠. 즉, IF문처럼 동작하게 한다고 생각하시면 됩니다.
MERGE (a:Person {name: 'Sir Michael Caine'})
ON CREATE SET a.born = 1934,
              a.birthPlace = 'UK'
ON MATCH SET a.birthPlace = 'UK'
RETURN a

wrap-up

  • 사실, 매우 복잡하게 보이지만, 그냥 SET, REMOVE를 써서 property든 label이든 모두 처리해주면 됩니다. 심지어, 그냥 SET만 써서 제거할 대는 NULL값을 주입해주면 알아서 키 값이 날아가니까요.
  • 그리고, CREATE, MERGE로 구분을 해두기는 했습니다만, 애초에 중복이 가능하도록 Graph Engine이 개발되어 있는 것이 제 입장에서는 약간 의아하게 느껴집니다. 물론, 이런 종류의 것은 일종의 선택이고, 어느 정도 이렇게 열린 상태로 두는 것이 해당 DB가 데이터를 어느 정도 비일관적일지라도 자유롭게 관리할 수 있도록 한다는 것처럼 보입니다.
  • 즉, 이렇게 자유로울 경우에는 개발자에 따라서 이 자유도를 어떤 일관성에 맞춰서 관리하기 위한 다양한 표준 케이스들이 필요하게 되죠. 늘 곱씹는 말이지만, 자유도가 넓을 수록 더 귀찮아집니다.

댓글남기기