{#create-vlabel-create-vlabel} # CREATE VLABEL 정점 레이블을 만듭니다. 그래프의 기반 정점 레이블을 상속하는 `(id, properties)` 테이블이며, 속성을 타입 컬럼으로 승격할 수도 있습니다. {#create-vlabel-synopsis} ## 개요
CREATE [ UNLOGGED ] VLABEL [ IF NOT EXISTS ] label_name [ ( promoted_property [, ... ] ) ]
    [ DISABLE INDEX ]
    [ INHERITS ( parent_label [, ... ] ) ]
    [ WITH ( storage_parameter [= value] [, ... ] ) ]
    [ TABLESPACE tablespace_name ]

CREATE [ UNLOGGED ] VLABEL ONLY label_name ( label_id ) [ option ... ]

where promoted_property is one of:

    column_name data_type GENERATED
    column_name data_type GENERATED ( property_key )
    column_name data_type GENERATED ALWAYS AS ( expression ) [ STORED ]

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

{#create-vlabel-description} ## 설명 정점 레이블은 `graphid` 타입의 `id`와 `jsonb` 타입의 `properties` 컬럼을 가진 테이블입니다. 부모를 지정하지 않으면 그래프의 `ag_vertex`를 상속합니다. 정점 레이블을 만들면 그 id에 번호를 매기는 시퀀스와 `id`에 대한 기본 키도 함께 만들어집니다. 그 배치는 [그래프가 어떻게 저장되는가](../../concepts/graph-on-postgresql.md)가 다룹니다. 레이블은 `graph_path`가 지정한 그래프에 만들어집니다. - **옵션은 순서가 중요합니다.** 보인 순서대로 와야 합니다. `DISABLE INDEX`, 그다음 `INHERITS`, 그다음 `WITH`, 그다음 `TABLESPACE`입니다. 다른 순서는 구문 오류입니다. - `INHERITS`에 지정한 부모는 같은 그래프에 있어야 하고 정점 레이블이어야 합니다. - `IF NOT EXISTS`와 `ONLY`는 서로 다른 형태에 속하므로 함께 쓸 수 없습니다. - Cypher는 필요할 때 레이블을 만듭니다. `film` 레이블이 없는 그래프에 `CREATE (:film)`을 쓰면 그 레이블이 만들어집니다. 옵션, 승격 컬럼, 부모가 필요할 때 `CREATE VLABEL`을 쓰십시오. {#create-vlabel-promoted-properties} ### 승격된 속성 승격된 속성은 `properties` 옆에 있는 실제 타입 컬럼이며, `properties`에서 계산해 저장한 것입니다. 그 속성을 읽으면 JSON 문서를 훑는 대신 그 컬럼으로 갑니다. 세 형태는 값이 어디서 오는지가 다릅니다: - `column_name data_type GENERATED`는 키가 그 컬럼 이름인 속성을 비춥니다. - `column_name data_type GENERATED ( property_key )`는 지정한 키를 비추므로, 컬럼 이름을 다르게 붙일 수 있습니다. 그 키는 식별자여서 소문자로 내려가며, Cypher가 맵 키를 저장하는 방식과 같습니다. - `column_name data_type GENERATED ALWAYS AS ( expression ) STORED`는 그 레이블 자신의 컬럼에 대한 SQL 표현식으로 컬럼을 계산합니다. 어떤 컬럼이 속성을 대신해 답하는지는 어느 형태로 썼는지가 아니라 그 표현식이 결정합니다. 형 변환을 붙이든 붙이지 않든 `properties ->> 'year'`처럼 `properties`에서 키 하나를 그냥 읽는 것은 `ag_label_property`에 기록됩니다. Cypher의 속성 읽기가 참조하는 카탈로그가 그것입니다. 그 읽기를 다른 무엇으로 감싸면 그 컬럼은 평범한 생성 컬럼이 됩니다. SQL은 그것을 읽고, `n.`는 그것을 보지 못합니다. 승격이 언제 할 만한지는 [속성을 컬럼으로 승격하기](../../querying/typed-columns.md)가 설명합니다. 승격된 속성이 될 수 없는 것 네 가지입니다: - **생성 컬럼이 아닌 것.** `column_name data_type`만 쓰면 `a non-generated promoted column is not supported yet`로 거부됩니다. - **`VIRTUAL`.** 승격 컬럼은 인덱스를 지니려면 구체화돼야 하므로, `VIRTUAL`은 `promoted generated properties must be STORED`로 거부됩니다. `STORED`가 기본값이며 생략할 수 있습니다. - **`GENERATED BY DEFAULT`.** `GENERATED ALWAYS`만 받아들여집니다. - **`id`·`properties`·`start`·`end`라는 이름.** 그것들은 그래프 요소의 구조 컬럼이므로, 그 이름은 예약된 것으로 거부됩니다. 데이터 타입은 텍스트 입력 함수가 불변인 타입이어야 하고, 길이를 고정할 수 없습니다. `date`, `timestamp`, `timestamptz`와 배열 타입은 `generation expression is not immutable`로 거부됩니다. `varchar(n)`과 `char(n)`은 값을 길이에 맞추는 과정에서 컬럼과 속성이 서로 다른 값을 담게 될 수 있으므로 거부됩니다. `smallint`, `int`, `bigint`, `numeric`, `real`, `double precision`, `text`, `boolean`, `uuid`, `jsonb`는 받아들여집니다. {#create-vlabel-parameters} ## 매개변수 UNLOGGED : 레이블의 테이블을 unlogged로 만듭니다. 그 내용은 미리 쓰기 로그에 기록되지 않으며 비정상 종료 뒤에는 사라집니다. IF NOT EXISTS : 그 이름의 레이블이 이미 있을 때 오류가 아니라 알림을 냅니다. 아무것도 만들어지지 않고, 이미 있는 레이블의 옵션도 바뀌지 않습니다. label_name : 만들 레이블의 이름이며, `graph_path`가 지정한 그래프 안에 만듭니다. promoted_property : 위 세 형태 중 하나로, 타입 컬럼으로 함께 비출 속성입니다. 컬럼은 적은 순서대로 `properties` 뒤에 추가됩니다. DISABLE INDEX : 레이블의 인덱스를 만들되 무효 상태로 두어, 행을 적재할 때 그것을 유지하지 않게 합니다. 읽기와 쓰기는 그대로 동작하고, 플래너는 무효 인덱스를 무시합니다. 다시 만들 때는 [`REINDEX VLABEL`](reindex.md)을 쓰십시오. `ENABLE INDEX`는 없습니다. INHERITS ( parent_label [, ...] ) : 기본 부모인 `ag_vertex`를 대신하는 부모 레이블입니다. 각각 같은 그래프의 정점 레이블이어야 합니다. [레이블 상속](../../concepts/label-inheritance.md)을 참고하십시오. WITH ( storage_parameter [= value] [, ...] ) : `CREATE TABLE`에서와 같이, 레이블 테이블의 저장 매개변수입니다. TABLESPACE tablespace_name : 레이블 테이블의 테이블스페이스입니다. 이것이 없으면 `default_tablespace`가 결정합니다. ONLY label_name ( label_id ) : 주어진 레이블 id로, 기본 부모·id 기본값·기본 키 없이 레이블을 만듭니다. 그 레이블 id는 이 레이블이 내주는 모든 `graphid`의 상위 16비트입니다. 덤프한 그래프의 id를 복원이 보존할 수 있도록 `pg_dump`가 내보내는 형태입니다. 이렇게 만든 레이블은 그것들을 되돌려 놓기 전까지 Cypher에서 쓸 수 없으므로, 덤프를 재현할 때만 쓰십시오. {#create-vlabel-examples} ## 예제 예제는 [예제 데이터셋](../../get_started/example-dataset.md)을 쓰지 않고 자체 그래프인 `filmstore`를 만들고, 끝에서 그것을 지웁니다. {#create-vlabel-create-a-vertex-label} ### 정점 레이블 만들기 ```cypher CREATE GRAPH filmstore; SET graph_path = filmstore; CREATE VLABEL film; ``` ``` CREATE VLABEL ``` ```psql \d filmstore.film ``` ``` Table "filmstore.film" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+--------------------------------------------------------------------------------------------- id | graphid | | not null | graphid(graph_labid('filmstore.film'::cstring), nextval('filmstore.film_id_seq'::regclass)) properties | jsonb | | not null | jsonb_build_object() Indexes: "film_pkey" PRIMARY KEY, btree (id) Inherits: filmstore.ag_vertex ``` {#create-vlabel-create-a-label-only-if-it-is-not-there} ### 없을 때만 레이블 만들기 ```cypher CREATE VLABEL IF NOT EXISTS film; ``` ``` NOTICE: label "film" already exists, skipping CREATE VLABEL ``` {#create-vlabel-create-a-label-under-a-parent} ### 부모 아래에 레이블 만들기 ```cypher CREATE VLABEL work; CREATE VLABEL documentary INHERITS (work); ``` ```psql \dGv filmstore.documentary ``` ``` List of labels Graph | Name | Type | Owner -----------+-------------+--------+------- filmstore | documentary | vertex | agens (1 row) Vertex label "filmstore.documentary" -- Inherits: filmstore.work ``` `documentary`는 `ag_vertex` 대신 `work`를 상속하므로 `MATCH (n:work)`가 다큐멘터리에도 일치합니다. 나머지는 [레이블 상속](../../concepts/label-inheritance.md)이 다룹니다. {#create-vlabel-load-a-label-before-building-its-indexes} ### 인덱스를 만들기 전에 레이블 적재하기 ```cypher CREATE VLABEL bulk DISABLE INDEX; ``` ```psql \d filmstore.bulk ``` ``` Table "filmstore.bulk" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+--------------------------------------------------------------------------------------------- id | graphid | | not null | graphid(graph_labid('filmstore.bulk'::cstring), nextval('filmstore.bulk_id_seq'::regclass)) properties | jsonb | | not null | jsonb_build_object() Indexes: "bulk_pkey" PRIMARY KEY, btree (id) INVALID Inherits: filmstore.ag_vertex ``` 기본 키는 존재하지만 `INVALID`로 표시됩니다. 행을 적재하고, 그다음 `REINDEX VLABEL bulk`를 실행해 그것을 만드십시오. {#create-vlabel-set-storage-parameters-and-a-tablespace} ### 저장 매개변수와 테이블스페이스 설정하기 ```cypher CREATE UNLOGGED VLABEL staging WITH (fillfactor = 70) TABLESPACE pg_default; ``` ```sql SELECT relpersistence, reloptions FROM pg_class WHERE oid = 'filmstore.staging'::regclass; ``` ``` relpersistence | reloptions ----------------+----------------- u | {fillfactor=70} (1 row) ``` {#create-vlabel-promote-two-properties-to-typed-columns} ### 속성 두 개를 타입 컬럼으로 승격하기 ```cypher CREATE VLABEL title (year int GENERATED, name text GENERATED); ``` ```psql \d filmstore.title ``` ``` Table "filmstore.title" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+----------------------------------------------------------------------------------------------- id | graphid | | not null | graphid(graph_labid('filmstore.title'::cstring), nextval('filmstore.title_id_seq'::regclass)) properties | jsonb | | not null | jsonb_build_object() year | integer | | | generated always as (ag_property_text(properties, 'year'::text, 'n'::"char")::integer) stored name | text | | | generated always as (ag_property_text(properties, 'name'::text, 's'::"char")) stored Indexes: "title_pkey" PRIMARY KEY, btree (id) Inherits: filmstore.ag_vertex ``` 쓰기에 관해 바뀌는 것은 없습니다. 속성은 여전히 `properties`로 들어가고, 컬럼이 그것을 따라갑니다: ```cypher CREATE (:title {name: 'Arrival', year: 2016}); MATCH (t:title) RETURN t.name AS name, t.year AS year; ``` ``` name | year ---------+------ Arrival | 2016 (1 row) ``` 같은 값이 컬럼에도 있고, SQL에서 닿을 수 있습니다: ```sql SELECT name, year FROM filmstore.title; ``` ``` name | year ---------+------ Arrival | 2016 (1 row) ``` 값이 그 컬럼의 종류와 맞지 않는 속성은 쓰기 시점에 거부되며, 양쪽을 모두 이름으로 알려 줍니다: ```cypher CREATE (:title {name: 42, year: 2016}); ``` ``` ERROR: property "name" holds a number DETAIL: A column promoting this property holds strings. CONTEXT: while computing the promoted typed columns of graph label "title" ``` {#create-vlabel-promote-a-property-whose-key-differs-from-the-column-name} ### 키가 컬럼 이름과 다른 속성 승격하기 ```cypher CREATE VLABEL review (released text GENERATED (releaseDate)); ``` ```psql \d filmstore.review ``` ``` Table "filmstore.review" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+------------------------------------------------------------------------------------------------- id | graphid | | not null | graphid(graph_labid('filmstore.review'::cstring), nextval('filmstore.review_id_seq'::regclass)) properties | jsonb | | not null | jsonb_build_object() released | text | | | generated always as (ag_property_text(properties, 'releasedate'::text, 's'::"char")) stored Indexes: "review_pkey" PRIMARY KEY, btree (id) Inherits: filmstore.ag_vertex ``` 키를 `releaseDate`로 적었지만 `releasedate`로 저장됐습니다. 여기서 속성 키는 식별자이고 식별자는 소문자로 내려가기 때문입니다. Cypher는 이 속성을 `n.releasedate`로 읽고, SQL은 그 컬럼을 `released`로 읽습니다. {#create-vlabel-compute-a-column-from-the-property-bag} ### `properties`에서 컬럼 계산하기 긴 형태는 속성 키가 아니라 그 레이블 자신의 컬럼에 대한 SQL 표현식을 받습니다: ```cypher CREATE VLABEL slug (handle text GENERATED ALWAYS AS (lower(properties ->> 'name')) STORED); ``` ```psql \d filmstore.slug ``` ``` Table "filmstore.slug" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+--------------------------------------------------------------------------------------------- id | graphid | | not null | graphid(graph_labid('filmstore.slug'::cstring), nextval('filmstore.slug_id_seq'::regclass)) properties | jsonb | | not null | jsonb_build_object() handle | text | | | generated always as (lower(properties ->> 'name'::text)) stored Indexes: "slug_pkey" PRIMARY KEY, btree (id) Inherits: filmstore.ag_vertex ``` ```cypher CREATE (:slug {name: 'Arrival'}); ``` ``` UPDATE 1 ``` Cypher 쓰기는 그 쓰기가 무엇이었든 행 수를 `UPDATE` 태그로 보고하고, 그 컬럼은 계산되어 저장됩니다: ```sql SELECT handle FROM filmstore.slug; ``` ``` handle --------- arrival (1 row) ``` 그러나 어떤 속성 키에도 답하지 않으므로 Cypher는 그것을 보지 못합니다: ```cypher MATCH (s:slug) RETURN s.handle AS handle; ``` ``` handle -------- (1 row) ``` `s.handle`은 `properties`에서 `handle` 키를 읽는데, 그것을 쓴 것은 아무것도 없습니다. 표현식이 키 하나를 그냥 읽는 것*인* 긴 형태는 축약형처럼 기록되고, Cypher도 그것을 봅니다: ```cypher CREATE VLABEL runtime (minutes int GENERATED ALWAYS AS ((properties ->> 'minutes')::int) STORED); CREATE (:runtime {minutes: 116}); MATCH (r:runtime) RETURN r.minutes AS minutes; ``` ``` minutes --------- 116 (1 row) ``` 그러므로 그 컬럼이 어떤 속성을 대신해 답해야 한다면 키 하나를 꺼내 쓰고, 그 컬럼이 SQL만을 위한 것이라면 계산해 쓰십시오. {#create-vlabel-refusals-worth-recognizing} ### 알아 둘 만한 거부 사례 아래는 모두 그대로 거부됩니다: ```cypher CREATE VLABEL bad1 (name varchar(40) GENERATED); ``` ``` ERROR: a promoted property cannot have a length-limited string type DETAIL: Type character varying fits a value to its length by truncating or padding it. HINT: Use text, which stores the property as it is. ``` ```cypher CREATE VLABEL bad2 (name text); ``` ``` ERROR: a non-generated promoted column is not supported yet HINT: Use GENERATED to mirror the property from the jsonb bag. ``` ```cypher CREATE VLABEL bad3 (released date GENERATED); ``` ``` ERROR: generation expression is not immutable ``` ```cypher CREATE VLABEL bad4 (id int GENERATED); ``` ``` ERROR: promoted property name "id" is reserved ``` ```cypher CREATE VLABEL bad5 (name text GENERATED ALWAYS AS (properties ->> 'name') VIRTUAL); ``` ``` ERROR: promoted generated properties must be STORED LINE 1: ...e text GENERATED ALWAYS AS (properties ->> 'name') VIRTUAL); ^ HINT: A VIRTUAL generated column cannot carry an index. ``` 속성 키는 계층 전체에서 하나의 컬럼으로 이어져야 합니다. 그래서 부모가 이미 승격한 키를 승격하는 것은 컬럼 이름이 같지 않으면 거부됩니다: ```cypher CREATE VLABEL feature (year int GENERATED) INHERITS (work); CREATE VLABEL short (released int GENERATED (year)) INHERITS (feature); ``` ``` ERROR: property "year" is already promoted on graph label "feature" DETAIL: It answers from column "year" there and would answer from column "released" here. HINT: Promote it to the same column name, or leave it to the label that has it. ``` 반대 경우도 거부됩니다. 조상이 이미 쓰는 컬럼 이름으로 다른 키를 승격하면, 키 둘이 상속된 컬럼 하나를 읽게 됩니다. {#create-vlabel-recreate-a-label-with-a-given-label-id} ### 주어진 레이블 id로 레이블 다시 만들기 레이블 id는 그래프의 `ag_label_seq`에서 만들어진 순서대로 나옵니다. `ONLY`는 그것을 명시적으로 정하며, 복원이 덤프에 담긴 id를 재현하는 방법이 그것입니다: ```cypher CREATE VLABEL ONLY restored (300); ``` ```sql SELECT l.labname, l.labid FROM pg_catalog.ag_label l JOIN pg_catalog.ag_graph g ON g.oid = l.graphid WHERE g.graphname = 'filmstore' ORDER BY l.labid; ``` ``` labname | labid -------------+------- ag_vertex | 1 ag_edge | 2 film | 3 work | 4 documentary | 5 bulk | 6 staging | 7 title | 8 review | 9 slug | 10 runtime | 11 feature | 12 restored | 300 (13 rows) ``` 그 레이블에는 부모도, `id` 기본값도, 기본 키도 없습니다. 그래서 그 상태로는 Cypher에서 쓸 수 없습니다: ```psql \d filmstore.restored ``` ``` Table "filmstore.restored" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+--------- id | graphid | | not null | properties | jsonb | | not null | ``` 그래프가 이미 내준 id를 다시 쓰면 `(graphid, labid)`에 걸린 고유 인덱스에서 실패합니다. 마지막으로 예제가 만든 그래프를 지웁니다: ```cypher DROP GRAPH filmstore CASCADE; ``` {#create-vlabel-notes} ## 참고 레이블 이름은 수식할 수 없습니다. `CREATE VLABEL filmstore.film`은 구문 오류이고, 그래프는 항상 `graph_path`에서 옵니다. 설정된 것이 없으면 이 문장은 `graph_path is NULL`로 멈춥니다. `\dGv`는 정점 레이블을 나열하고 일치하는 것마다 설명해 줍니다. `\dGl`은 두 종류의 레이블을 자세한 설명 없이 나열합니다. ```{note} `enable_property_promotion`은 Cypher의 속성 읽기를 승격된 컬럼에서 답할지 결정합니다. 기본값은 켜짐이고, 그것을 끄면 실행 계획뿐 아니라 결과도 달라질 수 있습니다. 컬럼은 자기 타입의 규칙으로 값을 비교하고 정렬하지만, `jsonb` 컬럼인 `properties`는 `jsonb` 순서를 쓰기 때문입니다. ``` {#create-vlabel-compatibility} ## 호환성 {#create-vlabel-isoiec-39075-gql} ### ISO/IEC 39075 (GQL) GQL에는 레이블 DDL이 없습니다. 표준의 스키마 장치는 *그래프 타입*, ``(§12.6)입니다. 그래프 타입은 그래프 하나 전체에 대해 정점 타입과 간선 타입을 한 번에 선언하고, 그것을 참조하는 그래프를 제약합니다. AgensGraph는 레이블을 하나씩 선언하고, 그 그래프는 열려 있습니다. 레이블은 자기 정점이 어떤 속성을 지녀야 하는지 제한하지 않습니다. 그래프 타입은 [GQL 적합성 표](../../appendix/gql-conformance.md)에 계획으로 기록돼 있습니다. {#create-vlabel-postgresql} ### PostgreSQL `CREATE VLABEL`은 `CREATE TABLE`에 대응합니다. `UNLOGGED`, `IF NOT EXISTS`, `INHERITS`, `WITH`, `TABLESPACE`는 거기서와 같은 뜻이고, 승격 속성 형태는 `GENERATED ALWAYS AS ... STORED` 생성 컬럼입니다. 나머지는 `CREATE TABLE`에 없습니다. 직접 고를 수 없는 고정된 컬럼 목록, `ag_label`과 `ag_label_property` 등록, 자동으로 붙는 부모, 그리고 순서가 중요한 옵션 배치입니다. 반대 방향으로는, `enable_graph_ddl`을 켜 두지 않으면 레이블의 테이블을 `ALTER TABLE`로 다시 모양 잡을 수 없습니다. [`ALTER VLABEL`·`ALTER ELABEL`](alter-label.md)을 참고하십시오. {#create-vlabel-see-also} ## 관련 항목 - [`CREATE ELABEL`](create-elabel.md) - [`ALTER VLABEL`·`ALTER ELABEL`](alter-label.md) - [`DROP VLABEL`·`DROP ELABEL`](drop-label.md) - [`CREATE PROPERTY INDEX`](create-property-index.md) - [속성을 컬럼으로 승격하기](../../querying/typed-columns.md) - [레이블 상속](../../concepts/label-inheritance.md)