CodeGym /コース /SQL SELF /配列のインデックスと演算子(`@>`, `<@`, `&&`)で高速検索

配列のインデックスと演算子(`@>`, `<@`, `&&`)で高速検索

SQL SELF
レベル 38 , レッスン 1
使用可能

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種類のインデックスがある:

  1. GIN(Generalized Inverted Index) — 配列にはこれが最強!
  2. 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 低い テーブルから行を取得

RowsCost の値はデータ量によって変わるけど、大事なのは「インデックスがちゃんと使われてる」ってこと!

演算子とインデックスの関係

例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つでもタグがかぶってたらヒットするよ。

インデックスと最適化のコツ

配列を使うときは、こんなポイントを意識しよう:

  1. GIN インデックスで配列検索を高速化。シーケンシャルスキャンより断然速い!
  2. 本当にクエリでよく使うカラムだけインデックス化。インデックスは容量を食うし、INSERTも遅くなるから、むやみに全部に付けないこと。
  3. EXPLAINEXPLAIN 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 インデックスを使って、クエリを爆速にしよう。自分のデータでぜひ試してみてね!

2
タスク
SQL SELF, レベル 38, レッスン 1
ロック未解除
配列を使ったテーブル作成と基本的な `@>` 演算子のクエリ
配列を使ったテーブル作成と基本的な `@>` 演算子のクエリ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION