{#subquery-expressions-subquery-value-expressions} # 서브쿼리 값 표현식 {bdg-primary-line}`GQL 절` {bdg-secondary-line}`2.18에 추가` 절이 아니라 서브쿼리로 질문에 답하는 여섯 개의 표현식이며, 값이 놓이는 곳이면 어디서나 쓸 수 있습니다. `RETURN`, `WHERE`, `FILTER`, `WITH`, 그리고 SQL입니다. {#subquery-expressions-synopsis} ## 개요
EXISTS { body } COUNT { body } COLLECT { body } ARRAY { body } VALUE { body } expression [ NOT ] IN { body }

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

{#subquery-expressions-description} ## 설명 각 표현식은 자기 본문을 실행해 그 결과를 하나의 값으로 줄입니다. 본문은 상관됩니다. 그 표현식이 적힌 자리의 유효 범위 변수를 보며, [`CALL { subquery }`](call-subquery.md)와 달리 선언할 가져오기 목록이 없습니다. 그 변수들은 본문 전체에서 유효 범위에 남으므로, 본문 안의 절은 그 이름을 다시 바인딩할 수 없습니다. 그 이름에 대한 `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` | {#subquery-expressions-what-a-body-may-be} ### 본문이 될 수 있는 것 본문은 네 종류가 받아들여지고, 그중 어느 것이 허용되는지는 표현식에 따라 다릅니다: | 본문 | `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`](next.md)는 쓸 수 없습니다. - 패턴만 쓴 본문에서는 경로에 이름을 붙일 수 없지만(`EXISTS { p = (a)-[]->(b) }`는 구문 오류), `shortestpath()`와 가변 길이 간선은 둘 다 됩니다. - **SQL `SELECT` 본문은 상관되지 않습니다.** 감싸는 쿼리의 변수를 볼 수 없습니다. 그것을 가리키면 `missing FROM-clause entry for table ""`이 나옵니다. {#subquery-expressions-parameters} ## 매개변수 body : 위 네 가지 모양 중 하나로 된 서브쿼리입니다. `COLLECT`, `ARRAY`, `VALUE`, `IN`은 그것이 정확히 컬럼 하나를 내놓아야 합니다. expression : `x IN { }`에서 찾을 값입니다. 정점, 간선, graphid는 `x IN [list]`에서처럼 동일성으로 비교하며, 왼쪽의 원소를 값만 반환하는 본문과 비교하면 타입 오류입니다. 그 밖의 값은 Cypher 본문의 컬럼과 `jsonb`로 만납니다. SQL 본문은 자기 컬럼 타입을 유지하므로 속성을 먼저 벗겨 내십시오. `m.title #>> '{}'`이 `text`를 냅니다. {#subquery-expressions-examples} ## 예제 모든 예제는 [예제 데이터셋](../../get_started/example-dataset.md)에서 실행됩니다. {#subquery-expressions-test-whether-a-pattern-exists} ### 패턴이 존재하는지 검사하기 `EXISTS { }`는 조기에 끊습니다. 플래너가 그것을 일치 개수를 세는 것이 아니라 세미 조인이나 `EXISTS` 서브플랜으로 바꿉니다. 패턴만 쓴 것이 가장 짧은 본문이고, 거기에 `WHERE`를 붙일 수 있습니다: ```cypher 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`입니다: ```cypher MATCH (p:person {name: 'Keanu Reeves'}) RETURN EXISTS { (p)-[:directed]->(:movie) } AS directs; ``` ``` directs --------- f (1 row) ``` {#subquery-expressions-count-matches} ### 일치 개수 세기 패턴에 대한 `COUNT { }`는 그 패턴의 일치 개수를 셉니다: ```cypher MATCH (m:movie {title: 'The Matrix'}) RETURN COUNT { (:person)-[:acted_in]->(m) } AS cast_size; ``` ``` cast_size ----------- 3 (1 row) ``` 읽기 문장 본문은 자기 `DISTINCT`·`LIMIT`·그룹화를 유지하므로, 패턴으로는 셀 수 없는 것을 셀 수 있습니다: ```cypher 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) ``` {#subquery-expressions-gather-a-column-into-a-list} ### 컬럼을 리스트로 모으기 `COLLECT { }`는 리스트를 반환하며, 본문의 순서와 null을 그대로 보존합니다. `ARRAY { }`는 같은 표현식을 표준의 이름으로 부른 것입니다: ::::{tab-set} :::{tab-item} GQL — ARRAY ```cypher MATCH (m:movie {title: 'The Matrix'}) RETURN ARRAY { MATCH (p:person)-[:acted_in]->(m) RETURN p.name ORDER BY p.name } AS cast; ``` ::: :::{tab-item} Cypher — COLLECT ```cypher 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이 아니라 빈 리스트를 줍니다: ```cypher 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) ``` {#subquery-expressions-take-a-single-value} ### 값 하나 취하기 `VALUE { }`는 스칼라 서브쿼리입니다. 이 페이지의 다른 모든 것과 달리 컬럼 자신의 타입을 유지하므로, 그래프 요소가 감싸이지 않고 그대로 살아남습니다: ```cypher MATCH (m:movie {title: 'Inception'}) RETURN VALUE { MATCH (p:person)-[:directed]->(m) RETURN p.name } AS director; ``` ``` director --------------------- "Christopher Nolan" (1 row) ``` ```cypher 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`이나 집계 함수를 붙이십시오: ```cypher 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 ``` {#subquery-expressions-test-membership-against-a-subquery} ### 서브쿼리에 속하는지 검사하기 `x IN { }`은 구체화된 리스트가 아니라 세미 조인이므로, 본문이 커도 값싸게 유지됩니다. 값과 Cypher 본문의 컬럼은 `jsonb`로 만납니다: ```cypher 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로 실행됩니다: ```cypher 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`로 비교하는 대신 속성을 `#>> '{}'`으로 벗겨 내십시오: ```cypher 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`입니다. {#subquery-expressions-use-one-in-a-projection-or-from-sql} ### 투영이나 SQL에서 쓰기 서브쿼리 표현식은 값이므로 `WITH`로 투영한 다음 검사할 수 있습니다: ```cypher 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에서도 동작합니다: ```cypher SELECT (MATCH (m:movie {title: 'The Matrix'}) RETURN COUNT { (:person)-[:acted_in]->(m) }) AS cast_size; ``` ``` cast_size ----------- 3 (1 row) ``` {#subquery-expressions-notes} ## 참고 본문 어디에 있든 쓰기 절은 그 키워드 자리에서 거부되며, 무엇으로 감쌌든 그렇습니다: ```cypher 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... ^ ``` 감싸는 쿼리의 변수를 가리게 될 바인딩도 그렇습니다. 그 절이 최상위에서 내는 것과 같은 오류가 납니다: ```cypher 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. ``` ```{note} `ARRAY`는 PostgreSQL에서와 마찬가지로 예약 키워드이고, `IN`도 그렇습니다. `COLLECT`, `COUNT`, `EXISTS`, `VALUE`는 예약되지 않았으므로 식별자로 계속 쓸 수 있습니다. `RETURN … AS value`도 여전히 동작합니다. ``` {#subquery-expressions-compatibility} ## 호환성 ```{admonition} GQL 적합성 :class: gql-conformance 여섯 중 둘이 표준에 있습니다: - `EXISTS { }`는 GQL의 *exists predicate*(ISO/IEC 39075:2024 §19.4)를 구현하며, 그 하위 조항의 ``·`` 본문과 중첩 쿼리 지정까지 포함합니다. 온전히 **지원합니다.** - `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()`만 정의합니다 — 그리고 표준의 리스트 집계는 `COLLECT_LIST(…)`이며 그것도 서브쿼리가 아니라 행에 대한 집계입니다. `COUNT { }`와 `COLLECT { }`는 openCypher에서 왔고, `ARRAY { }`는 SQL의 `ARRAY()`를, `x IN { }`은 SQL의 `IN ()`를 따릅니다. 진행 상황은 [GQL 적합성 표](../../appendix/gql-conformance.md)에서 확인하십시오. ``` {#subquery-expressions-opencypher} ### openCypher openCypher에는 `EXISTS { }`, `COUNT { }`, `COLLECT { }`가 같은 뜻으로 있습니다. `ARRAY { }`도, `VALUE { }`도, 서브쿼리에 대한 `x IN { }`도 없습니다. 스칼라에는 `head(COLLECT { … })`를, 속하는지 검사에는 `x IN COLLECT { … }`를 씁니다. 그 둘도 여기서 동작하지만 리스트 전체를 구체화하므로, `VALUE { }`와 `x IN { }`을 쓰는 편이 낫습니다. {#subquery-expressions-see-also} ## 관련 항목 - [`CALL { subquery }`](call-subquery.md) {bdg-secondary-line}`GQL` — 명시적 가져오기 목록을 가진, 절로서의 서브쿼리 - [`FILTER`](filter.md) {bdg-secondary-line}`GQL` — 이 조건들이 어울리는 절 - [`WHERE`](where.md) — 조건이 놓이는 또 다른 자리 - [SQL과 Cypher 섞기](../../querying/combining-sql-and-cypher.md) — SQL 본문이 어떻게 들어맞는가 - [GQL 적합성 표](../../appendix/gql-conformance.md)