서브쿼리 값 표현식#

GQL 절 2.18에 추가

절이 아니라 서브쿼리로 질문에 답하는 여섯 개의 표현식이며, 값이 놓이는 곳이면 어디서나 쓸 수 있습니다. RETURN, WHERE, FILTER, WITH, 그리고 SQL입니다.

개요#

EXISTS { body } COUNT { body } COLLECT { body } ARRAY { body } VALUE { body } expression [ NOT ] IN { body }

굵게 키워드 · 기울임 직접 채우는 자리 · [ ] 생략 가능 · ... 반복 가능. 구문 표기 규약을 참고하십시오.

설명#

각 표현식은 자기 본문을 실행해 그 결과를 하나의 값으로 줄입니다. 본문은 상관됩니다. 그 표현식이 적힌 자리의 유효 범위 변수를 보며, CALL { subquery }와 달리 선언할 가져오기 목록이 없습니다. 그 변수들은 본문 전체에서 유효 범위에 남으므로, 본문 안의 절은 그 이름을 다시 바인딩할 수 없습니다. 그 이름에 대한 LET, UNWIND, FOR, 값을 그 이름으로 바꾸는 WITH, 그 이름을 반환하는 CALL { }은 거부됩니다. 그 이름의 MATCH (n)은 바깥 정점을 가리키고, WITH n은 그것을 그대로 넘깁니다.

아래 표는 각각이 무엇을 반환하는지, 그리고 본문이 비었을 때 무엇을 하는지 말해 줍니다. 여섯 중 다섯은 빈 본문에 null이 아니라 자기 타입의 값으로 답합니다. false, 0, 빈 리스트입니다. VALUE { }에는 그렇게 되돌아갈 값이 없으므로, null을 주는 것은 그것 하나입니다.

표현식

반환하는 것

본문이 비었을 때

EXISTS { }

불리언

false

COUNT { }

행 수, bigint

0

COLLECT { }

본문의 단일 컬럼을 담은 리스트

[]

ARRAY { }

COLLECT와 같음

[]

VALUE { }

본문의 단일 값, 그 자신의 타입으로

null

x IN { }

불리언

false

본문이 될 수 있는 것#

본문은 네 종류가 받아들여지고, 그중 어느 것이 허용되는지는 표현식에 따라 다릅니다:

본문

EXISTS / COUNT

COLLECT / ARRAY / VALUE / IN

패턴만 쓴 것, WHERE를 붙일 수도 있음

예

아니요

MATCH 문장, OPTIONAL MATCH 포함

예

아니요

RETURN으로 끝나는 GQL 읽기 문장

예

예

SQL SELECT

예

예

  • 쓰기 절은 거부됩니다. CREATE, INSERT, MERGE, SET, REMOVE, DELETE, DETACH DELETE는 각각 그 키워드 자리에서 구문 오류를 냅니다.

  • 읽기 문장 본문에서는 DISTINCT, ORDER BY, SKIP, LIMIT, 집합 연산자, 중첩된 CALL { }을 쓸 수 있습니다. NEXT는 쓸 수 없습니다.

  • 패턴만 쓴 본문에서는 경로에 이름을 붙일 수 없지만(EXISTS { p = (a)-[]->(b) }는 구문 오류), shortestpath()와 가변 길이 간선은 둘 다 됩니다.

  • SQL SELECT 본문은 상관되지 않습니다. 감싸는 쿼리의 변수를 볼 수 없습니다. 그것을 가리키면 missing FROM-clause entry for table "<name>"이 나옵니다.

매개변수#

body

위 네 가지 모양 중 하나로 된 서브쿼리입니다. COLLECT, ARRAY, VALUE, IN은 그것이 정확히 컬럼 하나를 내놓아야 합니다.

expression

x IN { }에서 찾을 값입니다. 정점, 간선, graphid는 x IN [list]에서처럼 동일성으로 비교하며, 왼쪽의 원소를 값만 반환하는 본문과 비교하면 타입 오류입니다. 그 밖의 값은 Cypher 본문의 컬럼과 jsonb로 만납니다. SQL 본문은 자기 컬럼 타입을 유지하므로 속성을 먼저 벗겨 내십시오. m.title #>> '{}'이 text를 냅니다.

예제#

모든 예제는 예제 데이터셋에서 실행됩니다.

패턴이 존재하는지 검사하기#

EXISTS { }는 조기에 끊습니다. 플래너가 그것을 일치 개수를 세는 것이 아니라 세미 조인이나 EXISTS 서브플랜으로 바꿉니다. 패턴만 쓴 것이 가장 짧은 본문이고, 거기에 WHERE를 붙일 수 있습니다:

