Cypher 1 - 그림을 그대로 적습니다

Neo4j 에 물어볼 때 쓰는 말이 Cypher 입니다. 표를 다루는 SQL 과 달리 node 와 edge 로 된 그림을 다룹니다.

그래서 쿼리가 그림처럼 생겼습니다. 기호 셋이 전부입니다.

  ( )       node
  -[ ]->    edge
  { }       속성

이 편에서 이 셋을 하나씩 보고, 2편 에서 그걸로 긴 길을 찾습니다.

1. node — ( )

사람 하나, 가게 하나가 node 입니다. 괄호 안에 둘을 적을 수 있습니다.

  ( )            아무 node 나
  (:Person)      Person 종류인 node
  (f)            node 하나. 뒤에서 f 라고 부르겠다
  (f:Person)     Person 종류인 node. 뒤에서 f 라고 부르겠다

:Person 을 라벨이라고 하는데, 그냥 종류라고 보면 됩니다. 앞의 f 는 이름표입니다. 결과로 꺼내거나 뒤에서 또 쓸 때만 붙이고, 안 쓸 거면 비워둡니다.

2. edge — -[ ]->

node 둘을 잇습니다. 화살표 방향이 있습니다.

  -[:KNOWS]->    KNOWS 종류의 edge 를 따라 앞으로
  <-[:KNOWS]-    거꾸로
  -[:KNOWS]-     방향은 상관없다. 어느 쪽이든 따라간다
  -[r:KNOWS]->   edge 에도 이름표를 붙일 수 있다

방향 무시가 중요합니다. (제자)-[:TRAINED_UNDER]->(스승) 처럼 한 방향으로만 저장해 둬도, 찾을 때 - 로 적으면 스승 쪽에서 제자 쪽으로도 갈 수 있습니다.

3. 속성 — { }

node 에도 edge 에도 값이 붙습니다.

  (p:Person {name: 'Mina'})      name 이 'Mina' 인 Person
  -[:KNOWS {since: 2019}]->      since 가 2019 인 KNOWS edge

4. 찾아서 꺼내기 — MATCH 와 RETURN

사람 넷이 이렇게 이어져 있다고 합시다.

Mina 와 Jun 과 Hana 와 Tae 가 KNOWS edge 로 이어진 그래프

Mina 의 친구를 찾습니다.

MATCH (me:Person {name: 'Mina'})-[:KNOWS]->(f:Person)
RETURN f.name
f.name
"Jun"
"Tae"
  MATCH          이런 모양을 찾아라.  SQL 의 SELECT ... WHERE 자리
  RETURN         찾은 것 중 뭘 꺼낼지
  f.name         f 라고 이름 붙인 node 의 name 속성

찾을 모양을 그림으로 적는 게 다른 점입니다. 위 한 줄은 그림에서 Mina 에서 나가는 화살표를 눈으로 따라가는 것과 같습니다.

5. 두 단계

화살표를 한 번 더 이으면 됩니다.

MATCH (me:Person {name: 'Mina'})-[:KNOWS]->(:Person)-[:KNOWS]->(fof:Person)
RETURN fof.name
fof.name
"Hana"

가운데 (:Person) 에 이름표가 없습니다. 거쳐 가기만 하고 결과로 안 꺼내니까 비워둔 것입니다.

Tae 는 결과에 없습니다. Tae 에서 나가는 KNOWS edge 가 없어서 그림이 거기서 끊깁니다.

6. 이름 바꾸고, 줄 세우고, 자르기

MATCH (me:Person {name: 'Mina'})-[:KNOWS]->(f:Person)
RETURN f.name AS friend
ORDER BY friend
LIMIT 2
friend
"Jun"
"Tae"
  AS friend      결과 칸 이름을 friend 로 바꾼다
  ORDER BY       줄 세운다
  LIMIT          위에서 몇 개만

ORDER BY 와 LIMIT 과 AS 는 SQL 과 똑같습니다. ORDER BY 를 안 쓰면 순서는 보장되지 않습니다. 앞 절들의 결과도 순서가 달리 나올 수 있습니다.

