콘텐츠로 이동

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은 SQL을 통해 그래프 질의를 실행하는 AkasicDB의 인터페이스입니다. akasicdb.cypher() 함수를 통해 제공되며, openCypher 스타일의 MATCH 문법과 일부 PostgreSQL 표현식, 타입, 집계 함수 및 연산자를 결합한 AkasicDB 고유의 그래프 질의 방언을 처리합니다.

SQL/GQL은 ISO GQL과 openCypher의 문법을 참고하지만 어느 쪽도 그대로 구현하지 않습니다. 따라서 SQL/GQL이라는 명칭을 ISO GQL 준수 선언으로 해석해서는 안 됩니다. AkasicDB는 ISO GQL 적합성을 주장하지 않으며 특정 openCypher 버전과의 호환성도 보장하지 않습니다. GRAPH_TABLE과 속성 그래프 DDL 같은 SQL/PGQ 기능도 지원하지 않습니다. AkasicDB 그래프 질의는 이 페이지에 명시된 문법과 동작을 기준으로 작성해 주세요.

akasicdb.cypher()는 PostgreSQL 테이블 소스이므로 반환된 스칼라 값을 SQL로 후처리하거나, 관계형 테이블 또는 다른 그래프 질의와 조인하거나, 외부 SQL 집합 연산에 사용할 수 있습니다. Vector Features - Querying에서 설명하는 벡터 연산자도 피연산자 타입이 호환되고 해당 연산자가 설치되어 있으면 그래프 질의에서 사용할 수 있습니다.

함수 시그니처와 SQL 호출 요구 사항

함수 인자 설명

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

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

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

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

실제 SQL 호출에는 다음과 같이 외부 FROM 절과 컬럼 정의 목록도 지정해야 합니다.

SELECT *
FROM akasicdb.cypher('retail_graph', $$
MATCH (c:v_customer)
RETURN c.first_name
$$) AS (
    first_name varchar
);

SQL에서 함수를 호출할 때는 다음 요구 사항을 따라야 합니다.

  • akasicdb.cypher()는 FROM 절의 테이블 소스로 호출해야 합니다. SELECT akasicdb.cypher(...)처럼 스칼라 표현식으로 호출하는 방식은 지원하지 않습니다.
  • query_string은 PostgreSQL의 달러 인용 문자열로 작성해야 합니다. $$...$$와 $query$...$query$ 같은 태그 형식을 모두 사용할 수 있으며, 작은따옴표로 묶은 일반 문자열은 사용할 수 없습니다.
  • 모든 호출에 AS (name type, ...) 컬럼 정의 목록을 추가해야 합니다. 반환 타입이 record이므로 선언한 컬럼 수는 그래프 질의의 RETURN 절이 생성하는 결과 컬럼 수와 일치해야 합니다. 결과 컬럼과 AS 컬럼은 위치에 따라 대응합니다.
  • AkasicDB는 각 반환 값을 PostgreSQL의 명시적 타입 캐스팅 규칙에 따라 AS에 선언된 타입으로 변환합니다. 정점, 간선 또는 경로 객체를 나타내는 SQL 타입은 제공하지 않으므로 개별 스칼라 속성을 반환해야 합니다.

세 번째 인자는 선택 사항이며 그래프 질의 파라미터를 담은 JSONB 객체를 받습니다. 추출된 파라미터 값은 기본적으로 TEXT로 제공되며 ::integer와 같은 PostgreSQL 스타일 문법으로 캐스팅할 수 있습니다. JSON 객체와 배열은 JSON 텍스트로 직렬화됩니다.

질의 지원 개요

다음 표는 일반적으로 사용하는 지원 문법을 간략히 정리한 것으로 전체 목록은 아닙니다. 자세한 제약 사항은 아래의 예시와 "SQL/GQL 사용 시 유의사항"을 참고해 주세요. 이 페이지에 지원한다고 명시되지 않은 문법은 지원된다고 가정하지 마세요.