MATCH (p:person)
WHERE EXISTS { (p)-[:directed]->(m:movie) WHERE m.year > 2015 }
RETURN p.name AS director ORDER BY director;
      director
--------------------
 "Denis Villeneuve"
 "Lana Wachowski"
(2 rows)

RETURN에서는 불리언 컬럼이며, 일치하는 것이 없으면 null이 아니라 false입니다:

MATCH (p:person {name: 'Keanu Reeves'}) RETURN EXISTS { (p)-[:directed]->(:movie) } AS directs;
 directs
---------
 f
(1 row)

일치 개수 세기#

패턴에 대한 COUNT { }는 그 패턴의 일치 개수를 셉니다:

MATCH (m:movie {title: 'The Matrix'}) RETURN COUNT { (:person)-[:acted_in]->(m) } AS cast_size;
 cast_size
-----------
 3
(1 row)

읽기 문장 본문은 자기 DISTINCT·LIMIT·그룹화를 유지하므로, 패턴으로는 셀 수 없는 것을 셀 수 있습니다:

MATCH (m:movie {title: 'The Matrix'})
RETURN COUNT { MATCH (p:person)-[:acted_in]->(m) RETURN DISTINCT p } AS distinct_cast;
 distinct_cast
---------------
 3
(1 row)

컬럼을 리스트로 모으기#

COLLECT { }는 리스트를 반환하며, 본문의 순서와 null을 그대로 보존합니다. ARRAY { }는 같은 표현식을 표준의 이름으로 부른 것입니다:

MATCH (m:movie {title: 'The Matrix'})
RETURN ARRAY { MATCH (p:person)-[:acted_in]->(m) RETURN p.name ORDER BY p.name } AS cast;
MATCH (m:movie {title: 'The Matrix'})
RETURN COLLECT { MATCH (p:person)-[:acted_in]->(m) RETURN p.name ORDER BY p.name } AS cast;
                            cast
------------------------------------------------------------
 ["Carrie-Anne Moss", "Keanu Reeves", "Laurence Fishburne"]
(1 row)

빈 본문은 null이 아니라 빈 리스트를 줍니다:

MATCH (m:movie {title: 'The Matrix'})
RETURN COLLECT { MATCH (p:person)-[:acted_in]->(m) WHERE p.born > 2000 RETURN p.name } AS nobody;
 nobody
--------
 []
(1 row)

값 하나 취하기#

VALUE { }는 스칼라 서브쿼리입니다. 이 페이지의 다른 모든 것과 달리 컬럼 자신의 타입을 유지하므로, 그래프 요소가 감싸이지 않고 그대로 살아남습니다:

MATCH (m:movie {title: 'Inception'})
RETURN VALUE { MATCH (p:person)-[:directed]->(m) RETURN p.name } AS director;
      director
---------------------
 "Christopher Nolan"
(1 row)
MATCH (m:movie {title: 'Inception'})
RETURN label(VALUE { MATCH (p:person)-[:directed]->(m) RETURN p }) AS yielded_type;
 yielded_type
--------------
 "person"
(1 row)

본문은 최대 한 행을 내놓아야 합니다. 그보다 많으면 실행 시점 오류이므로, 본문이 이미 값 하나로 정해진 것이 아니라면 ORDER BY … LIMIT 1이나 집계 함수를 붙이십시오:

MATCH (m:movie {title: 'The Matrix'})
RETURN VALUE { MATCH (p:person)-[:acted_in]->(m) RETURN p.name } AS one_of_three;
ERROR:  more than one row returned by a subquery used as an expression

서브쿼리에 속하는지 검사하기#

x IN { }은 구체화된 리스트가 아니라 세미 조인이므로, 본문이 커도 값싸게 유지됩니다. 값과 Cypher 본문의 컬럼은 jsonb로 만납니다:

MATCH (m:movie)
WHERE m.year IN { MATCH (x:movie) WHERE x.genre = 'Adventure' RETURN x.year }
RETURN m.title AS film ORDER BY film;
      film
-----------------
 "Jurassic Park"
(1 row)

정점은 동일성으로 비교하므로, 속성이 같은 두 정점도 여전히 서로 다른 정점입니다. 세미 조인은 그때 id로 실행됩니다:

MATCH (p:person)
WHERE p IN { MATCH (d:person)-[:directed]->(:movie {title: 'Inception'}) RETURN d }
RETURN p.name AS director;
      director
--------------------
 "Christopher Nolan"
(1 row)

