{#call-subquery-call--subquery-} # CALL { subquery } {bdg-primary-line}`GQL 절` {bdg-secondary-line}`2.18에 추가` 읽기 전용 서브쿼리를 파이프라인의 한 절로 실행하며, 그 서브쿼리가 감싸는 쿼리에서 변수를 가져오면 입력 행마다 한 번 실행합니다. ```{important} **`CALL { … }`만 쓰면 아무것도 가져오지 않습니다.** 중괄호 안의 패턴은 자기 변수 이름을 *선언*하므로, 본문에 적은 바깥 변수의 이름은 그 바깥 변수를 가리키는 것이 아니라 같은 이름의 제약 없는 새 변수를 바인딩합니다. 필요한 변수를 이름으로 적으십시오. `CALL (v) { … }`, `CALL (*) { … }`, 또는 `CALL { WITH v … }`입니다. ``` {#call-subquery-synopsis} ## 개요
[ OPTIONAL ] CALL { subquery } [ OPTIONAL ] CALL ( [ variable [, variable ] ... ] ) { subquery } [ OPTIONAL ] CALL ( * ) { subquery } [ OPTIONAL ] CALL { WITH variable [, variable ] ... subquery }

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

{#call-subquery-description} ## 설명 `CALL { … }`은 서브쿼리를 평가해 그 결과를 작업 표에 조인합니다. 본문이 무엇을 볼 수 있는지는 가져오기 목록이 전적으로 결정합니다. - 가져오기 목록이 있으면 본문은 그 변수들이 바인딩된 상태로 **입력 행마다** 실행되고, 반환하는 행은 그 행에 조인됩니다. - 가져오는 것이 없으면 본문은 **상관되지 않습니다.** 한 번 실행되고 그 결과가 모든 입력 행에 교차 조인됩니다. - 본문은 읽기 전용이며 `RETURN`으로 끝나야 합니다. 쓰기 절은 파싱 시점에 거부되고, `FINISH`로 끝나는 본문은 `CALL subquery must RETURN at least one variable`을 냅니다. - 본문이 반환하는 이름은 감싸는 쿼리의 바인딩이 됩니다. 바깥에 이미 바인딩된 이름은 덮어 가려지는 것이 아니라 거부됩니다. 본문 안에서도 가져온 변수는 그 이름을 유지합니다. 그중 하나를 다시 바인딩하게 될 `LET`, `UNWIND`, `FOR`, 이름을 바꾸는 `WITH`는 거부됩니다. - 본문이 아무것도 반환하지 않은 입력 행은 버려집니다. 그것을 남기려면 [`OPTIONAL CALL`](optional-call.md)을 쓰십시오. {#call-subquery-the-forms-that-import} ### 가져오는 형태들 표기 다섯 가지가 받아들여집니다. 앞의 둘은 아무것도 가져오지 않습니다. 그 둘과 뒤의 셋의 차이가 위 경고의 요점 전부입니다: | 형태 | 본문이 보는 것 | | --- | --- | | `CALL { … }` | 없음 | | `CALL () { … }` | 없음 | | `CALL (v, w) { … }` | `v`와 `w` | | `CALL (*) { … }` | 유효 범위의 모든 변수 | | `CALL { WITH v, w … }` | `v`와 `w` | 범위 절과 가져오는 `WITH`를 함께 쓸 수 있습니다. `CALL (v) { WITH w … }`는 둘 다 가져옵니다. 비슷해 보이는 세 가지는 대안이 아니라 구문 오류입니다. `CALL (*) { WITH v … }`, `CALL (*, v) { … }`, 그리고 이름만 나열한 것이 아닌 모든 가져오기 `WITH`입니다(`WITH *`와 `WITH v AS x` 둘 다 거부됩니다). {#call-subquery-parameters} ## 매개변수 variable : 감싸는 쿼리에서 바인딩된 변수이며, 중괄호 안에서 보이게 할 대상입니다. 존재하지 않는 변수는 `variable "" to import into CALL does not exist`를 냅니다. 앞에 절이 없는 상태의 범위 절은 `CALL has no preceding clause to import variables from`을 냅니다. subquery : `RETURN`으로 끝나는 읽기 전용 쿼리입니다. `MATCH`, `OPTIONAL MATCH`, `WITH`, `LET`, `UNWIND`, `FOR`, `LOAD FROM`, 중첩된 `CALL`, `DISTINCT`, `ORDER BY`, `SKIP`/`LIMIT`, 집합 연산자가 모두 허용됩니다. [`NEXT`](next.md)는 허용되지 않습니다. {#call-subquery-examples} ## 예제 모든 예제는 [예제 데이터셋](../../get_started/example-dataset.md)에서 실행됩니다. {#call-subquery-why-the-import-list-matters} ### 가져오기 목록이 중요한 이유 서브쿼리 형태는 이런 질문을 위해 만들어졌습니다. 누군가가 연출한 영화마다 출연진은 몇 명이었나? 가져오기 목록 없이 쓰면 다른 질문에 답합니다: ```cypher MATCH (d:person)-[:directed]->(m:movie) CALL { MATCH (a:person)-[:acted_in]->(m) RETURN count(a) AS cast_size } RETURN m.title AS film, cast_size ORDER BY film LIMIT 4; ``` ``` film | cast_size ---------------------+----------- "Avatar" | 25 "Blade Runner" | 25 "Blade Runner 2049" | 25 "Elysium" | 25 (4 rows) ``` 모든 영화의 출연진 수가 같고, 25는 그래프 전체의 `acted_in` 간선 수입니다: ```cypher MATCH ()-[r:acted_in]->() RETURN count(r) AS acted_in_edges; ``` ``` acted_in_edges ---------------- 25 (1 row) ``` 중괄호 안의 `m`은 바깥에서 일치시킨 `m`이 아닙니다. 본문 자신의 패턴이 선언한 완전히 새로운 변수이고, `acted_in` 간선으로만 제약됩니다. 그래서 본문은 그래프의 모든 `acted_in` 간선을 상관 없이 한 번 세고, 그 하나의 숫자를 모든 행에 교차 조인합니다. 가져올 것을 이름으로 적으면 의도한 질문을 하게 됩니다: ```cypher MATCH (d:person)-[:directed]->(m:movie) CALL (m) { MATCH (a:person)-[:acted_in]->(m) RETURN count(a) AS cast_size } RETURN m.title AS film, cast_size ORDER BY film LIMIT 4; ``` ``` film | cast_size ---------------------+----------- "Avatar" | 1 "Blade Runner" | 1 "Blade Runner 2049" | 2 "Elysium" | 1 (4 rows) ``` `CALL (*) { … }`과 `CALL { WITH m … }`도 같은 숫자를 냅니다. 아무것도 보고되지 않는 것은, 변수를 가리키는 것은 *표현식*뿐이고 *패턴*은 변수를 선언하기 때문입니다. 같은 실수를 `WHERE`로 옮기면 잡힙니다: ```cypher MATCH (d:person)-[:directed]->(m:movie) CALL { MATCH (a:person) WHERE a.name = m.title RETURN count(a) AS n } RETURN n; ``` ``` ERROR: variable does not exist LINE 2: CALL { MATCH (a:person) WHERE a.name = m.title RETURN count(... ^ ``` 그러므로 이 침묵은 패턴에서 다시 쓴 이름에 한정된 것입니다. 그리고 그것이 흔한 경우이며, 가져오기 목록을 매번 쓰는 이유입니다. {#call-subquery-aggregate-per-row-without-collapsing-the-pipeline} ### 파이프라인을 접지 않고 행마다 집계하기 집계하는 본문은 계산한 값이 0일 때에도 입력 행마다 정확히 한 행을 반환하므로, 바깥의 행 수는 그대로입니다: ```cypher MATCH (p:person) WHERE p.name IN ['Keanu Reeves', 'Christopher Nolan'] CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN count(m) AS films } RETURN p.name AS person, films ORDER BY person; ``` ``` person | films ---------------------+------- "Christopher Nolan" | 0 "Keanu Reeves" | 4 (2 rows) ``` {#call-subquery-compute-one-number-once} ### 숫자 하나를 한 번만 계산하기 빈 범위 절은 본문이 상관되지 않았음을 분명히 말해 줍니다. 본문이 그 쿼리의 상수라면 그렇게 쓰십시오: ```cypher MATCH (p:person {name: 'Keanu Reeves'}) CALL () { MATCH (m:movie) RETURN count(m) AS movies } RETURN p.name AS person, movies; ``` ``` person | movies ----------------+-------- "Keanu Reeves" | 18 (1 row) ``` {#call-subquery-import-several-variables} ### 변수 여러 개 가져오기 이름을 적은 모든 변수가 보이고, 본문은 그중 일부만 써도 됩니다: ```cypher MATCH (p:person {name: 'Keanu Reeves'}), (q:person {name: 'Carrie-Anne Moss'}) CALL (p, q) { MATCH (p)-[:acted_in]->(m:movie)<-[:acted_in]-(q) RETURN count(m) AS shared } RETURN shared; ``` ``` shared -------- 2 (1 row) ``` {#call-subquery-notes} ## 참고 상관된 본문이 실행 계획에서 행마다 도는 서브쿼리인 것은 아닙니다. 플래너가 그것을 끌어올리므로, 위 쿼리는 그 자리에 패턴을 적었을 때와 같은 조인으로 계획됩니다. `person`·`acted_in`·`movie`에 대한 `Hash Join`이고, `MATCH (p:person)-[:acted_in]->(m:movie)`와 똑같습니다. 집계하는 본문처럼 플래너가 평평하게 만들 수 없는 본문만 `Nested Loop` 아래 `Aggregate`가 놓인, 행마다 도는 lateral 조인으로 남습니다. 쓰기 절은 그 쓰기 키워드 자리에서 구문 오류로 거부되며, 종류마다 그렇습니다. `CREATE`, `INSERT`, `MERGE`, `SET`, `REMOVE`, `DELETE`, `DETACH DELETE`입니다. 바깥 쿼리가 이미 바인딩한 이름을 반환하는 본문은 `variable "" returned by the CALL subquery is already bound in the outer query`를 냅니다. 그것은 뒤따르는 모든 것에 대해 바깥 바인딩을 덮어 가리게 됩니다. 본문의 `RETURN`에서 별칭을 붙이십시오. {#call-subquery-compatibility} ## 호환성 ```{admonition} GQL 적합성 :class: gql-conformance §14.5의 쿼리 자리에서, §15.1의 *call procedure statement* 아래에 GQL의 *inline procedure call*(ISO/IEC 39075:2024 §15.2)을 구현합니다. - **지원** — 바인딩 변수 목록을 적은 *variable scope clause*와 빈 `()` 형태, 중첩, 그리고 `OPTIONAL` 접두. - **미지원** — 데이터를 바꾸는 본문(§13.6, *call data-modifying procedure statement*). - **이탈, 그리고 반드시 알아야 할 것** — §15.2 구문 규칙 3은 variable scope clause가 *없을* 때 들어오는 작업 레코드의 **모든** 필드를 나열한 scope clause로 호출을 다시 쓴다고 규정합니다. 표준대로라면 그냥 쓴 `CALL { … }`은 `CALL (*) { … }`과 똑같이 전부를 들여옵니다. AgensGraph는 scope clause가 없으면 `CALL () { … }`처럼 아무것도 들여오지 않는 것으로 봅니다. - **확장** — `(*)`는 표준의 *binding variable reference list*에 없고, 들여오는 `WITH`도 없습니다. 둘 다 openCypher에서 왔습니다. 진행 상황은 [GQL 적합성 표](../../appendix/gql-conformance.md)에서 확인하십시오. ``` {#call-subquery-opencypher} ### openCypher openCypher도 같은 가져오기 목록을 요구하지만, 그것이 없으면 알려 줍니다. 거기서는 `CALL { }` 안에서 선언되지 않은 바깥 변수를 가리키는 것이 조용한 재바인딩이 아니라 오류입니다. openCypher에서 옮겨 온 코드는 계속 동작합니다. `CALL (v) { }`, `CALL (*) { }`, `CALL { WITH v … }`가 모두 거기서와 같은 뜻이기 때문입니다. openCypher가 거부했던 `CALL { }`만 쓴 코드는 여기서 실행되고 다른 답을 냅니다. {#call-subquery-see-also} ## 관련 항목 - [`CALL`](call.md) {bdg-secondary-line}`GQL` — 두 호출 형태 - [`OPTIONAL CALL`](optional-call.md) {bdg-secondary-line}`GQL` — 본문이 아무것도 반환하지 않은 행 남기기 - [`CALL … YIELD`](call-yield.md) {bdg-secondary-line}`GQL` — 대신 이름 있는 함수 호출하기 - [서브쿼리 값 표현식](subquery-expressions.md) {bdg-secondary-line}`GQL` — 표현식 안의 서브쿼리 - [GQL 적합성 표](../../appendix/gql-conformance.md)