PostgreSQL | SQLite+MySQL経験者向け、30日で習得するPostgreSQL:高度SQL - Day13 配列型

SQL PostgreSQL
スポンサーリンク

Day13 前半のゴール

「“SQLの中でも配列を持てる”感覚を手に入れる」

今日は、PostgreSQLのもうひとつの武器「配列型(ARRAY)」です。
JSONと似ているところもありますが、「型がはっきりしている」「もっと軽く扱える」という違いがあります。

前半のゴールはこうです。
ARRAY型が「同じ型の値をまとめて持つための箱」だと理解できる。
配列カラムを持つテーブルを作るイメージが持てる。
基本的な配列リテラルと、要素の取り出し・長さ取得の感覚をつかめる。


ARRAY型とは何か

「“同じ型の値”をまとめて1カラムに入れる仕組み」

まずはイメージからいきます。
プログラミングでいう「配列」「リスト」と同じで、「同じ型の値を複数まとめて持つ」ためのものです。

例えば、「ユーザーが複数のタグを持っている」ケースを考えます。

premium
beta_user
dark_mode_enabled

これを、普通にやるなら「user_tags テーブルを作って、1タグ=1行」で持ちます。
それも正しいやり方ですが、PostgreSQLなら「タグを配列として1カラムにまとめて持つ」という選択肢も取れます。

ARRAY型は、「型がはっきりしている」のがポイントです。
text[] なら「文字列の配列」、integer[] なら「整数の配列」というふうに、
「何の配列か」がスキーマに書かれます。

JSONは「中身の型がバラバラでもOK」ですが、ARRAYは「同じ型だけ」です。
その代わり、軽くて速くて、扱いがシンプルです。


配列カラムを持つテーブルを作ってみる

「タグを text[] で持つユーザーテーブル」

具体的なテーブルを1つ作ってみましょう。

