{#writing-writing-a-graph} # 그래프 쓰기 쓰기 절에는 이름을 붙일 만한 자체 패턴 일치 기능이 없습니다. 앞에서 온 것이 무엇이든 그 행들을 받아 각 행에 대해 무언가를 합니다. 그래서 맨 처음의 `CREATE`를 빼면 모든 쓰기가 같은 모양을 따릅니다. 먼저 찾고, 그다음 씁니다. 이 페이지의 계획이 그것뿐입니다. 필요한 순서대로 쓰기 절을 훑습니다. `CREATE`, `MERGE`, `SET`, `REMOVE`, `DELETE`입니다. 그리고 트랜잭션이 그래프에 무엇을 하는지로 마칩니다. 각각의 정식 구문은 레퍼런스에 있습니다. [`CREATE`](../reference/clauses/create.md), [`MERGE`](../reference/clauses/merge.md), [`SET`](../reference/clauses/set.md), [`REMOVE`](../reference/clauses/remove.md), [`DELETE`](../reference/clauses/delete.md)입니다. 예제는 [예제 데이터셋](../get_started/example-dataset.md)에서 실행되며 실행 뒤에는 처음 상태로 되돌려 놓습니다. 그래서 페이지를 따라 내려가며 실행해도 다른 페이지가 출력하는 개수와 계속 맞습니다. {#writing-making-something-new} ## 새로 만들기 기존의 무엇도 바인딩하지 않는 패턴을 준 `CREATE`는 정점을 만듭니다: ```cypher CREATE (p:person {name: 'Gloria Foster'}) RETURN p; ``` ``` p ---------------------------------------- person[4.153]{"name": "Gloria Foster"} ``` `RETURN`은 생략할 수 있지만 레이블은 그렇지 않습니다. 레이블 없이 만든 정점은 `ag_vertex`에 놓이고, 간선에는 항상 레이블이 있어야 합니다. 결과에 나온 id는 그 레이블의 시퀀스가 어디까지 갔는지에 따른 값이므로, 직접 실행하면 다른 값이 나옵니다. 이미 존재하는 두 대상을 연결하려면 먼저 그것들을 찾고 그다음 간선을 만듭니다. 일치시키지 않은 정점에 간선을 쓰는 방법은 없습니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}), (m:movie {title: 'The Matrix'}) CREATE (p)-[r:acted_in {role: 'The Oracle'}]->(m) RETURN r; ``` ``` r ------------------------------------------------- acted_in[5.71][4.153,3.1]{"role": "The Oracle"} ``` 간선은 `label[id][start,end]{properties}` 형태로 출력됩니다. 여기서 `(p)`와 `(m)`에는 레이블도 속성 맵도 없습니다. 이미 바인딩된 변수를 가리키는 참조이며, 여기에 레이블을 붙이면 그 대신 *새* 정점을 만들게 됩니다. 요소가 하나보다 긴 `CREATE` 패턴은 그 안의 모든 것을 만듭니다: ```cypher CREATE (d:person {name: 'Marc Caro'})-[:directed]->(f:movie {title: 'The City of Lost Children', year: 1995}) RETURN d.name AS director, f.title AS film; ``` ``` director | film -------------+----------------------------- "Marc Caro" | "The City of Lost Children" ``` `INSERT`는 같은 절의 ISO/IEC 39075 표기이며 같은 패턴을 받습니다: `INSERT (:person {name: 'Ada Lovelace'})`. [`INSERT`](../reference/clauses/insert.md)를 참고하십시오. {#writing-making-something-only-if-it-is-not-there} ## 없을 때만 만들기 `MERGE`는 자기 패턴을 일치시키고, 일치하는 것이 없을 때만 그것을 만듭니다: ```cypher MERGE (p:person {name: 'Gloria Foster'}) RETURN p; ``` ``` p ---------------------------------------- person[4.153]{"name": "Gloria Foster"} ``` 이것은 두 번째 정점을 만든 것이 아니라 앞 절에서 만든 정점을 찾은 것입니다. *무엇을* 기준으로 일치시키는지 주의하십시오. 맵에 있는 모든 속성을 포함한 패턴 전체입니다. 속성을 하나 더하면 그 패턴은 이미 가진 정점을 설명하지 않게 되므로, `MERGE`는 그 패턴이 설명하는 정점을 새로 만듭니다: ```cypher MERGE (p:person {name: 'Gloria Foster', born: 1933}) RETURN p; ``` ``` p ------------------------------------------------------ person[4.155]{"born": 1933, "name": "Gloria Foster"} ``` 이제 둘이 됐습니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}) RETURN id(p) AS id, p.born AS born ORDER BY id; ``` ``` id | born -------+------ 4.153 | 4.155 | 1933 ``` 그러므로 어떤 대상에 대해 아는 모든 것을 맵에 담아 `MERGE`하는 것은 중복 생성기입니다. 대상을 *식별하는* 키만으로 `MERGE`하고 나머지 값은 그 뒤에 적용하는 것이 멱등합니다. `ON CREATE SET`과 `ON MATCH SET`이 그것을 위해 있습니다: ```cypher MERGE (p:person {name: 'Joe Pantoliano'}) ON CREATE SET p.born = 1951, p.seen = 1 ON MATCH SET p.seen = p.seen + 1 RETURN p; ``` ``` p ------------------------------------------------------------------ person[4.156]{"born": 1951, "name": "Joe Pantoliano", "seen": 1} ``` 다시 실행하면 `ON MATCH` 분기만 발동합니다: ``` p ------------------------------------------------------------------ person[4.156]{"born": 1951, "name": "Joe Pantoliano", "seen": 2} ``` `MERGE`는 간선에도 같은 방식으로 동작하며, 간선 패턴을 일치시킨다는 것은 그 레이블, 방향, 속성 맵, *그리고* 양 끝점까지 일치시킨다는 뜻입니다. 끝점은 먼저 `MATCH`로 바인딩해서 `MERGE`가 간선만 판단하게 하십시오. 아래 간선은 이미 있으므로 아무것도 만들어지지 않습니다: ```cypher MATCH (p:person {name: 'Christopher Nolan'}), (m:movie {title: 'Inception'}) MERGE (p)-[r:directed]->(m) RETURN r; ``` ``` r -------------------------- directed[6.8][4.8,3.5]{} ``` 계속하기 전에 Gloria Foster를 하나로 되돌립니다: ```cypher MATCH (p:person {name: 'Gloria Foster', born: 1933}) DELETE p; ``` 식별 속성에 걸어 둔 고유성 제약이 이 규율의 오래가는 형태입니다. 올바른 `MERGE`를 쓰는 데 의존하지 않고 중복 자체를 거부하기 때문입니다. [인덱스와 제약](#indexes-and-constraints-constraints)을 참고하십시오. {#writing-changing-properties} ## 속성 바꾸기 속성 하나에 대한 `SET`은 그 속성을 추가하거나 교체합니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}) SET p.born = 1933 RETURN p; ``` ``` p ------------------------------------------------------ person[4.153]{"born": 1933, "name": "Gloria Foster"} ``` `SET p += { ... }`는 맵을 병합해 넣으며, 키를 추가하고 겹치는 키는 덮어씁니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}) SET p += {died: 2001, city: 'Chicago'} RETURN p; ``` ``` p --------------------------------------------------------------------------------------- person[4.153]{"born": 1933, "city": "Chicago", "died": 2001, "name": "Gloria Foster"} ``` `SET p = { ... }`는 속성 맵 전체를 교체합니다. 새 맵에 없는 것은 모두 사라집니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}) SET p = {name: 'Gloria Foster', born: 1933} RETURN p; ``` ``` p ------------------------------------------------------ person[4.153]{"born": 1933, "name": "Gloria Foster"} ``` `+=`와 `=`의 차이는 `UPDATE ... SET one_column`과 행 전체를 교체하는 것의 차이이며, 잘못 입력하기 쉽습니다. null을 할당하는 것은 null을 저장하는 것이 아닙니다. 그 속성을 지웁니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}) SET p.born = NULL RETURN p; ``` ``` p ---------------------------------------- person[4.153]{"name": "Gloria Foster"} ``` 그러므로 null로 설정한 속성과 애초에 없던 속성은 같은 상태이고, 그 뒤에 둘을 구별할 방법이 없습니다. `allow_null_properties`가 이 동작을 바꿔 null을 저장하며, 기본적으로 꺼져 있습니다. `REMOVE`는 같은 일을 명시적으로 표기한 것입니다: ```cypher MATCH (p:person {name: 'Joe Pantoliano'}) REMOVE p.seen RETURN p; ``` ``` p ------------------------------------------------------- person[4.156]{"born": 1951, "name": "Joe Pantoliano"} ``` 간선의 속성은 정점의 속성과 똑같이 씁니다: ```cypher MATCH (:person {name: 'Gloria Foster'})-[r:acted_in]->(:movie {title: 'The Matrix'}) SET r.credited = true RETURN r; ``` ``` r ------------------------------------------------------------------- acted_in[5.71][4.153,3.1]{"role": "The Oracle", "credited": true} ``` ```{note} 승격된 속성은 "속성은 무엇이든 담을 수 있다"의 유일한 예외입니다. 어떤 키 뒤에 타입 있는 컬럼이 놓이면, JSON 타입이 그 컬럼에 맞지 않는 쓰기는 중단됩니다. [속성을 컬럼으로 승격하기](#typed-columns-what-promotion-costs-you)를 참고하십시오. ``` {#writing-removing-things} ## 지우기 `DELETE`는 일치시킨 것을 지우고 그것을 돌려주므로, 무엇이 사라졌는지 볼 수 있습니다: ```cypher MATCH (p:person {name: 'Joe Pantoliano'}) DELETE p RETURN p; ``` ``` p ------------------------------------------------------- person[4.156]{"born": 1951, "name": "Joe Pantoliano"} ``` 아직 간선이 남아 있는 정점은 지울 수 없습니다. 끝점이 없는 간선은 존재할 수 없기 때문입니다: ```cypher MATCH (p:person {name: 'Gloria Foster'}) DELETE p; ``` ``` ERROR: Vertex with connected edges cannot be removed HINT: Use DETACH DELETE or remove connected edges before deleting the vertex. ``` 간선을 직접 지우거나: ```cypher MATCH (:person {name: 'Gloria Foster'})-[r:acted_in]->() DELETE r RETURN r; ``` ``` r ------------------------------------------------------------------- acted_in[5.71][4.153,3.1]{"role": "The Oracle", "credited": true} ``` `DETACH DELETE`를 쓰십시오. 이것은 정점을 그에 닿아 있는 모든 간선과 함께 지웁니다: ```cypher MATCH (d:person {name: 'Marc Caro'}) DETACH DELETE d; MATCH (f:movie {title: 'The City of Lost Children'}) DETACH DELETE f; MATCH (p:person {name: 'Gloria Foster'}) DELETE p; ``` 데이터셋이 원래대로 돌아왔습니다: ```cypher MATCH (n) RETURN count(n) AS elements; ``` ``` elements ---------- 46 ``` 이 거부는 불편함이 아니라 안전장치입니다. 허브 정점에 `DETACH DELETE`를 쓰면 의도한 것보다 훨씬 많은 것이 지워질 수 있으므로, 무엇이든 지우기 전에 잃으려는 간선을 일치시켜 세어 보십시오. {#writing-reading-what-you-just-wrote} ## 방금 쓴 것을 읽기 쓰기 절이 문장의 끝은 아닙니다. 그 출력 행은 파이프라인을 따라 계속 내려가고, 그 뒤의 읽기는 쓰기가 남겨 놓은 상태의 그래프를 봅니다: ```cypher CREATE (:person {name: 'Gloria Foster'}) WITH 1 AS done MATCH (p:person {name: 'Gloria Foster'}) RETURN count(p) AS found; ``` ``` found ------- 1 ``` 플래너는 다음 읽기가 실행되기 전에 쓰기의 효과를 구체화해서 그것을 보장합니다. 이것이 "eager" 계획이며, `EXPLAIN`도 그렇게 표시합니다. 그 비용과 그것을 금지하는 설정은 [쿼리 튜닝](#performance-the-settings-that-change-plan-shape)이 다룹니다. {#writing-transactions} ## 트랜잭션 그래프는 데이터베이스 안의 테이블이므로, 그래프에 쓰는 것은 평범한 트랜잭션 쓰기입니다. 켜야 할 것도 없고 두 번째 커밋을 생각할 필요도 없습니다. 한 트랜잭션 안의 Cypher 쓰기와 SQL 쓰기는 함께 커밋되거나 함께 롤백됩니다. ```sql CREATE TABLE import_log (what text, at timestamptz DEFAULT now()); ``` ```cypher BEGIN; CREATE (:person {name: 'Gloria Foster'}); INSERT INTO import_log (what) VALUES ('added Gloria Foster'); MATCH (p:person {name: 'Gloria Foster'}) RETURN count(p) AS found; ``` ``` found ------- 1 ``` 트랜잭션 안에서는 두 쓰기가 모두 있습니다. 롤백하면 둘 다 없습니다: ```cypher ROLLBACK; MATCH (p:person {name: 'Gloria Foster'}) RETURN count(p) AS found; ``` ``` found ------- 0 ``` ```sql SELECT count(*) AS logged FROM import_log; ``` ``` logged -------- 0 ``` ```sql DROP TABLE import_log; ``` PostgreSQL이라는 점에서 따라오는 나머지 모든 것이 여기에도 따라옵니다. 격리 수준, 세이브포인트, 레이블 테이블에 대한 `SELECT ... FOR UPDATE`, 같은 정점에 쓰는 두 세션 사이의 교착 상태 탐지가 그렇습니다. 원한다면 대량 적재를 한 트랜잭션으로 할 수 있고, 트랜잭션 안의 `CREATE GRAPH`는 다른 DDL과 마찬가지로 롤백됩니다. 반대 방향으로는 관문이 둘 있습니다. SQL `INSERT`, `UPDATE`, `DELETE`, `MERGE`로 레이블에 행을 쓰는 것은 `enable_graph_dml`이 켜져 있지 않으면 거부됩니다. `ALTER TABLE`로 레이블의 모양을 바꾸는 것은 `enable_graph_ddl`이 켜져 있지 않으면 거부됩니다. 어느 쪽이든 그래프 카탈로그가 더 이상 사실이 아닌 것을 설명하는 상태로 만들 수 있습니다. [그래프가 어떻게 저장되는가](#graph-on-postgresql-what-is-deliberately-closed-off)를 참고하십시오. {#writing-where-to-go-next} ## 다음에 갈 곳 * [데이터 적재](../guides/loading-data.md) — 기존 테이블에 대한 `LOAD FROM`을 포함한 대량 적재입니다. * [쿼리 조합하기](composing.md) — `WITH`, `UNWIND`, 집계입니다. 모두 쓰기 절 앞에서 유용합니다. * [보안](../operations/security.md) — 레이블에 대한 권한 부여와 행 수준 보안입니다.