CREATE PROPERTY INDEX#
레이블의 속성 하나 이상에 인덱스를 만듭니다.
개요#
ON [ ONLY ] label_name [ USING method ]
( { property | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ... ] )
[ NULLS [ NOT ] DISTINCT ]
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ TABLESPACE tablespace_name ]
[ WHERE condition ]
굵게 키워드 · 기울임 직접 채우는 자리 · [ ] 생략 가능 · ... 반복 가능. 구문 표기 규약을 참고하십시오.
설명#
이 문장 뒤에 별도의 인덱스 종류가 있는 것은 아닙니다. 속성 인덱스는 레이블의 jsonb 컬럼인 properties에서 그
속성을 읽는 표현식에 걸린 평범한 PostgreSQL 인덱스입니다.
CREATE PROPERTY INDEX ON movie (title)은 키가 (properties.'title')인 btree를 만듭니다. \d가
그렇게 보고하며, 플래너가 그 인덱스를 쓰려면 조건을 그 형태로 볼 수 있어야 합니다.
이 문장의 제한은 그 표현식이 전부 설명합니다:
인덱싱되는 값의 타입이
jsonb이므로, 접근 방식에jsonb용 기본 연산자 클래스가 있어야 합니다. 기본값인btree, 그리고hash와gin에는 있습니다.gist·spgist·brin에는 없고, 그중 하나를 요구하면data type jsonb has no default operator class for access method "gist"로 실패합니다.콜레이션을 적용할 수 없습니다.
jsonb에는 콜레이션이 없으므로,properties읽기에COLLATE를 쓰면collations are not supported by type jsonb로 실패합니다.그 값은 결코 포함 컬럼이 될 수 없습니다.
INCLUDE는expressions are not supported in included columns로 거부되고, 속성 읽기는 언제나 표현식이므로INCLUDE가 그것에 적용되지 않습니다.
승격된 속성에 걸린 인덱스는 다릅니다. 그때는 값이 표현식이 아니라 컬럼이기 때문입니다. \d는
btree ((properties.'year'))가 아니라 btree (year)로 보고합니다. 접근 방식, 연산자 클래스,
콜레이션은 그 컬럼 자신의 타입에만 제한되므로, int 컬럼에 brin (year)도, text 컬럼에
btree (name COLLATE "C")도 둘 다 됩니다. 애초에 언제 승격해야 하는지는
속성을 컬럼으로 승격하기가 다룹니다.
규칙이 세 가지 더 있습니다:
레이블은
graph_path가 지정한 그래프에서 찾습니다. 그 이름은 수식할 수 없습니다.부모 레이블에 걸린 인덱스는 그 레이블 자신의 행만 덮습니다.
ONLY를 쓰든 쓰지 않든 자식 레이블은 인덱싱되지 않습니다. 레이블 상속을 참고하십시오.IF NOT EXISTS는 검사할 이름이 필요하므로, 이름 없는 인덱스와 함께 쓸 수 없습니다.
매개변수#
- UNIQUE
인덱싱되는 값이 이미 있는 값과 중복되는 행을 거부하며, 인덱스를 만들 때 이미 있는 행도 검사합니다. 고유성 규칙을 Cypher 자신의 어법으로 표현하는 방법은
CREATE CONSTRAINT를 참고하십시오.- CONCURRENTLY
그 레이블에 대한 쓰기를 잠그지 않고 인덱스를 만듭니다. 대가는 더 긴 생성 시간과 테이블 스캔 두 번입니다.
- IF NOT EXISTS index_name
그 이름의 관계가 이미 있을 때 오류가 아니라 알림을 냅니다. 아무것도 만들어지지 않고, 이미 있는 인덱스가 요구한 그 인덱스인지 확인하는 것도 없습니다.
- index_name
인덱스의 이름입니다. 생략하면 레이블과 인덱싱한 속성에서 이름이 유도됩니다.
movie와year는movie_year_idx가 됩니다.- ONLY
받아들여지지만 평범한 레이블에는 효과가 없습니다. 파티션으로 재귀하는 것을 막는 것인데, 레이블은 파티션된 테이블이 아니라 상속 부모입니다.
- label_name
행을 인덱싱할 레이블이며,
graph_path가 지정한 그래프 안에서 찾습니다.- USING method
인덱스 접근 방식입니다.
properties에서 읽는 속성에 대해서는 기본값인btree,hash,gin중에서 고릅니다. 승격된 컬럼에 대해서는 그 컬럼 타입에 기본 연산자 클래스가 있는 방식이면 무엇이든 됩니다.- property
그 레이블의 속성이며, Cypher가 쓰는 방식으로 적습니다.
title이거나, 중첩된 맵 안의 키라면name.first처럼 적습니다.- ( expression )
그 레이블의 속성에 대한, 괄호로 감싼 Cypher 표현식입니다. 예를 들어
(year / 10)입니다.- COLLATE collation
인덱싱되는 값의 콜레이션입니다. 그 값이 콜레이션을 가질 수 있는 타입의 승격된 컬럼일 때만 쓸 수 있습니다.
- opclass
인덱싱되는 값의 연산자 클래스입니다. 예를 들어
gin인덱스에는jsonb_path_ops입니다. 연산자 클래스 매개변수는 받아들여지지 않습니다.- ASC | DESC | NULLS FIRST | NULLS LAST
인덱스에 저장되는 정렬 순서입니다. 기본값은
ASC와NULLS LAST이고,DESC아래에서는NULLS FIRST가 기본값입니다.- NULLS [ NOT ] DISTINCT
고유 인덱스에서 null 둘을 서로 다른 값으로 셀지 여부입니다. 기본값인
NULLS DISTINCT는 인덱스가 null을 여러 개 담을 수 있게 합니다.- WITH ( storage_parameter [= value] [, …] )
인덱스의 저장 매개변수입니다. btree의
fillfactor나 gin의fastupdate같은 것입니다.- TABLESPACE tablespace_name
인덱스의 테이블스페이스입니다. 이것이 없으면
default_tablespace가 결정합니다.- WHERE condition
부분 인덱스를 만듭니다. 그 조건이 받아들이는 행만 덮습니다.
예제#
예제는 예제 데이터셋을 쓰지 않고 자체 그래프인
indexdemo를 만들고, 끝에서 그것을 지웁니다.
CREATE GRAPH indexdemo;
SET graph_path = indexdemo;
CREATE VLABEL movie;
CREATE VLABEL person;
CREATE (:movie {title: 'Arrival', year: 2016, genre: 'Sci-Fi'}),
(:movie {title: 'Dune', year: 2021, genre: 'Sci-Fi'}),
(:movie {title: 'Gattaca', year: 1997, genre: 'Sci-Fi'}),
(:movie {title: 'Sicario', year: 2015, genre: 'Thriller'}),
(:movie {title: 'Prisoners', year: 2013, genre: 'Thriller'}),
(:movie {title: 'Enemy', year: 2013, genre: 'Thriller'});
CREATE (:person {name: 'Denis Villeneuve'}), (:person {name: 'Roger Deakins'});
속성에 인덱스 걸기#
CREATE PROPERTY INDEX movie_title_idx ON movie (title);
CREATE PROPERTY INDEX
이것이 표현식에 걸린 btree를 만들었습니다:
\d indexdemo.movie
Table "indexdemo.movie"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('indexdemo.movie'::cstring), nextval('indexdemo.movie_id_seq'::regclass))
properties | jsonb | | not null | jsonb_build_object()
Indexes:
"movie_pkey" PRIMARY KEY, btree (id)
"movie_title_idx" btree ((properties.'title'))
Inherits: indexdemo.ag_vertex
\dGi는 같은 인덱스를 그것을 다시 만들 문장의 형태로 나열합니다:
\dGi indexdemo.*
List of property indexes
Graph | LabelName | IndexName | Unique | Owner | Indexdef
-----------+-----------+-----------------+--------+-------+--------------------------------------------------------------------
indexdemo | movie | movie_title_idx | f | agens | CREATE PROPERTY INDEX movie_title_idx ON movie USING btree (title)
(1 row)
플래너는 title에 대한 같음 비교를 그 표현식과 맞춥니다. 정점 여섯 개는 플래너가 인덱스 스캔을
선호하기에는 턱없이 적으므로, 아래 설정으로 그 계획을 보이게 합니다:
SET enable_seqscan = off;
EXPLAIN (COSTS OFF) MATCH (m:movie {title: 'Dune'}) RETURN m.year;
QUERY PLAN
------------------------------------------------------
Index Scan using movie_title_idx on movie m
Index Cond: (properties.'title' = '"Dune"'::jsonb)
(2 rows)
RESET enable_seqscan;
인덱스 이름을 자동으로 정하기#
CREATE PROPERTY INDEX ON movie (year);
\dGi indexdemo.movie_year_idx
List of property indexes
Graph | LabelName | IndexName | Unique | Owner | Indexdef
-----------+-----------+----------------+--------+-------+------------------------------------------------------------------
indexdemo | movie | movie_year_idx | f | agens | CREATE PROPERTY INDEX movie_year_idx ON movie USING btree (year)
(1 row)
접근 방식 고르기#
속성 읽기에 대해 btree의 대안은 gin과 hash입니다:
CREATE PROPERTY INDEX movie_genre_gin ON movie USING gin (genre);
CREATE PROPERTY INDEX movie_genre_hash ON movie USING hash (genre);
CREATE PROPERTY INDEX
CREATE PROPERTY INDEX
나머지 세 접근 방식은 AgensGraph와 무관한 이유로 안 됩니다. jsonb용 기본 연산자 클래스가 없기
때문입니다:
CREATE PROPERTY INDEX movie_genre_gist ON movie USING gist (genre);
ERROR: data type jsonb has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
spgist와 brin도 같은 방식으로 실패합니다.
중복 값 거부하기#
CREATE UNIQUE PROPERTY INDEX movie_title_uniq ON movie (title);
CREATE (:movie {title: 'Dune', year: 1984});
ERROR: duplicate key value violates unique constraint "movie_title_uniq"
DETAIL: Key ((properties.'title'))=("Dune") already exists.
그 메시지의 Key는 인덱스 표현식이며, 그것으로 고유 속성 인덱스를 컬럼에 걸린 고유성 제약과
구별합니다.
레이블의 일부에 인덱스 걸기#
CREATE PROPERTY INDEX movie_recent_idx ON movie (title) WHERE (year > 2010);
CREATE PROPERTY INDEX
속성 여러 개와 표현식에 인덱스 걸기#
CREATE PROPERTY INDEX movie_genre_year_idx ON movie (genre, year DESC NULLS LAST);
CREATE PROPERTY INDEX movie_decade_idx ON movie ((year / 10));
CREATE PROPERTY INDEX movie_title_ops ON movie USING gin (title jsonb_path_ops);
그 하나하나가 표현식 인덱스이며, \d가 무엇이 만들어졌는지 정확히 보여 줍니다. 산술 인덱스가
지니고 있는 Cypher 연산자까지 보여 줍니다:
\d indexdemo.movie
Table "indexdemo.movie"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('indexdemo.movie'::cstring), nextval('indexdemo.movie_id_seq'::regclass))
properties | jsonb | | not null | jsonb_build_object()
Indexes:
"movie_pkey" PRIMARY KEY, btree (id)
"movie_decade_idx" btree (((properties.'year') `/` cypher_to_jsonb(10)))
"movie_genre_gin" gin ((properties.'genre'))
"movie_genre_hash" hash ((properties.'genre'))
"movie_genre_year_idx" btree ((properties.'genre'), (properties.'year') DESC NULLS LAST)
"movie_recent_idx" btree ((properties.'title')) WHERE (properties.'year') > cypher_to_jsonb(2010)
"movie_title_idx" btree ((properties.'title'))
"movie_title_ops" gin ((properties.'title') jsonb_path_ops)
"movie_title_uniq" UNIQUE, btree ((properties.'title'))
"movie_year_idx" btree ((properties.'year'))
Inherits: indexdemo.ag_vertex
승격된 속성에 인덱스 걸기#
속성이 컬럼으로 승격된 레이블은 그 대신 컬럼에 인덱스를 겁니다:
CREATE VLABEL title (year int GENERATED, name text GENERATED);
CREATE (:title {name: 'Arrival', year: 2016}), (:title {name: 'Dune', year: 2021}), (:title {name: 'Gattaca', year: 1997});
CREATE PROPERTY INDEX title_year_idx ON title (year);
\d indexdemo.title
Table "indexdemo.title"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('indexdemo.title'::cstring), nextval('indexdemo.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)
"title_year_idx" btree (year)
Inherits: indexdemo.ag_vertex
btree ((properties.'year'))가 아니라 btree (year)입니다. 실행 계획도 같게 읽히며, jsonb가
아니라 정수를 비교합니다:
SET enable_seqscan = off;
EXPLAIN (COSTS OFF) MATCH (t:title) WHERE t.year > 2010 RETURN t.name;
QUERY PLAN
--------------------------------------------
Index Scan using title_year_idx on title t
Index Cond: (year > 2010)
(2 rows)
RESET enable_seqscan;
인덱싱되는 값이 컬럼이므로, 콜레이션과 연산자 클래스, 그리고 그 컬럼 타입이 지원하는 접근 방식이 모두 쓸 수 있게 됩니다:
CREATE PROPERTY INDEX title_name_coll ON title (name COLLATE "C");
CREATE PROPERTY INDEX title_name_pat ON title (name text_pattern_ops);
CREATE PROPERTY INDEX title_year_brin ON title USING brin (year);
\d indexdemo.title
Table "indexdemo.title"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('indexdemo.title'::cstring), nextval('indexdemo.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)
"title_name_coll" btree (name COLLATE "C")
"title_name_pat" btree (name text_pattern_ops)
"title_year_brin" brin (year)
"title_year_idx" btree (year)
Inherits: indexdemo.ag_vertex
그 셋 중 어느 것도 properties에서 읽는 속성에 대해서는 받아들여지지 않습니다.
알아 둘 만한 거부 사례#
INCLUDE는 승격 컬럼이든 아니든 아예 쓸 수 없습니다. 속성은 언제나 표현식으로 적히기
때문입니다:
CREATE PROPERTY INDEX title_name_incl ON title (name) INCLUDE (year);
ERROR: expressions are not supported in included columns
연산자 클래스 매개변수는 이 문장의 일부가 아닙니다:
CREATE PROPERTY INDEX movie_title_op ON movie USING gin (title jsonb_ops (fastupdate = off));
ERROR: syntax error at or near "("
LINE 1: ...ovie_title_op ON movie USING gin (title jsonb_ops (fastupdat...
^
중첩 경로는 그 속성이 맵을 담고 있을 때만 됩니다. 여기서 person.name은 문자열이므로, 그 안으로
들어가려 하면 인덱스를 만드는 중에 실패합니다:
CREATE PROPERTY INDEX person_first_idx ON person (name.first);
ERROR: map or list is expected but scalar value
그리고 이미 쓰이고 있는 이름은 교체되는 것이 아니라 보고됩니다:
CREATE PROPERTY INDEX IF NOT EXISTS movie_title_idx ON movie (title);
NOTICE: relation "movie_title_idx" already exists, skipping
CREATE PROPERTY INDEX
마지막으로 예제가 만든 그래프를 지웁니다:
DROP GRAPH indexdemo CASCADE;
참고#
CONCURRENTLY는 받아들여지고 CREATE INDEX에서와 같은 일을 합니다. 쓰기를 잠그지 않고 만듭니다.
ONLY도 받아들여지지만 평범한 레이블에서는 아무것도 바꾸지 않습니다. 부모에 걸린 인덱스는 어느
쪽이든 부모의 행만 덮으므로, 계층에서는 스캔될 레이블마다 인덱스가 하나씩 필요합니다.
승격된 키가 여기서 어떻게 해석되는가#
승격된 속성이 있는 레이블에서는 ( ... )의 두 형태가 다르게
해석됩니다:
적는 것 |
인덱싱되는 것 |
|---|---|
맨 키를 쓴 |
승격된 컬럼 |
표현식을 쓴 |
jsonb |
승격된 키에 대한 쿼리는 두 경우 모두 컬럼을 읽으므로, 쿼리가 쓸 수 있는 인덱스를 주는 것은 맨 키
형태입니다. 승격된 키에 대한 표현식에 인덱스를 걸려면 그 컬럼에 대한 SQL로 쓰십시오 —
CREATE INDEX ON g.film (lower(title)) — 그것도 \dGi가 여전히 나열해 줍니다.
\dGi는 자기 Indexdef 컬럼을 Cypher 형태로 렌더링하므로 둘 다 키를 같게 표기합니다. 어떤 인덱스가
둘 중 무엇에 걸려 있는지 보려면 pg_get_indexdef를 읽거나 레이블 테이블에 \d를 쓰십시오. 거기서
맨 승격 키는 btree (year)로, 속성 형태는 btree ((properties.'year'))로 읽힙니다.
호환성#
ISO/IEC 39075 (GQL)#
GQL에는 인덱스 DDL이 없습니다. 표준의 카탈로그 변경 문장은 스키마·그래프·그래프 타입(§12)에
한정됩니다. 거기에는 인덱스의 표준 표기가 없으므로, CREATE PROPERTY INDEX는 AgensGraph 자신의
것입니다.
PostgreSQL#
CREATE PROPERTY INDEX는 표현식에 대한 CREATE INDEX에 대응하고, UNIQUE, CONCURRENTLY,
USING, COLLATE, 연산자 클래스, 정렬 순서, NULLS [NOT] DISTINCT, WITH, TABLESPACE,
WHERE는 모두 거기서와 같은 뜻입니다. 다른 점이 세 가지 중요합니다. 인덱스 대상을 컬럼이나 괄호로
감싼 SQL 표현식이 아니라 Cypher 속성으로 적으므로, 파서가 properties 읽기를 대신 만들어 줍니다.
CREATE INDEX가 받는 INCLUDE와 연산자 클래스 매개변수는 쓸 수 없습니다. 그리고
DROP PROPERTY INDEX는 속성 인덱스만 받아들이는 반면, DROP INDEX는
레이블의 어떤 인덱스든, 속성 인덱스를 포함해 받아들입니다.