콘텐츠로 이동

Graph Query

AkasicDB에서 그래프 질의를 수행하는 방법을 안내합니다.


그래프 준비

테이블 준비는 링크를 참고해주세요.

-- 그래프 'retail_graph'를 정의
SELECT akasicdb.define_graph('retail_graph');

-- 그래프 정의 'retail_graph'에 정점 정의 'v_item', 'v_customer', 'v_store' 추가
SELECT akasicdb.define_vertex(
  'retail_graph', 
  'v_item', 
  ARRAY['i_no integer', 'name varchar(50)', 'price decimal(7,2)'],
  'item'
);
SELECT akasicdb.define_vertex(
  'retail_graph', 
  'v_customer', 
  ARRAY['c_id integer', 'first_name varchar(20)', 'last_name varchar(30)'],
  'customer'
);
SELECT akasicdb.define_vertex(
  'retail_graph', 
  'v_store', 
  ARRAY['s_id integer', 'name varchar(50)'], 
  'store',
  'SELECT s_id, name FROM store'
);

-- 그래프 정의 'retail_graph'에 간선 정의 'buy', 'sell' 추가
SELECT akasicdb.define_edge(
  'retail_graph', 
  'buy',
  'v_customer', 'v_item',
  null,
  'SELECT null FROM customer c, orders o, item i '
  'WHERE c.c_id = o.c_id AND i.i_no = o.i_no',
  'customer c', 'item i'
);

SELECT akasicdb.define_edge(
  'retail_graph', 
  'sell',
  'v_store', 'v_item',
  null,
  'SELECT null FROM store s, orders o, item i '
  'WHERE s.s_id = o.s_id AND i.i_no = o.i_no',
  'store s', 'item i'
);

-- 그래프 정의 'retail_graph'에 간선 정의 'co_purchase' 추가
SELECT akasicdb.define_edge(
  'retail_graph', 
  'co_purchase',
  'v_customer', 'v_customer',
  null,
  'SELECT null '
  'FROM customer c1, orders o1, item i, orders o2, customer c2 '
  'WHERE c1.c_id = o1.c_id AND i.i_no = o1.i_no '
  'AND c2.c_id = o2.c_id AND i.i_no = o2.i_no',
  'customer c1', 'customer c2'
);

-- 그래프 정의 'retail_graph'를 기반으로 그래프 생성                   
SELECT akasicdb.create_graph('retail_graph');

SQL/GQL 질의 인터페이스

SQL/GQL이란?

SQL/GQL은 AkasicDB의 그래프·벡터·관계형 질의 문법입니다. akasicdb.cypher() 함수를 통해 ISO 공식 그래프 질의 언어인 GQL을 사용할 수 있습니다. 또한 그래프 질의의 결과를 PostgreSQL 테이블처럼 후처리하거나, 다른 관계형 질의와 결합할 수 있습니다. Vector Features - Querying에서 설명할 벡터 연산자도 직접 GQL 질의 내에서 사용할 수 있습니다.

함수 인자 설명

akasicdb.cypher()의 기본 사용법:

akasicdb.cypher(
    graph_name   NAME, -- 질의를 수행할 그래프 이름
    query_string TEXT  -- 수행할 그래프 질의
);

akasicdb.cypher()에 파라미터를 포함한 형태:

akasicdb.cypher(
    graph_name   NAME, -- 질의를 수행할 그래프 이름
    query_string TEXT,  -- 수행할 그래프 질의
    parameters   JSONB   -- JSON 형식의 파라미터 값
);

그래프 질의는 아래의 SQL/GQL 제약을 따르는 GQL(openCypher) 계열 문법으로 작성해야 합니다. 이때, query_string의 경우 $$(dollar quote)로 둘러싸인 문자열이어야 합니다. 또한, akasicdb.cypher() 호출 뒤에는 AS 절을 붙여서, 그래프 질의의 결과를 SQL 질의에서 튜플의 형태로 다룰 수 있도록 해야 합니다.

Basic Traversal

-- 이름이 Margaret인 소비자의 풀네임을 조회
SELECT *
FROM akasicdb.cypher('retail_graph', $$
MATCH (c:v_customer)
WHERE c.first_name = 'Margaret'
RETURN 
    c.first_name as first_name,
    c.last_name as last_name
$$) as (
    first_name varchar,
    last_name varchar
);
-- Margaret Farias와 같은 상품을 구매한 다른 소비자의 이름을 조회
SELECT *
FROM akasicdb.cypher('retail_graph', $$
MATCH (c1:v_customer)-[:buy]->(i:v_item)<-[:buy]-(c2:v_customer)
WHERE c1.first_name = 'Margaret' 
  AND c1.last_name = 'Farias'
  AND c1.vertex_id <> c2.vertex_id
RETURN 
    c2.first_name as first_name,
    c2.last_name as last_name
$$) as (
    first_name varchar,
    last_name varchar
);

