PostgreSQLの配列は、テーブルの1つのセルに複数の値をぶち込める超便利な機能だよ。たとえば記事のタグリストや、商品のカテゴリ一覧みたいに、関連データをまとめて管理したいときにめっちゃ使える。 でも、配列で検索・フィルタ・重なりチェックをやり始めると、パフォーマンスが一気に落ちることも…。そこで救世主になるのが配列のインデックス化!インデックスを使うと、こんな操作が爆速になる:
- 配列に特定の要素が入ってるかチェック
- 指定した要素を含む配列を検索
- 配列同士の重なり(オーバーラップ)をチェック
配列操作用の演算子
インデックス作成に入る前に、まずは配列を扱う基本の演算子をおさらいしよう:
@>(contains) — 配列が他の配列の全要素を含んでるかチェックする。
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
この例だと、「SQL」タグを持ってるコースを探してるよ。
<@(is contained by) — ある配列が他の配列に全部含まれてるかチェック。
SELECT *
FROM courses
WHERE ARRAY['PostgreSQL', 'SQL'] <@ tags;
このクエリは、ARRAY['PostgreSQL', 'SQL'] の全要素を含むタグを持つコースを探してる。
&&(overlap) — 配列同士に重なりがあるかチェック。
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
このクエリは、「NoSQL」か「Big Data」タグが1つでも入ってるコースを探すよ。
インデックス化の効果は?
例えば、courses テーブルに何百万件もデータがあって、上の演算子を使ったクエリを投げるとしよう。インデックスがなければ、PostgreSQLは全行を1つずつチェックする羽目になる(プログラマーがコンパイル待ちしてる時みたいに永遠に終わらない…)。 でもインデックスがあれば、そんな無駄な処理を回避できる!PostgreSQLには配列向けに2種類のインデックスがある:
GIN(Generalized Inverted Index) — 配列にはこれが最強!BTREE— 配列全体の比較用。
例:配列用インデックスを作ってみる
まずは配列入りの小さなテーブルを作って、実際に試してみよう。
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
tags TEXT[] NOT NULL
);
データをいくつか追加:
INSERT INTO courses (name, tags)
VALUES
('SQLのキホン', ARRAY['SQL', 'PostgreSQL', 'データベース']),
('Big Data入門', ARRAY['Hadoop', 'Big Data', 'NoSQL']),
('Python開発', ARRAY['Python', 'Web', 'データ']),
('PostgreSQLコース', ARRAY['PostgreSQL', 'Advanced', 'SQL']);
テーブルの中身はこんな感じ:
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 2 | Big Data入門 | {Hadoop, Big Data, NoSQL} |
| 3 | Python開発 | {Python, Web, データ} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
インデックスなし:遅い検索
例えば「SQL」タグを持つコースを全部探したいとき:
EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
このクエリは一応動くけど、データが多いと激遅。PostgreSQLは「シーケンシャルスキャン(Sequential Scan)」、つまり全行なめるしかない。
クエリ結果の例:
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
GINインデックスを作る
検索を速くするために GIN インデックスを作ろう:
CREATE INDEX idx_courses_tags
ON courses USING GIN (tags);
同じクエリをもう一度:
EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
今度はPostgreSQLが作った GIN インデックスを使って、クエリがめっちゃ速くなる!
前は シーケンシャルスキャン(Seq Scan)だったけど、今は Bitmap Index Scan になる:
| Step | Rows | Cost | Info |
|---|---|---|---|
| Bitmap Index Scan | N | 低い | インデックス idx_courses_tags を使う |
| Bitmap Heap Scan | N | 低い | テーブルから行を取得 |
Rows と Cost の値はデータ量によって変わるけど、大事なのは「インデックスがちゃんと使われてる」ってこと!
演算子とインデックスの関係
例1:@> 演算子
クエリ:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
GIN インデックスはこの演算子と相性バツグン。PostgreSQLが一瞬で該当行を見つけてくれる。
クエリ結果:
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
@> は「含む」って意味だよ。このクエリは tags 配列に SQL が入ってるコースを全部返す。
例2:&& 演算子
クエリ:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
この演算子は配列の重なりをチェックする。tags 配列が、指定した配列のどれか1つでも一致してたらOK。
ここでも GIN インデックスが大活躍。データが多くてもサクサク検索できる。
クエリ結果:
| id | name | tags |
|---|---|---|
| 2 | Big Data入門 | {Hadoop, Big Data, NoSQL} |
「重なりがある」って意味。1つでもタグがかぶってたらヒットするよ。
インデックスと最適化のコツ
配列を使うときは、こんなポイントを意識しよう:
GINインデックスで配列検索を高速化。シーケンシャルスキャンより断然速い!- 本当にクエリでよく使うカラムだけインデックス化。インデックスは容量を食うし、INSERTも遅くなるから、むやみに全部に付けないこと。
EXPLAINやEXPLAIN ANALYZEでクエリをプロファイル。インデックスがちゃんと使われてるか確認しよう。
例:配列用インデックスの作り方
配列操作ごとにインデックスをどう作るか、実際の用途も交えて見てみよう。
@> 演算子用インデックス
例えば、こんな courses テーブルがあるとする:
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 2 | Big Data入門 | {Hadoop, Big Data, NoSQL} |
| 3 | Python開発 | {Python, Web, データ} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
@>(配列が要素を含む)演算子用に GIN インデックスを作成:
CREATE INDEX idx_courses_tags_gin
ON courses USING GIN (tags);
クエリを実行:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
結果:
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
@>, <@, && 演算子用インデックス
テーブルは上と同じ。
@>, <@, && の各演算子は全部 GIN インデックスで爆速になるから、1つ作っておけばOK:
CREATE INDEX idx_tags
ON courses USING GIN (tags);
クエリ例と結果:
@>— 配列が指定要素を含むかチェック:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
<@— 配列が他の配列に全部含まれてるかチェック:
SELECT *
FROM courses
WHERE tags <@ ARRAY['SQL', 'PostgreSQL', 'Advanced', 'Big Data', 'NoSQL', 'Python'];
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL, PostgreSQL, データベース} |
| 2 | Big Data入門 | {Hadoop, Big Data, NoSQL} |
| 3 | Python開発 | {Python, Web, データ} |
| 4 | PostgreSQLコース | {PostgreSQL, Advanced, SQL} |
&&— 配列の重なりチェック:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
| id | name | tags |
|---|---|---|
| 2 | Big Data入門 | {Hadoop, Big Data, NoSQL} |
ちょっと難しいクエリもやってみよう
例えば、タグが ['Python', 'SQL', 'NoSQL'] のどれか1つでも重なってるコースを探すクエリ:
SELECT *
FROM courses
WHERE tags && ARRAY['Python', 'SQL', 'NoSQL'];
結果:
| id | name | tags |
|---|---|---|
| 1 | SQLのキホン | {SQL,PostgreSQL,データベース} |
| 2 | Big Data入門 | {Hadoop,Big Data,NoSQL} |
| 3 | Python開発 | {Python,Web,データ} |
GIN インデックスがあれば、こんなクエリも一瞬で終わる。データが何百万件あっても余裕!
配列操作でありがちなミス
インデックスが使われてない:EXPLAIN の出力で Seq Scan が出てたら、インデックスがちゃんと作られてるか、演算子がインデックス対応か確認しよう。
配列カラムの利用頻度が低い:そのカラムがほとんどクエリや更新で使われないなら、インデックスは無駄に容量を食うだけかも。
インデックスの作りすぎ:インデックスはディスク容量を食うし、書き込みも遅くなる。必要なものだけ作ろう!
これでPostgreSQLの配列操作はバッチリ!@>, <@, && と GIN インデックスを使って、クエリを爆速にしよう。自分のデータでぜひ試してみてね!
GO TO FULL VERSION