{#optional-call-optional-call} # OPTIONAL CALL {bdg-primary-line}`GQL 절` {bdg-secondary-line}`2.18에 추가` 호출이 행을 내놓지 못한 입력 행을 남겨 두고, 그 호출이 반환했을 모든 것을 null로 바인딩합니다. `OPTIONAL MATCH`에 대응하는 `CALL` 쪽 절입니다. {#optional-call-synopsis} ## 개요
OPTIONAL CALL [ variable_scope ] { subquery } OPTIONAL CALL function( [ argument [, argument ] ... ] ) YIELD yield_list

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

{#optional-call-description} ## 설명 `OPTIONAL`은 [`CALL`](call.md)의 두 형태 모두에 적용됩니다. 인라인 서브쿼리와 이름 있는 함수입니다. 그냥 쓴 `CALL`은 호출의 행을 파이프라인에 조인하므로, 행을 내놓지 못한 입력 행은 떨어져 나갑니다. `OPTIONAL CALL`은 대신 **왼쪽 외부 조인**으로 컴파일되고, 그것이 그 행을 보존합니다. - 보존은 **입력 행마다** 결정됩니다. 어떤 행에 대해 아무것도 찾지 못한 상관 호출은 그 행만 null로 만들고 나머지는 그대로 둡니다. - 그 호출이 바인딩했을 모든 변수가 함께 null이 되며, 타입이 무엇이든 그렇습니다. 정점, 간선, 경로, 리스트, 맵, 스칼라 모두입니다. - 그 바인딩은 jsonb의 `null`이 아니라 실제 SQL null이므로, 검사하는 방법은 `IS NULL`입니다. - `OPTIONAL`은 행을 보존할 뿐 만들어 내지는 않습니다. 입력이 비어 있으면 출력도 없습니다. - 보존된 행은 그 뒤로 평범한 행이므로, 뒤따르는 절이 다른 행과 똑같이 그것을 곱합니다. {#optional-call-parameters} ## 매개변수 variable_scope : 서브쿼리가 볼 수 있는 바깥 변수이며, [`CALL { subquery }`](call-subquery.md)와 정확히 같습니다. `(v, w)`, `(*)`, `()`, 또는 중괄호 안에서 가져오는 `WITH`입니다. 가져오기 규칙은 그대로 적용됩니다. `OPTIONAL CALL { … }`만 쓰면 아무것도 가져오지 않습니다. subquery : `RETURN`으로 끝나는 읽기 전용 쿼리입니다. function, argument, yield_list : [`CALL … YIELD`](call-yield.md)와 같습니다. `YIELD`는 여전히 필수입니다. {#optional-call-examples} ## 예제 모든 예제는 [예제 데이터셋](../../get_started/example-dataset.md)에서 실행됩니다. 사람 셋입니다. Keanu Reeves는 연기 이력이 넷, Ryan Gosling은 하나, Christopher Nolan은 없습니다. `OPTIONAL CALL`은 그 감독을 남깁니다: ```cypher MATCH (p:person) WHERE p.name IN ['Keanu Reeves', 'Ryan Gosling', 'Christopher Nolan'] OPTIONAL CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film } RETURN p.name AS person, film ORDER BY person, film; ``` ``` person | film ---------------------+---------------------------- "Christopher Nolan" | "Keanu Reeves" | "The Matrix" "Keanu Reeves" | "The Matrix Reloaded" "Keanu Reeves" | "The Matrix Resurrections" "Keanu Reeves" | "The Matrix Revolutions" "Ryan Gosling" | "Blade Runner 2049" (6 rows) ``` `OPTIONAL`을 빼면 같은 본문이 그 행을 완전히 잃고, 다른 두 사람은 영향을 받지 않습니다: ```cypher MATCH (p:person) WHERE p.name IN ['Keanu Reeves', 'Ryan Gosling', 'Christopher Nolan'] CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film } RETURN p.name AS person, film ORDER BY person, film; ``` ``` person | film ----------------+---------------------------- "Keanu Reeves" | "The Matrix" "Keanu Reeves" | "The Matrix Reloaded" "Keanu Reeves" | "The Matrix Resurrections" "Keanu Reeves" | "The Matrix Revolutions" "Ryan Gosling" | "Blade Runner 2049" (5 rows) ``` 네 행, 한 행, 그리고 보존된 한 행입니다. 이 배수는 쿼리의 성질이 아니라 입력 행마다 정해지는 것입니다. {#optional-call-preserve-a-row-a-function-yielded-nothing-for} ### 함수가 아무것도 내놓지 않은 행 보존하기 `OPTIONAL`은 이름 있는 형태에서도 같은 자리에 놓이며, 어떤 행에 대해 빈 집합을 반환하는 함수를 감당합니다: ```cypher MATCH (m:movie) WHERE m.title IN ['The Matrix', 'Avatar'] OPTIONAL CALL generate_series(1, CASE WHEN m.title = 'Avatar' THEN 0 ELSE 2 END) YIELD generate_series AS n RETURN m.title AS film, n ORDER BY film, n; ``` ``` film | n --------------+--- "Avatar" | "The Matrix" | 1 "The Matrix" | 2 (3 rows) ``` `generate_series(1, 0)`은 비어 있으므로 Avatar의 행이 `n`이 null인 채로 살아남습니다. `OPTIONAL`이 없으면 그 행은 사라지고 The Matrix의 두 행만 남습니다. {#optional-call-test-a-preserved-binding} ### 보존된 바인딩 검사하기 보존된 행에서는 본문이 반환했을 모든 변수가 null이며, 그것은 SQL null입니다: ```cypher MATCH (p:person {name: 'Christopher Nolan'}) OPTIONAL CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film, m.year AS year } RETURN film IS NULL AS film_is_null, year IS NULL AS year_is_null; ``` ``` film_is_null | year_is_null --------------+-------------- t | t (1 row) ``` {#optional-call-confirm-that-no-row-is-invented} ### 행이 만들어지지 않음을 확인하기 `OPTIONAL`은 입력 행을 보존합니다. 입력 행이 없으면 보존할 것이 없습니다: ```cypher MATCH (p:person {name: 'Nobody'}) OPTIONAL CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film } RETURN p.name AS person, film; ``` ``` person | film --------+------ (0 rows) ``` {#optional-call-notes} ## 참고 실행 계획이 그 방식을 그대로 말해 줍니다. `generate_series` 스캔이 `Nested Loop Left Join`의 null이 될 수 있는 쪽입니다. 그 조인이 Avatar의 행을 보존합니다: ```cypher EXPLAIN (COSTS OFF) MATCH (m:movie) WHERE m.title IN ['The Matrix', 'Avatar'] OPTIONAL CALL generate_series(1, CASE WHEN m.title = 'Avatar' THEN 0 ELSE 2 END) YIELD generate_series AS n RETURN m.title AS film, n; ``` ``` QUERY PLAN ----------------------------------------------------------------------------------------- Nested Loop Left Join -> Seq Scan on movie m Filter: (properties.'title' = ANY ('{"\"The Matrix\"","\"Avatar\""}'::jsonb[])) -> Function Scan on generate_series (4 rows) ``` 어떤 본문은 항상 행을 적어도 하나 내놓고, 그런 경우 `OPTIONAL`은 아무것도 바꾸지 않습니다. 그룹화하지 않은 집계가 흔한 경우입니다. 그것은 항상 한 행을 내놓으므로, 이력이 없는 사람은 null이 아니라 `0`을 받습니다. [`CALL { subquery }`](#call-subquery-aggregate-per-row-without-collapsing-the-pipeline)의 `films` 컬럼과 비교해 보십시오. `OPTIONAL`이 완화하는 것은 그 밖에 없습니다. 본문의 쓰기 절은 여전히 구문 오류이고, 바깥 이름을 반환하는 본문은 여전히 거부되며, `OPTIONAL CALL { … }`만 쓰면 여전히 아무것도 가져오지 않습니다. {#optional-call-compatibility} ## 호환성 ```{admonition} GQL 적합성 :class: gql-conformance GQL *call procedure statement*(ISO/IEC 39075:2024 §15.1)의 `OPTIONAL` 접두를 구현하며, 그것은 §15.2의 *inline procedure call*과 §15.3의 *named procedure call*을 모두 포함합니다. - **지원** — 두 호출 형태 모두에 붙는 `OPTIONAL`. 입력 행마다 행을 보존하고 반환되는 모든 변수를 null로 바인딩합니다. - **미지원** — 데이터를 바꾸는 호출에 붙는 `OPTIONAL`(§13.6). 애초에 호출이 그래프에 쓸 수 없기 때문입니다. 진행 상황은 [GQL 적합성 표](../../appendix/gql-conformance.md)에서 확인하십시오. ``` {#optional-call-opencypher} ### openCypher `OPTIONAL CALL`은 GQL의 구문이고, openCypher에는 `OPTIONAL MATCH`만 있습니다. 둘이 같은 것을 표현할 수 있는 자리에서는 결과가 일치합니다. 표준이 `OPTIONAL MATCH p`를 `OPTIONAL CALL (imports) { MATCH p RETURN vars }`로 정의하기 때문입니다. 그러므로 `OPTIONAL CALL`은 두 번째 장치가 아니라 더 일반적인 기본 요소입니다. {#optional-call-see-also} ## 관련 항목 - [`OPTIONAL MATCH`](optional-match.md) — 패턴에 대한, 같은 보존 - [`CALL`](call.md) {bdg-secondary-line}`GQL` — 두 호출 형태 - [`CALL { subquery }`](call-subquery.md) {bdg-secondary-line}`GQL` — 인라인 형태와 그 가져오기 목록 - [`CALL … YIELD`](call-yield.md) {bdg-secondary-line}`GQL` — 이름 있는 함수 형태 - [GQL 적합성 표](../../appendix/gql-conformance.md)