{#cypher-compatibility-cypher-compatibility} # Cypher 호환성 AgensGraph의 Cypher는 다른 구현과 충분히 비슷해서 대부분의 쿼리가 고치지 않고 넘어옵니다. 그러면서도 몇몇은 실패하거나, 더 나쁘게는 다른 답을 내며 성공할 만큼 다릅니다. 이 부록은 그 차이를, 서버가 내는 오류나 돌려주는 결과와 함께 나열합니다. 옮겨 온 쿼리가 이상하게 굴 때 그것을 알아볼 수 있도록. 모든 예제는 `graph_path`를 `moviekb`로 둔 채 [예제 데이터셋](../get_started/example-dataset.md)을 상대로 실행합니다. 아래의 대부분은 구조에 관한 두 사실로 설명됩니다. 정점은 레이블을 정확히 하나 가집니다. 레이블이 테이블이고 행은 테이블 하나에 살기 때문입니다. 그리고 Cypher 쿼리는 PostgreSQL 쿼리이므로, 렉서와 타입 체계와 오류 코드가 PostgreSQL의 것입니다. {#cypher-compatibility-constructs-that-do-not-exist} ## 존재하지 않는 구문 {#cypher-compatibility-several-types-on-one-relationship-pattern} ### 관계 패턴 하나에 여러 종류 관계 패턴은 종류를 많아야 하나 받습니다: ```cypher MATCH (p:person)-[r:acted_in|directed]->(m:movie) RETURN count(r); ``` ``` ERROR: multiple types for relationship not supported ``` 가변 길이(`-[r:acted_in|directed*1..2]->`)에서도, `shortestpath()` 안에서도 같은 오류가 돌아옵니다. `-[r:acted_in|:directed]->` 표기는 그냥 구문 오류입니다. 대신 종류 없는 간선을 일치시킨 뒤 레이블을 검사하십시오: ```cypher MATCH (p:person)-[r]->(m:movie) WHERE label(r) IN ['acted_in','directed'] RETURN count(r); ``` ``` count ------- 46 ``` 정점 레이블 선택도 같은 식으로 실패합니다. `MATCH (n:person|movie)`는 `|`에서 구문 오류이고, GQL 표준이 정의하는 다른 레이블 표현식 연산자 `:person&movie`·`:!person`·`:%`도 마찬가지입니다. {#cypher-compatibility-a-label-set-on-a-vertex} ### 정점의 레이블 집합 레이블의 집합이란 것이 없고, 정점이 가진 레이블을 바꿀 방법도 없습니다. 아래는 모두 두 번째 콜론에서 구문 오류입니다: ```cypher MATCH (n:person {name:'Keanu Reeves'}) SET n:actor RETURN n; MATCH (n:person {name:'Keanu Reeves'}) REMOVE n:actor RETURN n; MATCH (n:person:movie) RETURN count(n); CREATE (:aa:bb); ``` ``` ERROR: syntax error at or near ":" ``` 그 자리를 레이블 상속이 대신합니다. `CREATE VLABEL actor INHERITS (person)`은 부모에 대한 일치가 함께 반환하는 별도의 레이블을 만듭니다. 그래서 `MATCH (n:person)`은 배우도 찾고, `MATCH (n:person ONLY)`은 그들을 뺍니다. 정점은 배우로 승격되는 것이 아니라 처음부터 배우로 만들어집니다. [레이블 상속](../concepts/label-inheritance.md)을 참고하십시오. {#cypher-compatibility-foreach} ### FOREACH ```cypher FOREACH (x IN [1,2] | CREATE (:zz {v:x})); ``` ``` ERROR: syntax error at or near "FOREACH" ``` `UNWIND`와 GQL의 `FOR` 문장이 둘 다 리스트를 순회합니다: ```cypher UNWIND [1,2] AS x RETURN x; ``` ``` x --- 1 2 ``` 둘 중 어느 것 뒤에도 쓰기 절이 올 수 있으므로, `UNWIND list AS x CREATE (…)`가 `FOREACH` 본문이 담는 것을 대신합니다. {#cypher-compatibility-create-unique} ### CREATE UNIQUE ```cypher MATCH (a:person {name:'Keanu Reeves'}), (b:movie {title:'Avatar'}) CREATE UNIQUE (a)-[:acted_in]->(b); ``` ``` ERROR: syntax error at or near "UNIQUE" ``` 패턴을 일치시키고 일치가 없을 때만 만드는 `MERGE`를 쓰십시오. 여기서는 간선이 이미 있으므로 `MERGE`가 하나를 더 만드는 대신 있는 것을 반환합니다: ```cypher MATCH (a:person {name:'Sam Worthington'}), (b:movie {title:'Avatar'}) MERGE (a)-[r:acted_in]->(b) RETURN id(r); ``` ``` id ------ 5.21 ``` {#cypher-compatibility-load-csv} ### LOAD CSV ```cypher LOAD CSV WITH HEADERS FROM 'file:///tmp/movies.csv' AS row RETURN row; ``` ``` ERROR: syntax error at or near "CSV" ``` 가져오기 경로는 테이블을 거칩니다. `COPY`나 외부 테이블이 CSV를 릴레이션에 넣고, `LOAD FROM`이 그 행을 그래프 요소로 바꿉니다: ```sql CREATE TABLE people_src (name text, age int); INSERT INTO people_src VALUES ('Ann', 30), ('Bo', 41); ``` ```cypher LOAD FROM people_src AS r CREATE (:person {name: r.name, age: r.age}); ``` ``` UPDATE 2 ``` ```sql DROP TABLE people_src; ``` 외부 테이블 래퍼를 포함한 전체 절차는 [데이터 적재](../guides/loading-data.md)를 참고하십시오. {#cypher-compatibility-the-anonymous-arrows----and---} ### 익명 화살표 `-->` 와 `--` `--`는 SQL에서와 마찬가지로 주석을 시작하므로, 익명 화살표는 문장을 조용히 끝내 버립니다: ```cypher MATCH (a:movie)-->(b) RETURN count(*); ``` ``` ERROR: Cypher query must end with RETURN, FINISH or update clause ``` `<--`는 다르게 실패합니다. 주석이 시작되기 전에 `<`가 먼저 소비되기 때문입니다: ```cypher MATCH (a:movie)<--(b) RETURN count(*); ``` ``` ERROR: syntax error at end of input ``` 대괄호를 쓴 형태로 적으십시오. `-[]->`·`<-[]-`·`-[]-` 가 모두 동작합니다: ```cypher MATCH (a:movie)-[]->(b) RETURN count(*); ``` ``` count ------- 11 ``` {#cypher-compatibility-map-projection} ### 맵 투영 ```cypher MATCH (m:movie {title:'Avatar'}) RETURN m {.title, .year}; ``` ``` ERROR: syntax error at or near "{" ``` 맵을 직접 만드십시오. 맵 생성자는 평범한 표현식입니다: ```cypher MATCH (m:movie {title:'Avatar'}) RETURN {title: m.title, year: m.year}; ``` ``` ?column? ----------------------------------- {"year": 2009, "title": "Avatar"} ``` `properties(m)`이 맵 전체를 반환하므로 `m {.*}`를 대신합니다. {#cypher-compatibility-pattern-comprehension} ### 패턴 컴프리헨션 ```cypher MATCH (m:movie {title:'Avatar'}) RETURN [(m)<-[:acted_in]-(p) | p.name]; ``` ``` ERROR: syntax error at or near ":" ``` 서브쿼리 표현식이 같은 일을 하며, 언어의 나머지 부분과 더 비슷하게 읽힙니다: ```cypher MATCH (m:movie {title:'Avatar'}) RETURN COLLECT { MATCH (m)<-[:acted_in]-(p) RETURN p.name }; ``` ``` array --------------------- ["Sam Worthington"] ``` {#cypher-compatibility-reduce-extract-and-filter} ### `reduce()`·`extract()`·`filter()` 셋 다 없습니다. ```cypher RETURN reduce(acc = 0, x IN [1,2,3] | acc + x); ``` ``` ERROR: variable does not exist ``` `extract`와 `filter`는 이미 다른 데 쓰이고 있으므로 `extract()`와 `filter()`는 대신 구문 오류입니다. `extract`는 SQL의 `EXTRACT(field FROM source)`가, `filter`는 GQL의 `FILTER` 문장이 차지하고 있습니다. 리스트 컴프리헨션이 `extract()`와 `filter()`를 대신하며, 세 가지 표기가 모두 동작합니다: ```cypher RETURN [x IN [1,2,3] | x * 10], [x IN [1,2,3] WHERE x > 1], [x IN [1,2,3] WHERE x > 1 | x * 10]; ``` ``` ?column? | ?column? | ?column? --------------+----------+---------- [10, 20, 30] | [2, 3] | [20, 30] ``` 접기(fold)가 필요하면 Cypher 결과 위에서 SQL로 집계하거나, 그 접기가 합이라면 `sum()`을 쓰십시오. {#cypher-compatibility-a-bare-pattern-as-a-predicate} ### 패턴 자체를 조건으로 쓰기 패턴은 불리언 표현식이 아닙니다: ```cypher MATCH (m:movie) WHERE (m)<-[:acted_in]-() RETURN count(m); ``` ``` ERROR: syntax error at or near ":" ``` `exists()`로, 또는 GQL의 `EXISTS { }` 서브쿼리 표현식으로 감싸십시오: ```cypher MATCH (m:movie) WHERE exists((m)<-[:acted_in]-()) RETURN count(m); ``` ``` count ------- 18 ``` {#cypher-compatibility-backtick-quoted-identifiers} ### 백틱으로 감싼 식별자 백틱은 따옴표 문자가 아닙니다: ```cypher MATCH (m:`movie`) RETURN count(m); ``` ``` ERROR: syntax error at or near "`" ``` 식별자는 SQL 방식대로 큰따옴표로 감쌉니다. `MATCH (m:"movie")` 입니다. 2.18에서 예약어가 된 단어들에 이것이 중요합니다. [키워드](keywords.md)를 참고하십시오. {#cypher-compatibility-temporal-spatial-and-geodistance-constructors} ### 시간·공간·거리 생성자 `duration()`·`distance()`·`haversin()`은 없습니다: ```cypher RETURN haversin(1.0); ``` ``` ERROR: function haversin(numeric) does not exist HINT: No function matches the given name and argument types. You might need to add explicit type casts. ``` `point()`와 `timestamp()`는 더 헷갈리게 실패합니다. 각각 PostgreSQL의 이름과 부딪히기 때문입니다. `point({x:1, y:2})`는 PostgreSQL의 기하 타입 `point`를 찾는데, 그 오버로드는 실수 쌍과 다른 기하 타입을 받으므로 호출이 없는 것이 아니라 모호해집니다: ```cypher RETURN point({x:1, y:2}); ``` ``` ERROR: function point(jsonb) is not unique HINT: Could not choose a best candidate function. You might need to add explicit type casts. ``` `timestamp()`는 구문 오류입니다. `timestamp`가 타입 이름이기 때문입니다. `datetime()`은 있지만 다른 함수이며, 특히 조심해야 할 것입니다. 시간대나 날짜 구성 요소의 목록을 받고 ISO-8601 문자열은 결코 받지 않습니다. 타임스탬프 문자열을 넘기면 그것을 시간대로 읽습니다: ```cypher RETURN datetime('2020-01-01T00:00:00'); ``` ``` ERROR: time zone "2020-01-01T00:00:00" not recognized ``` 시간대 이름이나 날짜 구성 요소 아홉 개는 받습니다: ```cypher RETURN datetime(2020, 1, 1, 0, 0, 0, 0, 0, 'UTC'); ``` ``` datetime ----------------------- "2020-01-01T00:00:00" ``` 문자열에서 타임스탬프를 얻으려면 형 변환하십시오. `'2020-01-01'::date`, `'2020-01-01 00:00'::timestamptz` 입니다. `CURRENT_DATE`와 `CURRENT_TIMESTAMP`는 키워드만으로 동작합니다. 기간은 PostgreSQL의 interval이며 SQL에서 `::interval`로 닿습니다. 공간 관련 작업은 PostgreSQL 확장을 거치며, 벡터 검색과 같은 방식으로 그래프 쿼리에서 닿습니다. [벡터 검색](../guides/search/vector.md)을 참고하십시오. {#cypher-compatibility-the-db-procedures} ### `db.*` 프로시저 `db` 네임스페이스는 없습니다: ```cypher CALL db.labels(); ``` ``` ERROR: schema "db" does not exist ``` 같은 물음에는 카탈로그가 답합니다. `ag_label`이 그래프의 레이블을 나열합니다. [그래프 카탈로그](../system_catalogs/catalogs.md)를 참고하십시오. {#cypher-compatibility-profile} ### PROFILE ```cypher PROFILE MATCH (m:movie) RETURN count(m); ``` ``` ERROR: syntax error at or near "PROFILE" ``` `EXPLAIN`이 PostgreSQL의 옵션과 함께 동작합니다. `EXPLAIN (ANALYZE, BUFFERS) MATCH …` 입니다. {#cypher-compatibility-behaviour-that-differs} ## 동작이 다른 것 {#cypher-compatibility-every-column-a-return-produces-is-jsonb} ### RETURN이 내놓는 모든 컬럼은 jsonb다 속성 읽기는 `jsonb` 값이고 투영을 거쳐도 그대로입니다. 그래서 문자열은 JSON 따옴표와 함께 찍힙니다: ```cypher MATCH (m:movie {title:'Avatar'}) RETURN m.title, pg_typeof(m.title); ``` ``` title | pg_typeof ----------+----------- "Avatar" | "jsonb" ``` `::text`는 따옴표를 없애지 않습니다. 따옴표를 포함해 jsonb 값을 그려 낼 뿐입니다. 평범한 스칼라를 얻으려면 Cypher를 감싼 SQL에서 뽑아내십시오: ```psql SELECT t #>> '{}' AS title FROM (MATCH (m:movie) RETURN m.title AS t) q ORDER BY title LIMIT 1; ``` ``` title ---------- Avatar ``` [SQL과 Cypher](../querying/combining-sql-and-cypher.md)를 참고하십시오. {#cypher-compatibility-order-by-2-sorts-nothing} ### `ORDER BY 2`는 아무것도 정렬하지 않는다 정렬 키의 정수는 출력 컬럼 번호가 아니라 상수입니다. 모든 행을 같은 상수로 정렬하면 순서가 그대로 남고, 아무것도 경고해 주지 않습니다: ```cypher MATCH (m:movie) RETURN m.title AS t, m.year AS y ORDER BY 2 LIMIT 5; ``` ``` t | y ----------------------------+------ "The Matrix Resurrections" | 2021 "Inception" | 2010 "Interstellar" | 2014 "Avatar" | 2009 "Blade Runner" | 1982 ``` 키의 이름을 대십시오. `ORDER BY y`는 기대한 대로 정렬합니다. 정수 형태는 SQL에서는 동작하며, 거기서 정수 정렬 키는 그 자리의 출력 컬럼을 뜻합니다. 그러므로 Cypher를 `SELECT … ORDER BY 2`로 감싸도 정렬됩니다. ```cypher MATCH (m:movie) RETURN m.title AS t, m.year AS y ORDER BY y LIMIT 5; ``` ``` t | y ------------------------------+------ "Blade Runner" | 1982 "The Terminator" | 1984 "Total Recall" | 1990 "Terminator 2: Judgment Day" | 1991 "Jurassic Park" | 1993 ``` {#cypher-compatibility-nid-is-not-the-identity} ### `n.id`는 식별자가 아니다 `id`는 평범한 속성 이름입니다. 무엇인가 값을 써 두지 않았다면 없습니다: ```cypher MATCH (m:movie {title:'Avatar'}) RETURN m.id, id(m); ``` ``` id | id ----+----- | 3.7 ``` `id(m)`이 식별자, 즉 *labid*`.`*locid* 로 찍히는 `graphid`를 반환합니다. 그 두 부분이 무엇을 담는지는 [제한](limits.md)을 참고하십시오. {#cypher-compatibility-a-bare-call---answers-a-different-question} ### 그냥 쓴 `CALL { }`은 다른 물음에 답한다 서브쿼리 본문은 바깥의 변수를 보지 못합니다. 그 이름이 아무 제약 없는 새 변수로 다시 묶이므로, 본문은 지금 행이 아니라 그래프 전체를 상대로 실행됩니다: ```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` 간선입니다. 가져올 변수 목록을 적으면 같은 쿼리가 올바릅니다: ```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 ``` ```{important} `CALL` 서브쿼리에는 언제나 가져올 변수 목록을 주십시오. `CALL (v) { … }`·`CALL (*) { … }`· `CALL { WITH v … }` 셋 다 올바르고 서로 일치합니다. 그냥 쓴 형태는 이 차이 가운데 오류를 내는 대신 다른 물음에 답하는 유일한 모양입니다. ``` {#cypher-compatibility-shortestpath-endpoints-must-be-named-variables} ### `shortestpath`의 양 끝점은 이름 있는 변수여야 한다 끝점에 패턴을 직접 적는 것은 거부됩니다: ```cypher MATCH p = shortestpath((:movie {title:'The Matrix'})-[:related_to*..4]->(:movie {title:'Inception'})) RETURN length(p); ``` ``` ERROR: node must be a reference to a specific node ``` 양 끝을 먼저 묶으십시오: ```cypher MATCH (a:movie {title:'The Matrix'}), (b:movie {title:'Inception'}) MATCH p = shortestpath((a)-[:related_to*..4]->(b)) RETURN length(p); ``` ``` length -------- 2 ``` 같은 규칙이 `allshortestpaths()`와 `dijkstra()`에도 적용됩니다. {#cypher-compatibility-an-unquoted-label-folds-to-lower-case} ### 따옴표 없는 레이블은 소문자로 내려간다 식별자는 SQL의 접기 규칙을 따르므로, 대소문자를 섞어 쓴 레이블은 소문자로 찾습니다: ```cypher MATCH (m:Movie) RETURN count(m); ``` ``` count ------- 18 ``` `Movie`·`MOVIE`·`movie`는 같은 레이블입니다. 따옴표로 감싼 대소문자 혼용 이름으로 만든 레이블은 쓸 때마다 따옴표로 감싸야 합니다. {#cypher-compatibility-a-condition-takes-only-a-boolean} ### 조건은 불리언만 받는다 속성을 참·거짓처럼 읽는 것은 비어 있지 않은지 검사하는 대신 형 변환 오류를 냅니다: ```cypher MATCH (m:movie {year: 1999}) WHERE m.title RETURN count(m); ``` ``` ERROR: cannot cast "The Matrix" (jsonb type string) to boolean ``` 메시지는 실행기가 처음 닿은 값을 가리키므로, 여러 행에 걸친 제약 없는 `MATCH`는 마침 먼저 읽은 값을 알려 줍니다. `WHERE`·`FILTER`·`AND`·`OR`·`NOT`·`XOR`·`CASE WHEN`과 `dijkstra()`의 한정자에서 그렇습니다. 뜻하는 검사를 그대로 쓰십시오. `WHERE m.title IS NOT NULL`이나 `WHERE m.count <> 0` 처럼 말입니다. 아니면 `::boolean`으로 명시적으로 변환하십시오. {#cypher-compatibility-a-missing-label-is-not-an-error} ### 없는 레이블은 오류가 아니다 존재하지 않는 레이블을 가리키는 패턴은 아무것과도 일치하지 않고 행 수 0을 반환합니다: ```cypher MATCH (n:no_such_label) RETURN count(n); ``` ``` count ------- 0 ``` 그래서 레이블 이름의 오타는 빈 결과처럼 보입니다. 기대한 것이 하나도 나오지 않으면 `ag_label`의 레이블 목록을 확인하십시오. {#cypher-compatibility-size-and-length-are-the-same-function} ### `size()`와 `length()`는 같은 함수다 `size()`는 `length()`의 다른 표기이며, 어느 쪽을 쓰든 출력 컬럼의 이름은 `length`입니다: ```cypher RETURN size([1,2,3]), size('abcd'), length('abcd'); ``` ``` length | length | length --------+--------+-------- 3 | 4 | 4 ``` 패턴에 대한 `size()`는 일치 수를 세며, 컬럼 이름은 `count`입니다: ```cypher MATCH (m:movie {title:'Avatar'}) RETURN size((m)<-[:acted_in]-()); ``` ``` count ------- 1 ``` {#cypher-compatibility-labels-always-returns-one-label} ### `labels()`는 언제나 레이블 하나를 반환한다 `labels()`는 있고 리스트를 반환하지만, 정점이 레이블을 하나 가지므로 그 리스트의 원소는 정확히 하나입니다: ```cypher MATCH (n:person) RETURN labels(n) LIMIT 1; ``` ``` labels ------------ ["person"] ``` `label(n)`은 그 하나의 레이블을 스칼라로 반환하며, 이쪽이 더 나은 형태입니다. 간선에는 `type(r)`과 `label(r)`이 모두 동작합니다. {#cypher-compatibility-collect-over-a-graph-element-returns-a-native-array} ### 그래프 요소에 대한 `collect()`는 네이티브 배열을 반환한다 정점을 모으면 사본의 목록이 아니라 `vertex[]`가 나오므로, 모은 요소를 그대로 쓸 수 있습니다: ```cypher MATCH (m:movie) RETURN pg_typeof(collect(m)); ``` ``` pg_typeof ------------ "vertex[]" ``` ```cypher MATCH (m:movie) WITH collect(m) AS ms RETURN ms[0].title; ``` ``` title ---------------------------- "The Matrix Resurrections" ``` *속성*을 모으는 것은 여전히 jsonb 리스트를 반환합니다. {#cypher-compatibility-a-vertex-with-edges-will-not-delete} ### 간선이 달린 정점은 지워지지 않는다 `DELETE`는 매달린 간선을 남기는 것을 거부합니다: ```cypher MATCH (m:movie {title:'Avatar'}) DELETE m; ``` ``` ERROR: Vertex with connected edges cannot be removed HINT: Use DETACH DELETE or remove connected edges before deleting the vertex. ``` {#cypher-compatibility-a-query-needs-a-graph-to-run-against} ### 쿼리에는 상대할 그래프가 있어야 한다 기본 그래프란 것이 없습니다. `graph_path`가 설정돼 있지 않으면 모든 그래프 문장이 거부됩니다: ```cypher RESET graph_path; MATCH (m:movie) RETURN count(m); ``` ``` ERROR: graph_path is NULL HINT: Use SET graph_path ``` 세션에 설정하거나, `postgresql.conf`에 또는 롤마다 설정하십시오. [설정](../operations/configuration.md)을 참고하십시오: ```cypher SET graph_path = moviekb; ``` {#cypher-compatibility-agensgraph-additions} ## AgensGraph가 더한 것 이것들은 다른 Cypher 구현에 대응하는 것이 없으므로, 이를 쓰는 쿼리는 밖으로 옮겨 가지 못합니다. `MATCH (n:label ONLY)`은 일치를 레이블 하나로 제한하며, 그것을 상속하는 레이블을 뺍니다. `ONLY`가 없으면 부모 레이블에 대한 일치가 자식들도 함께 반환합니다. `SET n += map`은 속성을 바꿔치우는 대신 맵을 속성에 병합합니다: ```cypher MATCH (n:person {name:'Keanu Reeves'}) SET n += {city:'Seoul'} RETURN properties(n); ``` ``` properties --------------------------------------------------------- {"born": 1964, "city": "Seoul", "name": "Keanu Reeves"} ``` `LOAD FROM table AS r CREATE (…)`는 릴레이션의 행에서 그래프 요소를 만들며, 대량 적재가 이렇게 이루어집니다. `dijkstra((a)-[e:related_to]->(b), e.weight)`는 가중치가 있는 최단 경로를 찾습니다. 가중치를 따르므로 가장 싼 길이 홉 수로도 가장 짧은 길일 필요는 없습니다: ```cypher MATCH (a:movie {title:'The Matrix'}), (b:movie {title:'Inception'}) MATCH p = dijkstra((a)-[e:related_to]->(b), e.weight) RETURN [x IN nodes(p) | x.title]; ``` ``` ?column? --------------------------------------------- ["The Matrix", "Total Recall", "Inception"] ``` Cypher 쿼리는 테이블 표현식이고 레이블은 테이블이므로, 한 문장에서 SQL과 Cypher를 섞을 수 있습니다: ```psql SELECT q.title #>> '{}' AS film, r.score FROM (MATCH (p:person)-[:directed]->(m:movie) WHERE p.name = 'Christopher Nolan' RETURN m.title AS title) q JOIN rating r ON r.title = q.title #>> '{}' WHERE r.source = 'critics' ORDER BY r.score DESC; ``` ``` film | score --------------+------- Inception | 8.8 Interstellar | 8.7 ``` {#cypher-compatibility-see-also} ## 관련 항목 - [GQL 적합성](gql-conformance.md) — ISO/IEC 39075의 어느 부분을 지원하는가 - [키워드](keywords.md) — 2.18이 예약한 단어와 그로 인해 깨지는 것 - [SQL과 Cypher](../querying/combining-sql-and-cypher.md) — 두 언어를 넘나드는 방법 전부 - [Cypher, GQL, SQL](../concepts/languages.md) — 세 언어가 어떻게 맞물리는가