WITH Clause

-- Margaret Farias가 상품을 구매한 상점에서 판매하는 다른 상품들을 조회
SELECT item_name 
FROM akasicdb.cypher('retail_graph', $$ 
MATCH (c:v_customer)-[:buy]->(i1:v_item)<-[:sell]-(s1:v_store) 
WHERE c.first_name = 'Margaret' 
  AND c.last_name = 'Farias'
WITH DISTINCT s1.vertex_id AS store_id 
MATCH (s2:v_store)-[:sell]->(i2:v_item) 
WHERE s2.vertex_id = store_id 
RETURN 
    DISTINCT i2.name AS item_name 
$$) as ( 
    item_name char 
); 

Aggregation

-- Margaret Farias가 특정 상점에서 구매한 상품의 이름과 구매한 수량을 조회
SELECT item_name, cnt
FROM akasicdb.cypher('retail_graph', $$
MATCH (c:v_customer)-[:buy]->(i:v_item)<-[:sell]-(s:v_store)
WHERE s.s_id = 1 
  AND c.first_name = 'Margaret' 
  AND c.last_name = 'Farias'
RETURN
    i.name AS item_name,
    count(i.name) AS cnt 
$$) as (
    item_name char,
    cnt integer
) 

VLE(Variable Length Edge) 패턴

-- co_purchase 간선을 통해 비슷한 구매 이력을 가진 소비자의 네트워크를 구축했을 때,
-- Latisha Hamilton으로부터 거리 1~3 이내의 소비자를 조회
SELECT *
FROM akasicdb.cypher('retail_graph', $$
MATCH (c1:v_customer)-[:co_purchase*1..3 SHORTEST]->(c2:v_customer)
WHERE c1.first_name = 'Latisha' AND c1.last_name = 'Hamilton'
RETURN
    c2.first_name as first_name,
    c2.last_name as last_name
$$) as (
    first_name varchar,
    last_name varchar
); 
  • VLE(Variable Length Edge)와 TC(Transitive Closure) 패턴의 경우, 아래의 제약이 있습니다.
  • 시작/끝 정점 레이블이 동일한 단일 간선 레이블만 사용할 수 있습니다.
  • 탐색을 시작하는 정점(위 경우 c1)이 필터링되지 않을 경우 매우 느릴 수 있습니다.
  • 탐색할 hop 수가 클 경우 매우 느릴 수 있습니다. 단, SHORTEST가 포함될 경우 hop 수가 크더라도 좋은 성능이 유지될 수 있습니다.

그래프 질의 내에서 파라미터 사용하기

-- 파라미터(customer_name, 값: Margaret)와 같은 이름을 가진 소비자의 풀네임을 조회
SELECT *
FROM akasicdb.cypher('retail_graph', $$
MATCH (c:v_customer)
WHERE c.first_name = $customer_name
RETURN 
    c.first_name as first_name,
    c.last_name as last_name
$$, '{"customer_name":"Margaret"}') as (
    first_name varchar,
    last_name varchar
);
  • 그래프 질의 내에서 파라미터는 $로 시작하는 심볼(위의 $customer_name)로 나타납니다.
  • 파라미터 값은 JSON 형식으로 함수에 전달할 수 있습니다.
-- 파라미터(customer_id, 값: 1)와 같은 `c_id`를 가지는 소비자의 풀네임을 조회
SELECT *
FROM akasicdb.cypher('retail_graph', $$
MATCH (c:v_customer)
WHERE c.c_id = $customer_id::integer
RETURN 
    c.first_name as first_name,
    c.last_name as last_name
$$, '{"customer_id":1}') as (
    first_name varchar,
    last_name varchar
);
  • 파라미터 값은 질의 내에서 기본적으로 TEXT 타입으로 처리됩니다.
  • 정수형 등의 다른 타입을 써야 할 경우 질의 내에서 명시적으로 타입 캐스팅을 해야 합니다.
  • SQL/GQL에서는 openCypher의 toInteger()와 같은 변환 함수 대신 ::integer와 같은 SQL 스타일 타입 캐스팅을 사용합니다. 단, 특수문자 등을 포함한 경우 ::'int[]'와 같이 타입 이름을 작은따옴표로 감싸서 사용해야 합니다.

SQL/GQL 사용 시 유의사항

SQL/GQL의 그래프 질의는 openCypher와 비슷하지만 지원되는 문법 범위와 사용 방식에서 몇 가지 차이점이 있습니다. 미지원 문법의 경우 추후 업데이트에서 추가될 수 있습니다.

