CREATE CONSTRAINT#
레이블의 어떤 속성이 고유하도록 요구하거나, 그 레이블의 모든 요소가 어떤 조건을 만족하도록 요구합니다.
개요#
CREATE CONSTRAINT [ constraint_name ] ON label_name ASSERT condition
굵게 키워드 · 기울임 직접 채우는 자리 · [ ] 생략 가능 · ... 반복 가능. 구문 표기 규약을 참고하십시오.
설명#
형태가 두 가지이고, 디스크에서는 서로 다른 두 가지가 됩니다.
ASSERT property IS UNIQUE 는 그 속성 읽기에 대한 배제 제약이 됩니다.
EXCLUDE USING btree ((properties.'title') WITH =)입니다. 값이 이미 있는 쓰기는 충돌로
거부됩니다.
ASSERT condition 은 같은 종류의 읽기에 대한 검사 제약이 됩니다.
CHECK ((properties.'age')::integer > 9)입니다. 그 조건에 걸린 행을 쓰는 것은 거부됩니다.
두 형태 모두 이름은 생략할 수 있습니다. 생략하면 이름이 유도됩니다. 고유성 형태는
<label>_unique_constraint, 검사 형태는 <label>_properties_check입니다. 그 이름이 이미 쓰이고
있으면 숫자 접미사가 붙습니다. 직접 주는 이름은 그래프의 스키마에서 비어 있어야 합니다. 고유성
형태가 그 이름의 인덱스를 만들기 때문입니다.
그 밖의 규칙입니다:
레이블은
graph_path가 지정한 그래프에서 찾습니다. 그 이름은 수식할 수 없습니다.두 형태 모두 정점 레이블과 간선 레이블에서 동작합니다.
CREATE OR REPLACE CONSTRAINT는 파싱된 뒤CREATE OR REPLACE CONSTRAINT is not supported로 거부됩니다. 제약을 바꾸려면 지우고 다시 만드십시오.제약은 그 레이블에 이미 있는 행에 대해 만들어지므로, 그것을 위반하는 행이 있으면 문장이 실패합니다.
검사 제약은 SQL의 검사 제약이므로, 없는 속성은 그 제약을 만족합니다. 없는 속성에 대해 조건은 알 수 없음으로 평가되고, 검사 제약은 알 수 없음을 받아들입니다. 이 제약은 키가 반드시 있게 하는 데 쓰는 것이 아니라 값을 제한하는 데 쓰십시오.
디스크에서의 대응 관계는 그래프가 어떻게 저장되는가가 한 화면에 다룹니다.
매개변수#
- constraint_name
그 제약의 이름입니다. 생략할 수 있고, 생략하면 레이블과 형태에서 이름이 유도됩니다. 고유성 형태에서는 그 이름이 인덱스 이름도 되므로, 그래프에 이미 있는 관계와 충돌해서는 안 됩니다.
- label_name
그 제약이 적용되는 레이블이며,
graph_path가 지정한 그래프 안에서 찾습니다.- property
고유해야 하는 속성이며, Cypher가 쓰는 방식으로 적습니다.
title이거나, 중첩된 맵 안의 키라면name.first처럼 적습니다.- condition
그 레이블의 속성에 대한 Cypher 조건입니다. 쓰이는 요소마다 한 번 평가됩니다.
예제#
예제는 예제 데이터셋을 쓰지 않고 자체 그래프인
constrain을 만들고, 끝에서 그것을 지웁니다.
CREATE GRAPH constrain;
SET graph_path = constrain;
CREATE VLABEL film;
CREATE ELABEL rated;
속성이 고유하도록 요구하기#
CREATE CONSTRAINT film_title_uniq ON film ASSERT title IS UNIQUE;
CREATE CONSTRAINT
이것이 그 속성 읽기에 대한 배제 제약을 만들었습니다:
\d constrain.film
Table "constrain.film"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+---------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('constrain.film'::cstring), nextval('constrain.film_id_seq'::regclass))
properties | jsonb | | not null | jsonb_build_object()
Indexes:
"film_pkey" PRIMARY KEY, btree (id)
"film_title_uniq" EXCLUDE USING btree ((properties.'title') WITH =)
Inherits: constrain.ag_vertex
첫 쓰기는 들어갑니다:
CREATE (:film {title: 'Arrival', year: 2016});
UPDATE 1
두 번째는 거부되고, 메시지가 그 제약과 값을 이름으로 알려 줍니다:
CREATE (:film {title: 'Arrival', year: 2021});
ERROR: conflicting key value violates exclusion constraint "film_title_uniq"
DETAIL: Key ((properties.'title'))=("Arrival") conflicts with existing key ((properties.'title'))=("Arrival").
제약에 이름을 붙이지 않기#
CREATE CONSTRAINT ON film ASSERT year IS UNIQUE;
\dGv는 두 제약을 적힌 대로 보여 주고, 두 번째 것에 주어진 유도된 이름도 보여 줍니다:
\dGv constrain.film
List of labels
Graph | Name | Type | Owner
-----------+------+--------+-------
constrain | film | vertex | agens
(1 row)
Vertex label "constrain.film"
--
Constraints:
"film_title_uniq" ASSERT (title) IS UNIQUE
"film_unique_constraint" ASSERT (year) IS UNIQUE
Inherits: constrain.ag_vertex
속성이 담을 수 있는 것 제한하기#
CREATE VLABEL teen;
CREATE CONSTRAINT teen_age ON teen ASSERT (10 <= age::int AND age::int <= 19);
\d constrain.teen
Table "constrain.teen"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+---------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('constrain.teen'::cstring), nextval('constrain.teen_id_seq'::regclass))
properties | jsonb | | not null | jsonb_build_object()
Indexes:
"teen_pkey" PRIMARY KEY, btree (id)
Check constraints:
"teen_age" CHECK (10 <= ((properties.'age')::integer) AND ((properties.'age')::integer) <= 19)
Inherits: constrain.ag_vertex
범위 안의 행은 쓰입니다:
CREATE (:teen {name: 'Kim', age: 15});
UPDATE 1
범위 밖의 것은 쓰이지 않습니다:
CREATE (:teen {name: 'Lee', age: 20});
ERROR: new row for relation "teen" violates check constraint "teen_age"
DETAIL: Failing row contains (5.2, {"age": 20, "name": "Lee"}).
age가 아예 없는 정점은 받아들여집니다. 조건이 거짓이 아니라 알 수 없음이기 때문입니다:
CREATE (:teen {name: 'Bo'});
MATCH (t:teen) RETURN t.name AS name, t.age AS age ORDER BY name;
name | age
-------+-----
"Bo" |
"Kim" | 15
(2 rows)
형 변환 없이 비교하기#
속성을 그대로 두고 비교를 적으면 정수가 아니라 jsonb 값끼리 비교하게 됩니다:
CREATE CONSTRAINT ON teen ASSERT age > 9;
\dGv constrain.teen
List of labels
Graph | Name | Type | Owner
-----------+------+--------+-------
constrain | teen | vertex | agens
(1 row)
Vertex label "constrain.teen"
--
Constraints:
"teen_age" ASSERT (10 <= ((age)::integer) AND ((age)::integer) <= 19)
"teen_properties_check" ASSERT ((age) > cypher_to_jsonb(9))
Inherits: constrain.ag_vertex
두 형태 모두 {age: 'twelve'}를 거부하지만, 보고 방식이 다릅니다. 형 변환한 형태는
invalid input syntax for type integer: "twelve"를 냅니다. 형 변환하지 않은 형태는 검사 제약 위반을
보고합니다. 속성의 범위뿐 아니라 타입까지 강제하고 싶다면 형 변환을 하십시오.
간선 레이블 제약하기#
CREATE CONSTRAINT rated_pair ON rated ASSERT source IS UNIQUE;
\d constrain.rated
Table "constrain.rated"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('constrain.rated'::cstring), nextval('constrain.rated_id_seq'::regclass))
start | graphid | | not null |
end | graphid | | not null |
properties | jsonb | | not null | jsonb_build_object()
Indexes:
"rated_end_idx" btree ("end", start)
"rated_id_idx" brin (id)
"rated_pair" EXCLUDE USING btree ((properties.'source') WITH =)
"rated_start_idx" btree (start, "end")
Inherits: constrain.ag_edge
그 제약은 그 레이블의 평범한 인덱스 세 개 옆에 놓입니다.
행이 이미 있는 레이블에 제약 추가하기#
제약은 이미 있는 것에 대해 검사되므로, 그것을 깨뜨리는 행 위에는 만들 수 없습니다. 두 형태가 그것을 다르게 보고합니다:
CREATE VLABEL rerun;
CREATE (:rerun {title: 'Arrival'}), (:rerun {title: 'Arrival'});
CREATE CONSTRAINT rerun_t ON rerun ASSERT title IS UNIQUE;
ERROR: could not create exclusion constraint "rerun_t"
DETAIL: Key ((properties.'title'))=("Arrival") conflicts with key ((properties.'title'))=("Arrival").
CREATE VLABEL adult;
CREATE (:adult {age: 40});
CREATE CONSTRAINT adult_a ON adult ASSERT (age::int < 20);
ERROR: check constraint "adult_a" of relation "adult" is violated by some row
알아 둘 만한 거부 사례#
교체는 없습니다:
CREATE OR REPLACE CONSTRAINT film_title_uniq ON film ASSERT title IS UNIQUE;
ERROR: CREATE OR REPLACE CONSTRAINT is not supported
고유성 제약의 이름은 인덱스 이름이므로 인덱스와 충돌합니다:
CREATE CONSTRAINT film_title_uniq ON film ASSERT year IS UNIQUE;
ERROR: relation "film_title_uniq" already exists
그리고 그 레이블이 존재해야 합니다. 여기서는 CREATE (:film)이 하는 것처럼 Cypher가 레이블을 만들어 주지 않습니다:
CREATE CONSTRAINT c ON nosuch ASSERT a IS UNIQUE;
ERROR: label "nosuch" does not exist
마지막으로 예제가 만든 그래프를 지웁니다:
DROP GRAPH constrain CASCADE;
참고#
고유성 제약은 고유 인덱스가 아니라 배제 제약이므로 CREATE UNIQUE PROPERTY INDEX와 바꿔 쓸 수
없습니다. 둘 다 중복을 거부합니다. 오류가 다릅니다.
conflicting key value violates exclusion constraint 대
duplicate key value violates unique constraint입니다. \dGv가 보고하는 것도 다릅니다. 그리고
그것을 지우는 문장도 다릅니다. 제약은 DROP CONSTRAINT로, 고유 인덱스는
DROP PROPERTY INDEX로 지웁니다.
두 형태는 레이블 상속에서 다르고, 그 차이에 사람들이
걸립니다. 검사 제약은 상속됩니다. 그 뒤에 만든 자식 레이블이 그 제약을 지니고 그것으로 검사됩니다.
고유성 제약은 그렇지 않습니다. 자식 레이블에는 배제 제약이 생기지 않으므로, 같은 값을 부모를 통해
한 번, 자식을 통해 한 번 쓸 수 있습니다. 그러면 부모에 대한 MATCH가 둘 다 반환합니다.
참고
계층 전체에서 속성을 고유하게 만들려면 그 계층의 레이블마다 제약을 만드십시오. 여러 레이블에 걸쳐 값 하나를 고유하게 만드는 장치는 없습니다. 제약은 그 뒤의 인덱스와 마찬가지로 테이블 하나를 덮습니다.
호환성#
ISO/IEC 39075 (GQL)#
GQL에는 제약 DDL이 없습니다. 표준은 그래프 타입(<create graph type statement>, §12.6)으로 그래프를
제약하며, 그 그래프 타입이 그래프의 요소가 어떤 레이블과 속성을 가질 수 있는지 선언합니다. 그래프
타입에는 속성별 고유성 규칙도, 검사 조건도 없습니다. 그리고 그래프가 그래프 타입을 참조할 때만
제약을 받습니다. AgensGraph의 그래프는 열려 있으므로 CREATE CONSTRAINT는 AgensGraph 자신의
것입니다. 그래프 타입은 GQL 적합성 표에 계획으로 기록돼
있습니다.
PostgreSQL#
검사 형태는 ALTER TABLE ... ADD CONSTRAINT ... CHECK에, 고유성 형태는
ALTER TABLE ... ADD CONSTRAINT ... EXCLUDE USING btree (... WITH =)에 대응합니다. 표현식에는 쓸
수 없는 UNIQUE에는 대응하지 않습니다. 조건은 SQL 표현식이 아니라 Cypher 표현식이므로, age는
컬럼이 아니라 속성을 뜻합니다. ADD CONSTRAINT가 제공하는 NOT NULL, PRIMARY KEY,
FOREIGN KEY, DEFERRABLE, NOT VALID는 여기에 표기가 없습니다.