영역 지원 동작
패턴 정점 변수가 처음 등장할 때 정확히 하나의 레이블을 지정해야 하며, 같은 변수가 다시 등장할 때는 레이블을 생략할 수 있습니다. 정방향(->) 및 역방향(<-) 탐색과 여러 고정 길이 관계로 이루어진 경로를 지원합니다.
읽기 MATCH, WHERE의 속성 조건, WITH 뒤에 이어지는 추가 MATCH 절
프로젝션 WITH와 RETURN에서 스칼라 속성 및 PostgreSQL 스칼라 표현식 사용
결과 제어 DISTINCT, 해석 가능한 표현식을 대상으로 하는 ORDER BY(ASC, DESC, ASCENDING, DESCENDING 지원), SKIP, LIMIT. WITH에서는 프로젝션 별칭과 이러한 기능을 함께 사용할 수 있습니다.
집계 count(*), count(DISTINCT ...), array_agg(), 그리고 PostgreSQL이 인자 타입에 맞게 해석할 수 있는 sum(), avg(), min(), max() 등의 집계 함수
가변 길이 탐색 범위가 제한된 VLE와 상한 또는 하한이 생략된 VLE. 자세한 탐색 동작은 VLE(Variable Length Edge) 패턴을 참고하세요.
파라미터와 타입 선택적인 JSONB 인자의 $parameter 값과 PostgreSQL 스타일 ::type 캐스팅
SQL 결합 LEFT JOIN을 포함한 외부 SQL 조인 및 UNION과 같은 외부 SQL 집합 연산
벡터 표현식 피연산자 타입이 호환되고 해당 연산자가 설치되어 있을 때 <->, <=>, <#>와 같은 PostgreSQL 연산자

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 varchar 
); 

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 varchar,
    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는 *, *n, *m..n, *..n, *m.., *.. 형식을 지원하며 0-hop 범위도 사용할 수 있습니다. 범위 값은 음수일 수 없고 하한은 상한보다 클 수 없습니다.

VLE(Variable Length Edge)와 TC(Transitive Closure) 패턴에는 다음 동작과 제한 사항이 적용됩니다.

  • 하나의 간선 레이블만 사용할 수 있으며, 해당 간선은 레이블이 같은 정점들을 연결해야 합니다.
  • 하나의 VLE에서는 동일한 간선을 반복해서 지날 수 없지만 같은 정점은 다시 방문할 수 있습니다. 따라서 단순 경로(simple path)가 아닌 트레일(trail), 즉 간선 단순(edge-simple) 탐색입니다.
  • 하나의 경로에 간선 패턴이 여러 개 포함된 경우, 서로 다른 간선 패턴에서는 같은 간선을 다시 지날 수 있습니다.
  • SHORTEST는 너비 우선 탐색을 사용하며, hop 범위 안에서 각 도착 정점에 대해 조건을 충족하는 경로 중 가장 먼저 발견한 경로 하나를 반환합니다. 이 동작이 ISO GQL 또는 openCypher의 최단 경로 구현과 동일하다고 가정하지 마세요.
  • shortestPath()는 동일한 탐색 모드로 처리되며 인자에는 가변 길이 관계를 하나만 지정할 수 있습니다.
  • 탐색을 시작하는 정점(위 예시의 c1)을 필터링하지 않으면 탐색이 매우 느려질 수 있습니다.
  • 탐색할 hop 수가 늘어나면 성능이 저하될 수 있습니다. 각 도착 정점에 대해 조건을 충족하는 경로 중 처음 발견한 경로가 필요한 경우에만 SHORTEST를 사용해 주세요.

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

-- 파라미터(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 사용 시 유의사항

AkasicDB의 그래프 질의 방언은 openCypher와 유사하지만 지원 범위와 의미 체계가 다릅니다. 이 문서에서 지원한다고 명시하지 않은 openCypher 문법은 사용할 수 있다고 가정하지 마세요. 반면, 문서에 나열되지 않은 PostgreSQL 함수도 PostgreSQL이 호환되는 타입을 해석할 수 있다면 동작할 수 있습니다. 이는 openCypher 호환성을 보장한다는 의미가 아닙니다. 현재 지원하지 않는 문법은 향후 업데이트에서 추가될 수 있습니다.

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

  • 새로운 정점 변수가 그래프 패턴에 처음 등장할 때는 정확히 하나의 레이블을 지정해야 합니다. 같은 변수를 다시 사용할 때는 레이블을 생략할 수 있습니다. c가 처음 등장할 때는 (c:v_customer)처럼 작성해야 하며, 변수가 처음 등장할 때 레이블을 생략하거나 여러 레이블을 지정하는 패턴은 지원하지 않습니다.
  • 간선 패턴에는 하나의 간선 레이블을 지정해야 합니다. (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[]);
    

  • PostgreSQL 배열을 사용하므로 배열 인덱스는 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를 기준으로 정렬해 주세요.

  • OFFSET은 그래프 질의 문법에 포함되지 않습니다. akasicdb.cypher() 안에서는 SKIP을 사용하거나 외부 SQL 질의에 OFFSET을 적용해 주세요. LIMIT 인자는 BIGINT로 변환되며 현재 행의 변수를 참조할 수 없습니다.

  • Cypher 데이터 변경 절인 CREATE, MERGE, SET, DELETE는 지원하지 않습니다. Graph DML에서 설명하는 SQL 함수와 SQL UPDATE를 사용해 주세요.