AgensGraph 쿼리
그래프 쿼리
소개
그래프 데이터를 조회하고 조작하기 위해 AgensGraph는 Cypher 쿼리 언어를 지원합니다. Cypher는 SQL과 유사한 선언적 언어입니다. Cypher는 그 구문이 그래프에서 발견되는 패턴을 시각적으로 묘사하므로 배우기 쉽습니다.
이 가이드에서는 예제 그래프를 사용하여 Cypher 쿼리를 작성하는 방법을 간략하게 설명합니다.
예제 그래프 생성
AgensGraph는 하나의 데이터베이스에 여러 개의 그래프를 저장할 수 있습니다. 그러나 Cypher에는 여러 그래프를 구별할 방법이 없습니다. 따라서 AgensGraph는 Cypher를 사용하여 그래프를 생성하고 관리할 수 있도록 추가적인 데이터 정의 언어(Data Definition Language)와 변수를 지원합니다.
다음 구문은 network 라는 그래프를 생성하고 이를 현재 그래프로 설정합니다.
CREATE GRAPH network;
SET graph_path = network;
이 예제에서는 graph_path 변수가 명시적으로 network 로 설정되어 있습니다. 그러나 그래프를 생성하기 전에 graph_path 가 설정되지 않은 경우, 그래프를 생성한 후 자동으로 설정됩니다.
레이블 생성
그래프 데이터를 생성하기 전에 레이블을 생성하는 것이 기본입니다. 이것이 기본값이기는 하지만, Cypher의 CREATE 구문에서 레이블을 지정하면 레이블이 자동으로 생성됩니다(VLABEL/ELABEL 둘 다 생성할 수 있습니다).
note: label과 lable을 혼동하지 않도록 주의하십시오. 의도하지 않은 새 레이블이 생성될 수 있습니다.
모든 그래프 요소는 하나의 레이블을 가집니다. 정점의 경우, 레이블을 지정하지 않으면 기본 레이블로 ag_vertex 를 가집니다. 간선의 경우 레이블을 생략할 수 없습니다. ag_edge 레이블도 존재하지만 다른 용도로 사용됩니다.
AgensGraph는 이러한 레이블을 생성하기 위한 DDL을 지원합니다.
다음 구문은 정점 레이블 person 과 간선 레이블 knows 를 생성합니다.
CREATE VLABEL person;
CREATE ELABEL knows;
CREATE (n:movie {title:'Matrix'});
정점과 간선 생성
이제 Cypher의 CREATE 절을 사용하여 person 에 대한 정점과 knows 에 대한 간선을 생성할 수 있습니다. CREATE 절은 정점과 간선으로 구성된 패턴을 생성합니다. 정점은 (variable:label {property: value, ...}) 형태를 가지며, 간선은 -[variable:label {property: value, ...}]- 형태를 가집니다. 간선의 방향을 나타내기 위해 가장 왼쪽에 < 를, 또는 가장 오른쪽에 > 를 추가로 사용합니다. 생성된 정점과 간선을 참조하지 않을 경우 variable 은 생략할 수 있습니다.
note: AgensGraph는 패턴에서 간선에 대한
--문법을 지원하지 않습니다.--는 줄 끝까지의 주석을 의미하기 때문입니다.
다음 구문은 “Tom knows Summer”, “Pat knows Nikki”, “Olive knows Todd” 라는 세 가지 간단한 패턴을 생성합니다.
CREATE (:person {name: 'Tom'})-[:knows {fromdate:'2011-11-24'}]->(:person {name: 'Summer'});
CREATE (:person {name: 'Pat'})-[:knows {fromdate:'2013-12-25'}]->(:person {name: 'Nikki'});
CREATE (:person {name: 'Olive'})-[:knows {fromdate:'2015-01-26'}]->(:person {name: 'Todd'});
MATCH (p:Person {name: 'Tom'}),(k:Person{name: 'Pat'})
CREATE (p)-[:KNOWS {fromdate:'2017-02-27'} ]->(k);
정점과 간선의 속성을 저장하기 위해 AgensGraph는 PostgreSQL의 jsonb 타입을 사용합니다. 속성은 중첩된 JSON 객체를 값으로 가질 수 있습니다. AgensGraph는 PostgreSQL의 타입 시스템을 사용하므로, PostgreSQL이 지원하는 모든 데이터 타입을 정점과 간선의 속성에 저장할 수 있습니다.
그래프 쿼리
위에서 생성한 패턴을 조회해 보겠습니다. Cypher에는 그래프에서 패턴을 찾기 위한 MATCH 절이 있습니다.
다음 구문은 “Tom이라는 person이 어떤 person을 안다(knows)” 라는 패턴을 찾습니다.
MATCH (n:person {name: 'Tom'})-[:knows]->(m:person) RETURN n.name AS n, m.name AS m;
결과 :
n | m
-------+----------
"Tom" | "Summer"
"Tom" | "Pat"
(2 rows)
속성은 jsonb 타입이므로, 속성 값에 접근하기 위한 방법이 필요합니다. PostgreSQL은 ->, ->>, #>, #>> 와 같은 연산자를 통해 이러한 방법을 지원합니다. 정점 m 의 속성 name 에 접근하려면 (m)->>name 과 같이 작성할 수 있습니다. AgensGraph는 이러한 요소에 접근하는 대체 방법을 제공합니다. AgensGraph는 위에서 보인 것과 같이 정점과 간선에 점 연산자 . 와 대괄호 연산자 [] 를 사용하여 JSON 객체의 속성 값과 JSON 배열의 요소에 접근합니다.
RETURN 절은 쿼리의 결과로 변수와 그 속성을 반환합니다. 결과는 일치하는 여러 패턴을 행으로 가지는 테이블입니다.
가변 길이 간선
‘Tom’ 의 knows와 knows의 knows를 찾는 쿼리를 생각해 보겠습니다. UNION 절을 사용할 수 있습니다.
MATCH (p:person {name: 'Tom'})-[:knows]->(f:person)
RETURN f.name
UNION ALL
MATCH (p:person {name: 'Tom'})-[:knows]->()-[:knows]->(f:person)
RETURN f.name;
다음과 같이 작성할 수도 있습니다.
MATCH (p:person {name: 'Tom'})-[r:knows*1..2]->(f:person)
RETURN f.name, r[1].fromdate;
가변 길이의 간선-정점 경로 뒤에 위치한 정점을 찾는 쿼리는 그래프 데이터베이스에서 전형적입니다. 간선에 사용된 *1..2 는 그러한 가변 길이 간선을 나타냅니다. 여기서 1 은 간선의 최소 길이이고 2 는 최대 길이입니다. 값을 지정하지 않으면 기본 범위 값은 1과 무한대입니다.
MATCH 절 뒤에 CREATE, SET, REMOVE, DELETE 와 같은 갱신 절을 사용할 수도 있습니다. 다음 절에서는 그래프의 데이터를 수정하고 삭제하는 방법을 살펴보겠습니다.
그래프 조작
SET 절을 사용하여 정점과 간선에 속성을 설정할 수 있습니다. 속성에 null 값을 설정하면 해당 속성은 제거됩니다.
다음 구문은 주어진 패턴을 찾아 일치하는 간선의 속성을 갱신합니다.
MATCH (:person {name: 'Tom'})-[r:knows]->(:person {name: 'Summer'})
SET r.since = '2009-01-08';
그래프에서 정점과 간선을 삭제하려면 DELETE 절을 사용할 수 있습니다.
다음 구문은 정점을 찾아 삭제합니다.
MATCH (n:person {name: 'Pat'}) DETACH DELETE (n);
위 예제는 실제로 DETACH DELETE 절을 사용하여 정점과 그 정점에 연결된 간선을 한 번에 모두 삭제합니다.
그래프 network 의 최종 형태는 다음과 같습니다.
MATCH (n)-[r]->(m) RETURN n.name AS n, properties(r) AS r, m.name AS m;
결과 :
n | r | m
---------+---------------------------------------------------+----------
"Tom" | {"since": "2009-01-08", "fromdate": "2011-11-24"} | "Summer"
"Olive" | {"fromdate": "2015-01-26"} | "Todd"
(2 rows)
MERGE
그래프에 특정 패턴이 존재하는지 보장해야 하는 경우 MERGE 를 사용할 수 있습니다. MERGE 는 그래프에 패턴이 존재하면 해당 패턴을 찾으려 시도하고, 존재하지 않으면 패턴을 생성합니다. 패턴이 존재하면 MATCH 절처럼 처리되고, 그렇지 않으면 CREATE 절처럼 처리됩니다. MERGE 는 전체 패턴에 대한 MATCH 또는 CREATE 입니다. 즉, 패턴의 어느 한 요소라도 존재하지 않으면 AgensGraph는 전체 패턴을 생성합니다.
다음 구문은 모든 사람의 도시가 그래프에 존재하도록 보장합니다.
CREATE VLABEL customer;
CREATE VLABEL city;
CREATE (:customer {name:'Tom', city:'santa clara'}),
(:customer {name:'Summer ', city:'san jose'}),
(:customer {name:'Pat', city:'santa clara'}),
(:customer {name:'Nikki', city:'san jose'}),
(:customer {name:'Olive', city:'san francisco'});
MATCH (a:customer)
MERGE (c:city {name:a.city});
MATCH (c:city) RETURN properties(c);
결과 :
properties
-------------------------
{"name": "santa clara"}
{"name": "san jose"}
{"name": "san francisco"}
(3 rows)
MERGE 는 패턴이 MATCH되었는지 CREATE되었는지에 따라 SET 을 수행할 수 있습니다. MATCH된 경우 ON MATCH SET 절을 실행합니다. CREATE된 경우 ON CREATE SET 절을 실행합니다.
CREATE (:customer {name:'Todd', city:'palo alto'});
MATCH (a:customer)
MERGE (c:city {name:a.city})
ON MATCH SET c.matched = 'true'
ON CREATE SET c.created = 'true';
MATCH (c:city) RETURN properties(c);
결과 :
properties
----------------------------------------------
{"name": "santa clara", "matched": "true"}
{"name": "san jose", "matched": "true"}
{"name": "san francisco", "matched": "true"}
{"name": "palo alto", "created": "true"}
(4 rows)
AgensGraph에서 트랜잭션의 기본 격리 수준은 Read committed입니다. 따라서 다른 트랜잭션이 동시에 동일한 패턴에 대해 MERGE 를 실행하면 두 개(또는 그 이상)의 동일한 패턴이 동시에 생성될 수 있습니다. 이를 방지하려면 아래 예제와 같이 serializable 격리 수준에서 트랜잭션을 실행할 수 있습니다. 동일한 패턴을 동시에 생성하려고 시도하면 한 트랜잭션이 오류와 함께 실패합니다. 실패한 트랜잭션을 재시도하면, 성공한 트랜잭션이 생성한 패턴이 이미 존재하기 때문에 CREATE 대신 MATCH 처럼 동작합니다.
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
MATCH (a:customer)
MERGE (c:city {name:a.city});
COMMIT;
최단 경로 찾기
shortestpath 함수를 사용하여 두 정점 사이의 최단 경로를 찾을 수 있습니다. 모든 경로를 찾으려면 allshortestpaths 함수를 사용할 수 있습니다.
MATCH (p:person {name:'Tom'}), (f:person {name:'Olive'}) CREATE (p)-[:knows]->(f);
MATCH (p1:person {name: 'Tom'}), (p2:person {name: 'Todd'}),
path=shortestpath((p1)-[:knows*1..5]->(p2)) RETURN path;
이 예제에서는 ‘Tom’ 에서 ‘Olive’ 로의 ‘knows’ 간선 경로를 생성합니다. ‘Tom’ 에서 ‘Todd’ 로의 ‘knows’ 경로를 찾기 위해 shortestpath 함수를 사용할 수 있습니다. shortestpath 함수는 시작 정점, 간선, 끝 정점으로 구성된 패턴을 받습니다. 특정 연결 차수를 찾고자 하는 경우 간선에 가변 길이 간선 표현식을 사용할 수 있습니다.
쿼리 결과는 다음과 같습니다.
[person[3.1]{"name": "Tom"},knows[4.5][3.1,3.5]{},person[3.5]{"name": "Olive"},knows[4.3][3.5,3.6]
{"fromdate": "2015-01-26"},person[3.6]{"name": "Todd"}]
하이브리드 쿼리
소개
이 절에서는 다음 예제 그래프를 사용하여 AgensGraph에서 SQL과 Cypher를 함께 사용하는 방법을 살펴봅니다.
하이브리드 쿼리는 RDB에서 사용하는 SQL 쿼리를 이용하여 테이블과 칼럼에 대해 집계 및 통계 처리를 수행하고, GDB에서 사용하는 Cypher 쿼리는 RDB의 Join 연산보다 더 나은 데이터 쿼리를 지원합니다.
CREATE GRAPH skai;
CREATE VLABEL dev;
CREATE (:dev {name: 'someone', year: 2015});
CREATE (:dev {name: 'somebody', year: 2016});
CREATE TABLE history (year, event)
AS VALUES (1996, 'PostgreSQL'), (2016, 'AgensGraph');
SQL 내의 Cypher
Cypher 쿼리의 결과는 릴레이션이므로, SQL의 FROM 절에서 Cypher 쿼리를 서브쿼리처럼 사용할 수 있습니다. FROM 절 내부에서 Cypher 구문을 사용하여 그래프 DB에 저장된 정점이나 간선의 데이터셋을 SQL 구문의 데이터로 활용할 수 있습니다.
문법 :
SELECT [column_name]
FROM ({table_name|SQLquery|CYPHERquery})
WHERE [column_name operator value];
예제 :
SELECT n->>'name' as name
FROM history, (MATCH (n:dev) RETURN n) as dev
WHERE history.year > (n->>'year')::int;
결과 :
name
---------
someone
(1 row)
Cypher 내의 SQL
Cypher 쿼리로 그래프 DB의 내용을 조회할 때, RDB의 특정 데이터를 사용하여 검색하고자 하는 경우 match와 where 절을 사용할 수 있습니다. 단, SQL 쿼리의 결과 데이터셋은 단일 행의 결과를 반환하도록 구성되어야 합니다.
문법 :
MATCH [table_name]
WHERE (column_name operator {value|SQLquery|CYPHERquery})
RETURN [column_name];
예제 :
MATCH (n:dev)
WHERE n.year < (SELECT year FROM history WHERE event = 'AgensGraph')
RETURN properties(n) AS n;
결과 :
n
-----------------------------------
{"name": "someone", "year": 2015}
(1 row)