아래 예시를 사용하기 전에 다음 패턴 규칙을 확인해 주세요.

  • 그래프 패턴에 정점 또는 간선이 처음 등장할 때는 반드시 하나의 레이블을 명시해야 합니다. 레이블이 없거나 여러 레이블을 지정한 패턴 대신 (c:v_customer)-[r:buy]->(i:v_item)와 같은 형태를 사용해 주세요.
  • 모든 간선은 방향성이 있습니다. 탐색 시 정방향(->) 또는 역방향(<-) 중 하나를 선택해야 하며, --와 같은 무방향 간선 패턴은 지원하지 않습니다.

  • OPTIONAL MATCH는 현재 지원하지 않습니다. 다만 SQL의 outer join을 사용해서 SQL/GQL에서 유사한 동작의 질의를 작성할 수 있습니다.

    openCypher SQL/GQL
    MATCH (a:v1)-[e1:r1]->(b:v2)
    OPTIONAL MATCH (b)-[e2:r2]->(c:v3)
    WHERE a.vertex_id = 1
    RETURN 
      a.vertex_id, 
      b.vertex_id, 
      c.vertex_id;
    

    SELECT q1.a_id, q1.b_id, q2.c_id 
    FROM akasicdb.cypher('graph', $$
      MATCH (a:v1)-[e1:r1]->(b:v2)
      WHERE a.vertex_id = 1
      RETURN a.vertex_id as a_id, b.vertex_id as b_id
    $$) AS q1(
      a_id integer,
      b_id integer
    ) LEFT JOIN akasicdb.cypher('graph', $$
      MATCH (b:v2)-[e2:r2]->(c:v3)
      RETURN b.vertex_id as b_id, c.vertex_id as c_id
    $$) AS q2(
      b_id integer,
      c_id integer
    ) ON q1.b_id = q2.b_id;
    

  • UNION 등의 집합 연산은 현재 akasicdb.cypher() 내에서 지원하지 않습니다. 필요한 경우 SQL의 집합 연산을 통해 SQL/GQL에서 유사한 동작의 질의를 작성할 수 있습니다.

    openCypher SQL/GQL
    MATCH (a:v1)-[e1:r1]->(b:v2)
    WHERE a.vertex_id = 1
    RETURN b.vertex_id
    UNION ALL
    MATCH (a:v1)-[e2:r2]->(c:v3)
    WHERE a.vertex_id = 1
    RETURN c.vertex_id;
    

    SELECT *
    FROM akasicdb.cypher('graph', $$
      MATCH (a:v1)-[e1:r1]->(b:v2)
      WHERE a.vertex_id = 1
      RETURN b.vertex_id as v_id
    $$) as (v_id integer)
    UNION ALL
    SELECT *
    FROM akasicdb.cypher('graph', $$
      MATCH (a:v1)-[e2:r2]->(c:v3)
      WHERE a.vertex_id = 1
      RETURN c.vertex_id as v_id
    $$) as (v_id integer);
    

  • (v:vertex_label {key: "Value"})와 같은 property map은 지원하지 않습니다. WHERE 절에 속성 관련 조건을 넣는 것으로 같은 동작의 질의를 작성할 수 있습니다.

    openCypher SQL/GQL
    MATCH (a:v1 {vertex_id: 1})-[e1:r1]->(b:v2)
    RETURN b.vertex_id;
    

    SELECT *
    FROM akasicdb.cypher('graph', $$
      MATCH (a:v1)-[e1:r1]->(b:v2)
      WHERE a.vertex_id = 1
      RETURN b.vertex_id as v_id
    $$) as (v_id integer);
    

  • exists() 및 path variable은 현재 지원하지 않습니다.

  • WITH/RETURN 절에서 정점/간선 자체를 반환하는 문법은 지원하지 않습니다.

  • IN 연산자는 현재 akasicdb.cypher() 내에서 지원하지 않습니다.

  • openCypher의 collect() 함수 대신 PostgreSQL의 array_agg() 함수를 사용하셔야 합니다.

    openCypher SQL/GQL
    MATCH (a:v1)
    RETURN collect(a.vertex_id);
    

    SELECT *
    FROM akasicdb.cypher('graph', $$
      MATCH (a:v1)
      RETURN array_agg(a.vertex_id) as v_ids
    $$) as (v_ids integer[]);
    

  • 배열 타입의 인덱스가 1부터 시작합니다.

  • List comprehension은 현재 지원하지 않습니다.

  • CALL subquery는 현재 지원하지 않습니다.

  • labels(n), type(r), properties(n)과 같은 openCypher 헬퍼 함수는 현재 지원하지 않습니다. 레이블은 패턴에 직접 지정하고, 속성은 이름으로 직접 접근해 주세요.

  • openCypher의 id() 함수는 현재 지원하지 않습니다. 정점은 n.vertex_id, 간선은 r.edge_id와 같은 내부 ID 속성을 직접 사용해 주세요.

  • ORDER BY에서는 같은 RETURN 절에서 만든 alias를 참조할 수 없습니다. 그래프 질의 안에서는 원래 표현식 또는 속성으로 정렬하거나, 바깥 SQL 질의에서 반환 alias를 기준으로 정렬해 주세요.