{#composing-composing-a-query} # 쿼리 조합하기 Cypher 문장과 GQL 문장은 파이프라인입니다. 각 절은 행의 흐름을 받아 그것에 한 가지 일을 하고, 결과를 다음 절에 넘깁니다. `MATCH`가 행을 만들고, `WHERE`가 일부를 걸러 내고, `WITH`가 모양을 바꾸고, `RETURN`이 문장을 끝냅니다. 이것은 SQL과 다른 사고 모형입니다. SQL에서 `SELECT`는 이름 붙은 부분들이 정해진 순서로 평가되는 하나의 블록입니다. 여기서는 순서가 곧 작성한 순서입니다. 하나의 쿼리가 일치시키고, 집계하고, 그 집계를 걸러 내고, 살아남은 것에서 다시 일치시키고, 또 집계할 수 있습니다. 서브쿼리도, CTE도, `HAVING`도 없이 말입니다. 여기 있는 것은 모두 [예제 데이터셋](../get_started/example-dataset.md)에서 실행됩니다. {#composing-with-is-the-divider} ## WITH가 구분자다 `WITH`는 파이프라인의 한 부분을 끝내고 다음 부분을 시작합니다. `RETURN`과 똑같이 투영 목록을 받으며, 그 목록에 있는 것만 유효 범위에 남습니다: ```cypher MATCH (d:person)-[:directed]->(m:movie) WITH d, count(m) AS films WHERE films > 2 RETURN d.name AS director, films ORDER BY films DESC, director; ``` ``` director | films -------------------+------- "Lana Wachowski" | 4 "James Cameron" | 3 "Lilly Wachowski" | 3 ``` `WITH` 뒤에는 `m`이 사라지고 `d`와 `films`가 남습니다. 그 뒤의 `WHERE`는 집계를 걸러 내며, SQL에서 `HAVING`이 필요한 일입니다. `WITH` 안의 순서는 고정되어 있고 외워 둘 만합니다. `WHERE`가 먼저, 그다음 `ORDER BY`, 그다음 `SKIP`과 `LIMIT`입니다. 그래서 아래 쿼리는 영화 두 편을 먼저 고른 뒤 걸러 내는 것이 아니라, 다섯 편으로 걸러 낸 뒤 그중 두 편을 취합니다: ```cypher MATCH (m:movie) WITH m.year AS y WHERE y > 2010 ORDER BY y LIMIT 2 RETURN y; ``` ``` y ------ 2012 2013 ``` 이름은 한 번만 넘길 수 있습니다: ```cypher MATCH (m:movie) WITH m.year AS y, m.genre AS y RETURN y; ``` ``` ERROR: WITH passes on "y" more than once ``` {#composing-how-it-differs-from-a-cte} ### CTE와 무엇이 다른가 두 `WITH` 키워드는 모두 쿼리를 나누지만, 같은 키워드가 아닙니다. SQL의 CTE는 *이름을 붙여 참조*합니다. `WITH films AS (...)`로 선언하고 그다음 `films`를 원하는 곳에서, 원하는 횟수만큼, 어떤 순서로든 언급합니다. Cypher의 `WITH`는 *위치로 정해집니다*. 이름이 없고, 바로 다음에 온다는 것으로 참조되며, 정확히 한 번 소비됩니다. Cypher 파이프라인을 두 갈래로 나눈 뒤 다시 합치는 방법은 없습니다. 둘은 섞이지 않습니다. Cypher의 `WITH`로는 이름 붙은 서브쿼리를 도입할 수 없습니다. `WITH d AS (MATCH ...)`는 구문 오류입니다. 그러나 SQL의 CTE가 Cypher 쿼리를 담을 수 있으며, CTE의 동작이 필요할 때 그것을 얻는 방법이 이것입니다: ```psql WITH films AS (MATCH (m:movie) WHERE m.year > 2010 RETURN m.title AS title, m.year AS year) SELECT f.title #>> '{}' AS title, r.score FROM films f JOIN rating r ON r.title = f.title #>> '{}' WHERE r.source = 'critics' ORDER BY r.score DESC; ``` ``` title | score --------------------------+------- Interstellar | 8.7 Blade Runner 2049 | 8.0 The Avengers | 8.0 Elysium | 6.6 The Matrix Resurrections | 5.7 ``` 위에서 바깥 문장은 SQL입니다. SQL의 CTE로 시작하는 문장은 SQL 문장이기 때문입니다. 그 경계를 다루는 페이지는 [SQL과 Cypher 섞기](combining-sql-and-cypher.md)입니다. Cypher 파이프라인이 `WITH`로 *시작할* 수 없는 이유도 그것입니다. 맨 앞의 `WITH`는 CTE로 읽히므로, 파서는 이름과 괄호로 감싼 쿼리를 찾습니다: ```cypher WITH 1 AS x RETURN x; ``` ``` ERROR: syntax error at or near "1" LINE 1: WITH 1 AS x RETURN x; ^ ``` [`LET`](#composing-the-gql-clauses)에는 그런 충돌이 없으므로, 계산된 값으로 문장을 시작할 때 쓰는 절은 이것입니다: ```cypher LET x = 1 RETURN x; ``` ``` x --- 1 ``` {#composing-aggregation} ## 집계 집계 함수는 PostgreSQL의 집계 함수이고, 그룹화는 암시적입니다. 투영 목록에서 집계 함수 안에 들어 있지 않은 것은 모두 그룹화 키가 됩니다. ```cypher MATCH (m:movie) RETURN m.genre AS genre, count(*) AS films, min(m.year) AS earliest ORDER BY genre; ``` ``` genre | films | earliest -------------+-------+---------- "Action" | 7 | 1984 "Adventure" | 1 | 1993 "Sci-Fi" | 10 | 1982 ``` `count(*)`는 행을 셉니다. `count(x)`는 `x`가 null이 아닌 행을 셉니다. 그래서 `count(x)`는 `OPTIONAL MATCH` 뒤에서 유용합니다. `count(DISTINCT x)`는 서로 다른 값을 세며, 이 둘의 차이는 보통 패턴이 퍼진 정도입니다: ```cypher MATCH (:person)-[:acted_in]->(m:movie) RETURN count(m) AS rows, count(DISTINCT m) AS films; ``` ``` rows | films ------+------- 25 | 18 ``` `collect()`는 가장 자주 꺼내 쓸 집계 함수입니다. 퍼진 것을 다시 한 행으로 되돌려 주기 때문입니다. 정점, 간선, 경로에 대해 쓰면 그 요소들의 네이티브 배열을 만들므로, 모아 놓은 요소를 계속 쓸 수 있습니다: ```cypher MATCH (p:person)-[:acted_in]->(m:movie {title: 'The Matrix'}) WITH collect(p) AS players RETURN pg_typeof(players) AS type, cardinality(players) AS n, players[1].name AS first; ``` ``` type | n | first ------------+---+---------------------- "vertex[]" | 3 | "Laurence Fishburne" ``` *속성*에 대한 `collect()`는 여전히 jsonb 리스트를 반환하므로, 저장된 데이터는 자기 모양을 유지합니다. 투영 전체에 붙인 `DISTINCT`는 행의 중복을 없앱니다. 같은 대상에 여러 경로로 도달하는 패턴 뒤에 필요한 것이 이것입니다: ```cypher MATCH (:person {name: 'Keanu Reeves'})-[:acted_in]->(:movie)<-[:acted_in]-(co:person) RETURN DISTINCT co.name AS co_actor ORDER BY co_actor; ``` ``` co_actor ---------------------- "Carrie-Anne Moss" "Laurence Fishburne" ``` {#composing-unwind} ## UNWIND `UNWIND`는 `collect()`의 반대입니다. 리스트를 받아 요소마다 행 하나씩을 만듭니다. ```cypher UNWIND [1999, 2010, 2014] AS y MATCH (m:movie {year: y}) RETURN y, m.title AS title ORDER BY y, title; ``` ``` y | title ------+---------------- 1999 | "The Matrix" 2010 | "Inception" 2014 | "Interstellar" ``` 같은 파이프라인에서 앞서 만든 리스트를 풀어내는 데도 씁니다. 집계로 걸러 낸 다음 다시 요소마다 행 하나로 돌아가는 방법이 이것입니다: ```cypher MATCH (m:movie)<-[:acted_in]-(p:person) WITH m, collect(p) AS players WHERE cardinality(players) > 2 UNWIND players AS actor RETURN m.title AS film, actor.name AS actor ORDER BY actor; ``` ``` film | actor --------------+---------------------- "The Matrix" | "Carrie-Anne Moss" "The Matrix" | "Keanu Reeves" "The Matrix" | "Laurence Fishburne" ``` [`FOR`](../reference/clauses/for.md)가 GQL 표기이며, 요소의 0부터 시작하는 인덱스를 위해 `WITH OFFSET`을 더해 줍니다. [`UNWIND`](../reference/clauses/unwind.md)를 참고하십시오. {#composing-ordering-and-paging} ## 정렬과 페이지 나누기 `ORDER BY`, `SKIP`, `OFFSET`, `LIMIT`은 그 자체로 절입니다. SQL처럼 `RETURN`이나 `WITH`의 끝에 놓일 수 있고: ```cypher MATCH (m:movie) RETURN m.title AS title, m.year AS year ORDER BY year SKIP 2 LIMIT 3; ``` ``` title | year ------------------------------+------ "Total Recall" | 1990 "Terminator 2: Judgment Day" | 1991 "Jurassic Park" | 1993 ``` 파이프라인 가운데에 홀로 설 수도 있습니다. 이것은 `RETURN` 형태로는 할 수 없습니다: ```cypher MATCH (m:movie) LET y = m.year ORDER BY y LIMIT 3 RETURN m.title AS title, y; ``` ``` title | y ------------------+------ "Blade Runner" | 1982 "The Terminator" | 1984 "Total Recall" | 1990 ``` 이것들이 문장을 *끝낼* 수는 없습니다. 쿼리는 여전히 `RETURN`, `FINISH`, 또는 쓰기 절로 끝나야 합니다. 정렬 키는 jsonb로 그려진 모습이 아니라 그 값 자체로 정렬되므로, 그 속성에 걸린 인덱스가 정렬에 쓰일 수 있습니다. 이것이 이 대목을 신경 써야 하는 이유 전부입니다: [인덱스와 제약](#indexes-and-constraints-ordering). {#composing-union} ## UNION `UNION`은 컬럼 이름이 같은 두 결과 집합을 쌓고 중복을 없앱니다. `UNION ALL`은 중복을 남깁니다. ```cypher MATCH (m:movie {title: 'The Matrix'}) RETURN m.title AS name UNION MATCH (p:person {name: 'Keanu Reeves'}) RETURN p.name AS name; ``` ``` name ---------------- "Keanu Reeves" "The Matrix" ``` 간선 레이블 택일 표기가 없는 것을 깔끔하게 우회하는 방법도 이것입니다. 좁은 패턴 둘을 union하면 간선 테이블 두 개를 스캔하지만, 타입 없는 패턴 하나는 그 전부를 스캔합니다. [그래프 읽기](#reading-one-label-per-edge)를 참고하십시오. {#composing-the-gql-clauses} ## GQL 절들 ISO/IEC 39075의 네 개 절이, Cypher에서는 여러 절이 필요한 일을 해냅니다. 다른 모든 것과 같은 문장에서 허용되며 별도의 모드는 없습니다. `LET`은 유효 범위의 어떤 것도 버리지 않으면서 계산된 값을 더합니다. `WITH`는 계속 쓰고 싶은 변수를 전부 나열하게 하지만, `LET`은 그 전부를 그대로 둡니다: ```cypher MATCH (m:movie) LET decade = (m.year / 10) * 10 FILTER decade = 2010 RETURN m.title AS title, m.year AS year, decade ORDER BY year; ``` ``` title | year | decade ---------------------+------+-------- "Inception" | 2010 | 2010 "The Avengers" | 2012 | 2010 "Elysium" | 2013 | 2010 "Interstellar" | 2014 | 2010 "Blade Runner 2049" | 2017 | 2010 ``` `FILTER`는 행을 걸러 내는 일만 하므로, 필터를 더 이상 `WITH`에 붙여야 하지 않습니다. 조건을 그냥 쓰거나 `FILTER WHERE` 표기를 씁니다. `NEXT`는 한 쿼리를 다른 쿼리의 결과에 이어 붙입니다. `WITH` 없이 투영 뒤에서 걸러 내는 방법이 이것입니다: ```cypher MATCH (m:movie) RETURN m.genre AS genre, count(*) AS films NEXT FILTER films > 3 RETURN genre, films ORDER BY genre; ``` ``` genre | films ----------+------- "Action" | 7 "Sci-Fi" | 10 ``` `FINISH`는 아무것도 투영하지 않는 쿼리를 끝냅니다. 효과만을 위해 실행하는 문장에서 `RETURN`에 대응하는 것입니다: ```cypher MATCH (m:movie {title: 'The Matrix'}) FINISH; ``` ``` -- (0 rows) ``` 각각 레퍼런스 페이지가 있습니다. [`LET`](../reference/clauses/let.md), [`FILTER`](../reference/clauses/filter.md), [`NEXT`](../reference/clauses/next.md), [`FINISH`](../reference/clauses/finish.md)입니다. {#composing-subqueries} ## 서브쿼리 `CALL { }`은 들어오는 행마다 서브쿼리를 한 번 실행하고 그 결과를 다시 조인합니다. 행마다 원하는 것이 표현식이 아니라 쿼리 전체일 때 쓰십시오. 행별 집계나 행별 상위 N개 같은 경우입니다. 이것에 관한 모든 것이 평범하지만 한 가지가 다르고, 그것은 오류를 내는 대신 답을 바꿔 버립니다. ```{important} 가져오기 목록이 없는 `CALL { }`은 바깥 변수를 **가져오지 않습니다.** 본문 안에서 쓴 이름이 바깥에도 있다면, 그 이름은 제약이 없는 새 변수로 다시 바인딩되므로 서브쿼리가 현재 행이 아니라 그래프 전체를 대상으로 실행됩니다. 가져오기 목록을 항상 쓰십시오. ``` 차이는 이렇습니다. `m`이 바깥에서 바인딩되고 본문도 `m`을 언급하는데, 답이 모든 영화에 대해 같습니다: ```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 3; ``` ``` film | cast_size ---------------------+----------- "Avatar" | 25 "Blade Runner" | 25 "Blade Runner 2049" | 25 ``` 25는 그래프에 있는 모든 `acted_in` 간선의 수입니다. 서브쿼리의 `m`은 바깥의 `m`이 아니었습니다. 가져올 것을 이름으로 적으면 바로잡힙니다: ```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 3; ``` ``` film | cast_size ---------------------+----------- "Avatar" | 1 "Blade Runner" | 1 "Blade Runner 2049" | 2 ``` `CALL (*) { ... }`은 유효 범위의 모든 것을 가져오고, `CALL { WITH m ... }`은 같은 것의 옛 표기입니다. 세 형태 모두 같은 답을 냅니다. 서브쿼리는 바깥에서 이미 바인딩된 이름을 돌려줄 수 없습니다: ```cypher MATCH (m:movie) CALL (m) { MATCH (p:person)-[:acted_in]->(m) RETURN m AS m } RETURN count(*); ``` ``` ERROR: variable "m" returned by the CALL subquery is already bound in the outer query ``` 서브쿼리 본문에는 쓰기 절을 쓸 수 없습니다. `OPTIONAL CALL`이 왼쪽 외부 조인 형태이며, 본문이 아무것도 내놓지 않아도 행을 만듭니다. [`CALL { }`](../reference/clauses/call-subquery.md)과 [`OPTIONAL CALL`](../reference/clauses/optional-call.md)을 참고하십시오. {#composing-subqueries-as-values} ### 값으로서의 서브쿼리 여섯 개의 값 표현식이 서브쿼리를 받아 `WHERE`나 `RETURN`에 넣을 수 있는 것으로 줄여 줍니다. 가져오기 목록이 필요하지 않습니다. 바깥 변수가 별도의 쿼리가 아니라 표현식으로서 유효 범위에 있기 때문입니다. `EXISTS { }`는 서브쿼리가 행을 하나라도 반환하면 참입니다: ```cypher MATCH (p:person) WHERE EXISTS { MATCH (p)-[:directed]->(:movie) } RETURN count(p) AS directors; ``` ``` directors ----------- 12 ``` `COUNT { }`는 서브쿼리가 반환하는 행의 수입니다: ```cypher MATCH (m:movie) WHERE COUNT { MATCH (:person)-[:acted_in]->(m) } > 2 RETURN m.title AS title ORDER BY title; ``` ``` title -------------- "The Matrix" ``` `COLLECT { }`와 `ARRAY { }`는 서브쿼리의 단일 컬럼을 리스트로 모읍니다: ```cypher MATCH (m:movie {title: 'The Matrix'}) RETURN COLLECT { MATCH (p:person)-[:acted_in]->(m) RETURN p.name ORDER BY p.name } AS players; ``` ``` players ------------------------------------------------------------ ["Carrie-Anne Moss", "Keanu Reeves", "Laurence Fishburne"] ``` `VALUE { }`는 그 단일 값을 취합니다: ```cypher MATCH (m:movie {title: 'The Matrix'}) RETURN VALUE { MATCH (p:person)-[:directed]->(m) RETURN p.name ORDER BY p.name LIMIT 1 } AS a_director; ``` ``` a_director ------------------ "Lana Wachowski" ``` 그리고 `x IN { }`은 그 결과에 속하는지를 검사합니다: ```cypher MATCH (p:person) WHERE p.name IN { MATCH (d:person)-[:directed]->(:movie {title: 'Inception'}) RETURN d.name } RETURN p.name AS name; ``` ``` name --------------------- "Christopher Nolan" ``` 여섯 중 가장 많이 쓸 것은 `EXISTS { }`입니다. 패턴만으로는 조건이 되지 않기 때문입니다. [그래프 읽기](#reading-asking-whether-a-pattern-exists)를 참고하십시오. 여섯 모두 [서브쿼리 표현식](../reference/clauses/subquery-expressions.md)에 정의돼 있습니다. {#composing-where-to-go-next} ## 다음에 갈 곳 * [경로](paths.md) — 길이를 미리 모르는 패턴입니다. * [SQL과 Cypher 섞기](combining-sql-and-cypher.md) — CTE, 보통 테이블과의 조인, 뷰입니다. * [쿼리 튜닝](performance.md) — 파이프라인이 실행 계획으로는 어떻게 보이는지입니다.