7. MATCH 는 여러 개 쓸 수 있습니다

MATCH (me:Person {name: 'Mina'})-[:KNOWS]->(f:Person)
MATCH (f)-[:LIVES_IN]->(c:City)
RETURN f.name, c.name

앞에서 붙인 이름표는 쿼리 끝까지 살아 있습니다. 첫 줄의 f 를 둘째 줄에서 그대로 씁니다.

다만 MATCH 가 늘면 행 수가 바뀝니다. 앞 행 하나마다 뒤 모양이 맞는 만큼 행이 생깁니다 — 하나도 안 맞으면 그 행은 사라지고, 셋이 맞으면 세 행이 됩니다.

위 쿼리는 둘째 MATCH 가 f 로 앞과 이어져 있어서 그렇습니다. 안 이어진 모양을 쓰면 다릅니다.

MATCH (f:Person)
MATCH (c:City)

이러면 사람 3명과 도시 4곳이 모든 짝으로 12행이 됩니다. 많이 이을 때는 중간에 WITH ... LIMIT 이나 DISTINCT 로 줄입니다. WITH f, c 를 쓰면 그 뒤로는 f 와 c 만 남습니다.

8. 값은 밖에서 넣습니다

MATCH (me:Person {id: $userId})-[:KNOWS]->(f:Person)
RETURN f.name

$userId 는 쿼리를 부를 때 넣는 값입니다. 이름을 쿼리에 박아 넣지 않고 비워두는 자리입니다.

9. 목록 다루기와 |

Cypher 는 목록을 자주 돌려줍니다. 목록을 한 번에 바꾸는 문법이 있습니다.

  [x IN 목록 | 바꿀 식]

파이썬의 [바꿀 식 for x in 목록] 과 앞뒤만 뒤집힌 모양입니다. 실제로 돌려보면 이렇습니다.

RETURN [n IN [1, 2, 3] | n * 10] AS tenfold
tenfold
[10, 20, 30]

n 은 목록에서 하나씩 꺼낸 것이고, | 뒤가 그걸 어떻게 바꿀지입니다.

| 는 뜻이 둘입니다

같은 기호가 다른 자리에서 다른 일을 합니다. 헷갈리기 쉬운 자리라 미리 갈라둡니다.

  [x IN 목록 | 식]      목록 안에서는  "각각 이렇게 바꿔라"
  -[:A|B]->             edge 안에서는  "A 또는 B"

두 번째는 KNOWS 든 WORKS_WITH 든 아무거나 따라가라는 뜻입니다.

MATCH (me:Person {name: 'Mina'})-[:KNOWS|WORKS_WITH]->(f:Person)
RETURN f.name

KNOWS 로 이어진 사람도, WORKS_WITH 로 이어진 사람도 다 나옵니다.

그리고 * 를 붙이면

2편에서 이런 모양을 만납니다. 조각으로 나눠 보면 새로울 게 없습니다.

  [:A|B|C*1..3]

  [: ... ]        edge 자리
  A|B|C           A 또는 B 또는 C
  *1..3           지나는 edge 가 1개에서 3개까지

*1..3 의 숫자는 지나는 edge 의 개수입니다. node 개수가 아닙니다. edge 를 두 개 지나면 node 는 셋을 밟습니다.

  (X) --A--> (Y)                edge 1개.  바로 옆
  (X) --A--> (Y) --C--> (Z)     edge 2개.  한 다리 건너

그래서 이 숫자를 거리라고 보면 됩니다. *1..3 은 "거리 1에서 3 사이에 있는 것" 입니다.

그리고 * 는 edge 종류 전체에 걸립니다. 세 종류를 섞어가며 가도 됩니다 — 위 둘째 줄처럼 A 로 한 번, C 로 한 번 가는 길도 거리 2로 걸립니다.

// 로 시작하는 줄은 주석입니다.

다음 편

두 단계면 -[:KNOWS]-> 를 두 번 쓰면 됩니다. 그런데 몇 단계인지 모르면 어떻게 할까요. 한 단계일 수도 세 단계일 수도 있다면요.

2편 이 거기서 시작합니다.