첫 쿼리#
이 페이지는 예제 데이터셋을 훑습니다 — 영화 18편, 사람 28명, 그리고 그 사이의 간선들입니다. 먼저 데이터셋을 적재하고 페이지를 위에서 아래로 따라가십시오. 각 쿼리는 앞의 쿼리 위에 쌓입니다.
여기 있는 것은 모두 Cypher입니다. 이에 대응하는 GQL 표기는 레퍼런스에 적혀 있습니다. SQL이 다시 등장하는 곳은 SQL과 Cypher 섞기입니다.
어느 그래프인지 말하기#
SET graph_path = moviekb;
Cypher가 필요로 하는 세션 상태는 이것 하나입니다. 이것이 없으면 모든 레이블 이름을 해석할 수 없습니다. 이제 그래프에 있는 모든 것을 세어 봅니다:
MATCH (n) RETURN count(n) AS elements;
elements
----------
46
MATCH (n)은 가능한 가장 비어 있는 패턴입니다 — 정점 하나, 레이블 없음, 속성 없음 — 그래서 모든
정점에 일치합니다. 46은 영화 18편에 사람 28명을 더한 것입니다.
하나 찾기#
패턴은 두 가지 방식으로 범위를 좁힙니다. 레이블로, 그리고 속성으로입니다.
MATCH (m:movie {title: 'The Matrix'}) RETURN m.year AS year, m.genre AS genre;
year | genre
------+----------
1999 | "Action"
(m:movie {title: 'The Matrix'})은 이렇게 읽습니다 — movie 레이블이 붙고 title 속성이
'The Matrix'인 정점, 그리고 그것을 변수 m에 바인딩한다. 그다음 RETURN이 m에서 값을
꺼냅니다.
"Action"에는 따옴표가 붙고 1999에는 붙지 않은 것을 보십시오. RETURN은 속성을 jsonb로
돌려주므로 문자열은 JSON 따옴표가 붙은 채로 옵니다. 결과를 클라이언트 쪽 문자열과 비교하기
전에 알아 둘 만한 점입니다 — 평범한 값을 얻는 방법은
SQL과 Cypher 섞기를 참고하십시오.
모든 정점에는 속성이 아닌 식별자도 있습니다:
MATCH (m:movie {title: 'The Matrix'}) RETURN m.title AS title, id(m) AS id;
title | id
--------------+-----
"The Matrix" | 3.1
3.1은 graphid입니다. 레이블 3, 행 1입니다. 이것이 정점의 식별자이고, id(m)이 그것을 묻는
방법입니다 — m.id는 id라는 이름의 속성을 찾는데, 그런 속성을 가진 영화는 없습니다.
간선 따라가기#
그래프의 요점은 그다음에 오는 것입니다. 패턴에 간선을 더합니다:
MATCH (p:person)-[r:acted_in]->(:movie {title: 'The Matrix'})
RETURN p.name AS actor, r.role AS role
ORDER BY actor;
actor | role
----------------------+------------
"Carrie-Anne Moss" | "Trinity"
"Keanu Reeves" | "Neo"
"Laurence Fishburne" | "Morpheus"
새로운 것이 세 가지 있습니다. -[r:acted_in]->는 acted_in 레이블이 붙은 간선이며 사람에서
영화를 향합니다. 이것이 r에 바인딩되므로 그 속성을 쓸 수 있습니다. r.role은 양 끝점이 아니라
관계의 속성입니다. 그리고 (:movie {title: 'The Matrix'})에는 변수가 없습니다. 패턴이 그 영화에
도달할 필요는 있지만 결과 투영에서는 그것을 언급하지 않기 때문입니다.
두 번 따라가기#
패턴을 반대 방향으로 늘리면, SQL이라면 자기 조인이 필요했을 질문을 한 것이 됩니다:
MATCH (:person {name: 'Keanu Reeves'})-[:acted_in]->(:movie)<-[:acted_in]-(co:person)
RETURN DISTINCT co.name AS co_actor
ORDER BY co_actor;
co_actor
----------------------
"Carrie-Anne Moss"
"Laurence Fishburne"
패턴을 왼쪽에서 오른쪽으로 읽습니다. Keanu Reeves에서 출발해 acted_in 간선을 따라 영화로 나가고,
다시 다른 acted_in 간선을 거꾸로 따라 두 번째 사람에게 갑니다. 영화 세 편을 함께 한 두 사람은
행 세 개를 만들기 때문에 DISTINCT가 필요합니다.
집계#
집계 함수는 PostgreSQL의 것이고, 예상하는 대로 동작합니다:
MATCH (d:person)-[:directed]->(m:movie)
RETURN d.name AS director, count(m) AS films
ORDER BY films DESC, director
LIMIT 5;
director | films
---------------------+-------
"Lana Wachowski" | 4
"James Cameron" | 3
"Lilly Wachowski" | 3
"Christopher Nolan" | 2
"Steven Spielberg" | 2
RETURN 안에서 집계 함수 안에 들어 있지 않은 것은 모두 그룹화 키가 됩니다. 이것도 SQL과 같습니다.
없을 수도 있는 것을 묻기#
MATCH는 패턴이 맞지 않으면 그 행을 버립니다. OPTIONAL MATCH는 행을 남겨 두고 채우지 못한
변수를 null로 채웁니다 — 왼쪽 외부 조인입니다:
MATCH (d:person)-[:directed]->(:movie)
WITH DISTINCT d
OPTIONAL MATCH (d)-[:acted_in]->(m:movie)
RETURN d.name AS director, m.title AS also_acted_in
ORDER BY director
LIMIT 6;
director | also_acted_in
---------------------+---------------
"Andrew Niccol" |
"Christopher Nolan" |
"Denis Villeneuve" |
"James Cameron" |
"Joss Whedon" |
"Lana Wachowski" |
이 데이터셋에는 연출과 연기를 모두 한 사람이 없으므로 also_acted_in은 모두 null입니다 — 그것이
바로 요점입니다. 그냥 MATCH를 썼다면 여섯 행이 전부 사라졌을 것입니다.
가운데 있는 WITH는 파이프라인 구분자입니다. 쿼리의 한 부분을 끝내고 다음 부분을 시작하며, 덕분에
선택적 일치가 실행되기 전에 DISTINCT가 감독들에게 적용됩니다.
몇 홉인지 모르는 길을 걷기#
경로를 모른 채로 두 사람이 어떻게 연결되는지 묻습니다:
MATCH p = shortestpath((a:person {name: 'Keanu Reeves'})-[*..6]-(b:person {name: 'Arnold Schwarzenegger'}))
RETURN length(p) AS hops;
hops
------
3
[*..6]은 가변 길이 간선입니다. 어떤 레이블이든, 어느 방향이든, 최대 여섯 개까지입니다.
p = ...는 경로 전체를 바인딩하고, length(p)는 그 간선 수입니다. 이것이 SQL로는 쓸 만한 형태가
없는 쿼리입니다.
shortestpath의 양 끝점에는 변수가 있어야 합니다. 그 변수를 쓰지 않더라도 그렇습니다. 익명으로
두면 ERROR: node must be a reference to a specific node가 나옵니다.
나머지는 경로가 다룹니다 — 범위를 정한 구간, allshortestpaths, 그리고
dijkstra를 쓰는 가중 탐색입니다.
무언가 쓰기#
쓰기는 동사만 다른 읽기처럼 보입니다. CREATE는 정점을 만듭니다:
CREATE (p:person {name: 'Hugo Weaving', born: 1960}) RETURN p;
p
----------------------------------------------------
person[4.63]{"born": 1960, "name": "Hugo Weaving"}
무언가에 연결하려면 먼저 양쪽 끝을 찾고 그다음 간선을 만듭니다:
MATCH (p:person {name: 'Hugo Weaving'}), (m:movie {title: 'The Matrix'})
CREATE (p)-[r:acted_in {role: 'Agent Smith'}]->(m)
RETURN r;
r
-------------------------------------------------
acted_in[5.60][4.63,3.1]{"role": "Agent Smith"}
간선은 label[id][start,end]{properties} 형태로 출력되므로 결과에서 양 끝점을 볼 수 있습니다.
SET은 속성을 추가하거나 교체합니다:
MATCH (p:person {name: 'Hugo Weaving'}) SET p.nationality = 'Australian' RETURN p;
p
---------------------------------------------------------------------------------
person[4.63]{"born": 1960, "name": "Hugo Weaving", "nationality": "Australian"}
REMOVE는 속성을 없앱니다:
MATCH (p:person {name: 'Hugo Weaving'}) REMOVE p.nationality RETURN p;
p
----------------------------------------------------
person[4.63]{"born": 1960, "name": "Hugo Weaving"}
그리고 DETACH DELETE는 정점을 그에 딸린 간선과 함께 지웁니다. 아직 간선이 남아 있는 정점에
그냥 DELETE를 쓰면 거부됩니다. AgensGraph는 끝점이 없는 간선을 남겨 두지 않기 때문입니다:
MATCH (p:person {name: 'Hugo Weaving'}) DETACH DELETE p;
이렇게 하면 데이터셋이 46개 요소로 돌아가므로 매뉴얼의 나머지 예제가 계속 맞습니다.
SQL 섞어 쓰기#
데이터셋과 함께 만든 rating 테이블은 그래프의 일부가 아닌 보통 테이블입니다. SQL이 테이블을
기대하는 자리에 Cypher를 놓아 Cypher 쿼리와 조인합니다:
SELECT q.film #>> '{}' AS film, r.score
FROM (MATCH (:person {name: 'Christopher Nolan'})-[:directed]->(m:movie)
RETURN m.title AS film) q
JOIN rating r ON r.title = q.film #>> '{}'
WHERE r.source = 'critics'
ORDER BY r.score DESC;
film | score
--------------+-------
Inception | 8.8
Interstellar | 8.7
#>> '{}'가 jsonb 제목을 평범한 text로 바꿔 text 컬럼과 비교할 수 있게 해 줍니다. 그리고
여기서는 영화 이름이 따옴표 없이 돌아오는 것을 보십시오. 이것은 Cypher가 아니라 SQL의 결과
투영이기 때문입니다.