OPTIONAL CALL#

GQL 절 2.18에 추가

호출이 행을 내놓지 못한 입력 행을 남겨 두고, 그 호출이 반환했을 모든 것을 null로 바인딩합니다. OPTIONAL MATCH에 대응하는 CALL 쪽 절입니다.

개요#

OPTIONAL CALL [ variable_scope ] { subquery } OPTIONAL CALL function( [ argument [, argument ] ... ] ) YIELD yield_list

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

설명#

OPTIONAL은 CALL의 두 형태 모두에 적용됩니다. 인라인 서브쿼리와 이름 있는 함수입니다. 그냥 쓴 CALL은 호출의 행을 파이프라인에 조인하므로, 행을 내놓지 못한 입력 행은 떨어져 나갑니다. OPTIONAL CALL은 대신 왼쪽 외부 조인으로 컴파일되고, 그것이 그 행을 보존합니다.

  • 보존은 입력 행마다 결정됩니다. 어떤 행에 대해 아무것도 찾지 못한 상관 호출은 그 행만 null로 만들고 나머지는 그대로 둡니다.

  • 그 호출이 바인딩했을 모든 변수가 함께 null이 되며, 타입이 무엇이든 그렇습니다. 정점, 간선, 경로, 리스트, 맵, 스칼라 모두입니다.

  • 그 바인딩은 jsonb의 null이 아니라 실제 SQL null이므로, 검사하는 방법은 IS NULL입니다.

  • OPTIONAL은 행을 보존할 뿐 만들어 내지는 않습니다. 입력이 비어 있으면 출력도 없습니다.

  • 보존된 행은 그 뒤로 평범한 행이므로, 뒤따르는 절이 다른 행과 똑같이 그것을 곱합니다.

매개변수#

variable_scope

서브쿼리가 볼 수 있는 바깥 변수이며, CALL { subquery }와 정확히 같습니다. (v, w), (*), (), 또는 중괄호 안에서 가져오는 WITH입니다. 가져오기 규칙은 그대로 적용됩니다. OPTIONAL CALL { … }만 쓰면 아무것도 가져오지 않습니다.

subquery

RETURN으로 끝나는 읽기 전용 쿼리입니다.

function, argument, yield_list

CALL … YIELD와 같습니다. YIELD는 여전히 필수입니다.

예제#

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

사람 셋입니다. Keanu Reeves는 연기 이력이 넷, Ryan Gosling은 하나, Christopher Nolan은 없습니다. OPTIONAL CALL은 그 감독을 남깁니다:

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을 빼면 같은 본문이 그 행을 완전히 잃고, 다른 두 사람은 영향을 받지 않습니다:

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은 이름 있는 형태에서도 같은 자리에 놓이며, 어떤 행에 대해 빈 집합을 반환하는 함수를 감당합니다:

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의 두 행만 남습니다.

보존된 바인딩 검사하기#

보존된 행에서는 본문이 반환했을 모든 변수가 null이며, 그것은 SQL null입니다:

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은 입력 행을 보존합니다. 입력 행이 없으면 보존할 것이 없습니다:

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)

참고#

실행 계획이 그 방식을 그대로 말해 줍니다. generate_series 스캔이 Nested Loop Left Join의 null이 될 수 있는 쪽입니다. 그 조인이 Avatar의 행을 보존합니다:

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 }의 films 컬럼과 비교해 보십시오.

OPTIONAL이 완화하는 것은 그 밖에 없습니다. 본문의 쓰기 절은 여전히 구문 오류이고, 바깥 이름을 반환하는 본문은 여전히 거부되며, OPTIONAL CALL { … }만 쓰면 여전히 아무것도 가져오지 않습니다.

호환성#

GQL 적합성

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 적합성 표에서 확인하십시오.

openCypher#

OPTIONAL CALL은 GQL의 구문이고, openCypher에는 OPTIONAL MATCH만 있습니다. 둘이 같은 것을 표현할 수 있는 자리에서는 결과가 일치합니다. 표준이 OPTIONAL MATCH p를 OPTIONAL CALL (imports) { MATCH p RETURN vars }로 정의하기 때문입니다. 그러므로 OPTIONAL CALL은 두 번째 장치가 아니라 더 일반적인 기본 요소입니다.

관련 항목#