SQL 본문은 자기 컬럼 타입을 유지합니다. rating.title은 text이므로, 그것을 jsonb로 비교하는 대신 속성을 #>> '{}'으로 벗겨 내십시오:

MATCH (m:movie)
WHERE m.title #>> '{}' IN { SELECT title FROM rating WHERE source = 'audience' }
RETURN m.title AS film ORDER BY film;
      film
----------------
 "Inception"
 "Interstellar"
 "The Matrix"
(3 rows)

벗겨 내지 않으면 그 비교에 쓸 연산자가 없습니다. operator does not exist: jsonb = text입니다.

투영이나 SQL에서 쓰기#

서브쿼리 표현식은 값이므로 WITH로 투영한 다음 검사할 수 있습니다:

MATCH (m:movie)
WITH m.title AS film, COUNT { (:person)-[:acted_in]->(m) } AS cast_size
FILTER cast_size > 2
RETURN film, cast_size;
     film     | cast_size
--------------+-----------
 "The Matrix" | 3
(1 row)

변수를 공급하는 Cypher 쿼리로 감싸면 SQL에서도 동작합니다:

SELECT (MATCH (m:movie {title: 'The Matrix'}) RETURN COUNT { (:person)-[:acted_in]->(m) }) AS cast_size;
 cast_size
-----------
 3
(1 row)

참고#

본문 어디에 있든 쓰기 절은 그 키워드 자리에서 거부되며, 무엇으로 감쌌든 그렇습니다:

MATCH (m:movie) RETURN COUNT { CREATE (:tmp) RETURN 1 AS one } AS n;
ERROR:  syntax error at or near "CREATE"
LINE 1: MATCH (m:movie) RETURN COUNT { CREATE (:tmp) RETURN 1 AS one...
                                       ^

감싸는 쿼리의 변수를 가리게 될 바인딩도 그렇습니다. 그 절이 최상위에서 내는 것과 같은 오류가 납니다:

MATCH (m:movie) RETURN COUNT { LET m = 1 RETURN m } AS n;
ERROR:  variable "m" already exists
LINE 1: MATCH (m:movie) RETURN COUNT { LET m = 1 RETURN m } AS n;
                                           ^
HINT:  LET cannot redefine an existing variable; use a different name.

참고

ARRAY는 PostgreSQL에서와 마찬가지로 예약 키워드이고, IN도 그렇습니다. COLLECT, COUNT, EXISTS, VALUE는 예약되지 않았으므로 식별자로 계속 쓸 수 있습니다. RETURN … AS value도 여전히 동작합니다.

호환성#

GQL 적합성

여섯 중 둘이 표준에 있습니다:

  • EXISTS { }는 GQL의 exists predicate(ISO/IEC 39075:2024 §19.4)를 구현하며, 그 하위 조항의 <graph pattern>·<match statement block> 본문과 중첩 쿼리 지정까지 포함합니다. 온전히 지원합니다.

  • VALUE { }는 GQL의 value query expression(§20.6), 기능 GQ18 “Scalar subqueries”를 구현하며, 일반 규칙 2의 「빈 본문은 null」 규칙도 따릅니다. 지원합니다. 다만 §20.6 구문 규칙 10은 본문에 LIMIT 1이나 집계 함수가 있어야 한다고 요구하는데, AgensGraph는 어떤 본문이든 받고 두 행 이상을 반환하면 실행 시점 오류를 냅니다.

COUNT { }, COLLECT { }, ARRAY { }, x IN { }은 확장입니다. ISO/IEC 39075:2024에는 COUNT의 서브쿼리 형태가 없습니다 — §20.9는 COUNT(*)와 COUNT(<value expression>)만 정의합니다 — 그리고 표준의 리스트 집계는 COLLECT_LIST(…)이며 그것도 서브쿼리가 아니라 행에 대한 집계입니다. COUNT { }와 COLLECT { }는 openCypher에서 왔고, ARRAY { }는 SQL의 ARRAY(<subquery>)를, x IN { }은 SQL의 IN (<subquery>)를 따릅니다.

진행 상황은 GQL 적합성 표에서 확인하십시오.

openCypher#

openCypher에는 EXISTS { }, COUNT { }, COLLECT { }가 같은 뜻으로 있습니다. ARRAY { }도, VALUE { }도, 서브쿼리에 대한 x IN { }도 없습니다. 스칼라에는 head(COLLECT { … })를, 속하는지 검사에는 x IN COLLECT { … }를 씁니다. 그 둘도 여기서 동작하지만 리스트 전체를 구체화하므로, VALUE { }와 x IN { }을 쓰는 편이 낫습니다.

관련 항목#