CREATE TABLE users (
  id         SERIAL PRIMARY KEY,
  email      TEXT    NOT NULL UNIQUE,
  name       TEXT    NOT NULL,
  tags       TEXT[]  NOT NULL DEFAULT '{}',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
SQL

ここでのポイントを分解します。

tags TEXT[]
文字列の配列(text[])としてタグを持つカラム。

DEFAULT '{}'
空配列をデフォルト値にしている。
NULLではなく「要素ゼロの配列」として扱えるので、後で楽。

この tags に、例えばこういう値が入ります。

ユーザーA:{"premium", "beta_user"}
ユーザーB:{"free"}
ユーザーC:{}(タグなし)

PostgreSQLの配列リテラルは {...} という形をしていて、
中にカンマ区切りで要素を書きます。


配列リテラルの基本

「INSERT時に {} で書く感覚をつかむ」

実際にINSERTするSQLを見てみます。

INSERT INTO users (email, name, tags)
VALUES (
  'taro@example.com',
  'Taro',
  ARRAY['premium', 'beta_user']
);
SQL

または、こう書くこともできます。

INSERT INTO users (email, name, tags)
VALUES (
  'hanako@example.com',
  'Hanako',
  '{"free"}'
);
SQL

ここでの違いはこうです。

ARRAY['premium', 'beta_user']
SQLの配列コンストラクタ。
中の '...' は text 型で、結果は text[]。

'{"free"}'
配列リテラルを文字列として書いて、PostgreSQLが text[] に解釈する。

どちらも最終的には同じ text[] になります。
初心者的には、ARRAY[...] の方が「配列を作っている感」が分かりやすいと思います。

空配列を入れたいときは、こうです。

INSERT INTO users (email, name, tags)
VALUES (
  'no-tags@example.com',
  'NoTags',
  ARRAY[]::text[]
);
SQL

ARRAY[] だけだと「型が分からない」ので、::text[] で「textの配列」と明示しています。
ここは少しだけPostgreSQLっぽさが出るところです。


配列の要素を取り出す

「tags[1] で“1番目のタグ”を取る」

配列から要素を取り出すのは、とてもシンプルです。
PostgreSQLの配列は「1始まり」です(ここ、JSONと違うので注意)。

例えば、こういう行があるとします。

tags = {"premium", "beta_user", "dark_mode_enabled"}

このとき、「1番目のタグ」を取りたいなら、こう書きます。

SELECT
  tags[1] AS first_tag,
  tags[2] AS second_tag
FROM users
WHERE email = 'taro@example.com';
SQL

結果は、

first_tagpremium
second_tagbeta_user

というふうになります。

ここでの重要ポイントは、「インデックスが1から始まる」ことです。
プログラミング言語の多くは0始まりなので、
「配列だけど1始まり」というPostgreSQLの癖を、ここで意識しておいてください。


配列の長さを知る

「何個タグを持っているか」を数える

配列の長さ(要素数)を知るには、cardinalityarray_length を使います。

例えば、「ユーザーごとのタグ数」を見たいとします。

SELECT
  email,
  tags,
  cardinality(tags) AS tag_count
FROM users
ORDER BY tag_count DESC;
SQL

cardinality(tags) は、「配列の要素数」を返します。
結果は例えばこうなります。

email              | tags                                   | tag_count
-------------------+----------------------------------------+----------
taro@example.com   | {"premium","beta_user","dark_mode..."} | 3
hanako@example.com | {"free"}                               | 1
no-tags@example... | {}                                     | 0

array_length(tags, 1) でも同じようなことができます。

SELECT
  email,
  array_length(tags, 1) AS tag_count
FROM users;
SQL

第2引数の 1 は「次元」を表していて、
1次元配列なら「1」でOKです(多次元配列は後半の話)。

「配列の長さをSQLで取れる」というのは、
「配列をただの文字列として持っている」のとは大きな違いです。


配列型とJSON配列の違いを整理する

「“型が決まっている軽い配列”か、“何でも入る柔軟な配列”か」

ここまでで、「ARRAYってJSON配列と似てない?」と思ったはずです。
似ていますが、使いどころが少し違います。

ARRAY(text[] や integer[])

型が決まっている(全部text、全部integerなど)。
軽くて速い。
演算子や関数がシンプル。
「同じ種類の値を少しだけまとめて持ちたい」ときに向いている。

JSON配列(jsonbの中の [...]

中身の型がバラバラでもOK。
構造が複雑でもOK(オブジェクトの配列など)。
柔軟だが、重めで、扱いも少し複雑。
「構造化されたデータを柔軟に持ちたい」ときに向いている。

例えば、「タグのような単純な文字列の集まり」は、ARRAYで十分です。
「デバイス情報のような、複数のキーを持つオブジェクトの集まり」は、JSON配列の方が向いています。

Day13 前半では、「ARRAYは“軽い・型付きの配列”、JSON配列は“柔軟・構造付きの配列”」というざっくりした違いを持っておいてくれれば十分です。


配列型を使うときの“最初の一歩の設計感覚”

「“単純なリスト”ならARRAYで持ってみる」

いきなり何でもARRAYにする必要はありません。
でも、「これは単純なリストだな」と思ったら、ARRAYを検討する価値があります。

例えば、

ユーザーのタグ(text[])。
記事の関連キーワード(text[])。
通知の送信先IDリスト(integer[])。

こういう「同じ型の値が複数あるだけ」のものは、
わざわざ別テーブルにしてもいいし、ARRAYでまとめてもいい領域です。

PostgreSQLはARRAYをちゃんとサポートしているので、
「RDBだから配列はダメ」という固定観念は、一度外してみていいと思います。


Day13 前半のまとめ

PostgreSQLの配列型(ARRAY)は、「同じ型の値をまとめて1カラムに入れる」ための仕組みで、text[]integer[] のように「何の配列か」がスキーマに書かれる。
ユーザーのタグを tags TEXT[] のようなカラムで持ち、ARRAY['premium','beta_user']'{"free"}' のようなリテラルでINSERTし、tags[1] で1番目の要素を取り出したり、cardinality(tags)array_length(tags,1) で「何個タグを持っているか」を数えたりできる。
ARRAYは「型が決まっていて軽い配列」、JSON配列は「柔軟で構造を持つ配列」という違いがあり、タグやキーワードのような“単純なリスト”はARRAYで持つと扱いやすくなる――これが Day13 前半の着地点になる。

タイトルとURLをコピーしました