굵게 키워드 · 기울임 직접 채우는 자리 · [ ] 생략 가능 · ... 반복 가능. 구문 표기 규약을 참고하십시오.
{#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)