Day13 後半のゴール
「“配列を条件に使う・展開する・更新する”を一通りこなせるようにする」
前半で「配列を持つ」「要素を取り出す」「長さを数える」ところまでは来ました。
後半では、実務でよく使う3つの軸を押さえます。
配列の中身で絞り込む(タグ検索など)。
配列を行に展開して集計する。
配列に要素を足す・消すといった更新をする。
ここまでできると、「ARRAYをちゃんと使っている」と胸を張って言えるレベルになります。
配列の中身で絞り込む
「このタグを持っているユーザー」を探す
前半の users.tags TEXT[] をそのまま使います。
やりたいことは、「タグ ‘premium’ を持っているユーザーを探したい」です。
最初に覚えるべき演算子は = ANY(...) と @> の2つです。
= ANY(tags) は、「tags のどれか1つと等しいか?」を意味します。
SELECT
id,
email,
tags
FROM users
WHERE 'premium' = ANY(tags);
SQLこのクエリは、「tags の中に ‘premium’ が1つでもあればマッチ」します。
イメージとしては「配列に ‘premium’ が含まれているか?」です。
もうひとつの書き方が @> です。
SELECT
id,
email,
tags
FROM users
WHERE tags @> ARRAY['premium'];
SQLtags @> ARRAY['premium'] は、「tags が ARRAY[‘premium’] を“含んでいるか”」を意味します。
配列同士の「包含」チェックです。
どちらも結果は同じですが、@> は「複数タグをまとめて条件にしたい」ときに強くなります。
例えば、「’premium’ と ‘beta_user’ の両方を持っているユーザー」を探したいときは、こう書けます。
SELECT
id,
email,
tags
FROM users
WHERE tags @> ARRAY['premium', 'beta_user'];
SQL「配列の中身で絞り込む」場面では、
「1つだけなら = ANY、複数まとめてなら @>」という感覚を持っておくと選びやすくなります。
配列を行に展開して集計する
「タグごとのユーザー数」を数える
次は、「タグごとに何人のユーザーがいるか」を数えてみます。
これは「配列をバラしてから集計する」典型パターンです。
unnest を使うと、配列を「1要素=1行」に展開できます。
SELECT
email,
unnest(tags) AS tag
FROM users;
SQLある行で tags = {"premium","beta_user"} なら、結果は2行になります。
1行目:tag = 'premium'
2行目:tag = 'beta_user'
これをCTEにして、タグごとに集計します。
WITH user_tags AS (
SELECT
id AS user_id,
unnest(tags) AS tag
FROM users
)
SELECT
tag,
COUNT(DISTINCT user_id) AS user_count
FROM user_tags
GROUP BY tag
ORDER BY user_count DESC;
SQL流れを整理するとこうです。
user_tags で「ユーザー×タグ」の行を作る。tag ごとに COUNT(DISTINCT user_id) で人数を数える。
結果は例えばこうなります。
tag | user_count
-------------+-----------
premium | 120
beta_user | 40
free | 300
dark_mode... | 25
「配列を展開してから GROUP BY する」というパターンは、
JSONの jsonb_array_elements と同じノリで、「配列の中身を集計対象にする」ための基本形です。
配列に要素を追加する
「既存のタグに ‘dark_mode_enabled’ を足す」
配列を更新するときは、「配列同士の演算」を使います。
一番よく使うのが、連結演算子 || です。
例えば、「premiumユーザー全員に ‘dark_mode_enabled’ タグを追加したい」とします。
UPDATE users
SET tags = tags || ARRAY['dark_mode_enabled']
WHERE 'premium' = ANY(tags);
SQLここでのポイントはこうです。
tags || ARRAY['dark_mode_enabled']
既存の配列の末尾に、新しい要素をくっつける。
ただし、このままだと「すでに ‘dark_mode_enabled’ を持っているユーザー」にも重複して追加される可能性があります。
重複を避けたいなら、条件を工夫します。
UPDATE users
SET tags = tags || ARRAY['dark_mode_enabled']
WHERE 'premium' = ANY(tags)
AND NOT ('dark_mode_enabled' = ANY(tags));
SQL「premiumを持っていて、かつまだdark_mode_enabledを持っていないユーザー」にだけ追加する、という形です。
配列更新の基本は、「新しい配列を作って入れ直す」イメージです。
「配列の中身を直接書き換える」というより、「元の配列+追加分で新しい配列を作る」と考えると理解しやすくなります。
配列から要素を削除する
「特定のタグだけ取り除く」
逆に、「特定のタグを配列から取り除きたい」こともあります。
このときは、array_remove を使います。
例えば、「全ユーザーから ‘beta_user’ タグを外したい」とします。
UPDATE users
SET tags = array_remove(tags, 'beta_user')
WHERE 'beta_user' = ANY(tags);
SQLarray_remove(tags, 'beta_user') は、「tags の中から ‘beta_user’ を全部取り除いた新しい配列」を返します。
結果として、
{"premium","beta_user"} → {"premium"}{"beta_user"} → {}
のように変わります。
ここでも、「配列を直接いじる」のではなく、「関数で加工した新しい配列をセットし直す」という発想です。
配列とセキュリティ・権限のイメージ
「“権限のリスト”をARRAYで持つときの注意」
セキュリティ寄りの話も少しだけ触れておきます。
例えば、「ユーザーが持つ権限を text[] で持つ」設計はよくあります。
roles TEXT[] に、{"admin","editor"} のような配列を入れるイメージです。
このとき、「admin権限を持つユーザー」を探すクエリはこうなります。
SELECT
id,
email
FROM users
WHERE 'admin' = ANY(roles);
SQLここで大事なのは、「クエリがシンプルだからこそ、権限の意味付けをアプリ側でちゃんと管理する」ことです。
「rolesに ‘admin’ が入っていたら何でもできる」ような設計にすると、
誤ってrolesを更新したときの影響が大きくなります。
現場では、
「rolesは“アプリ側のロール名”だけを入れる」
「ロール名ごとの具体的な権限は、別テーブルやアプリの設定で管理する」
といった分離をすることが多いです。
ARRAY自体はただの「文字列のリスト」なので、
「何を入れるか」「どう解釈するか」のルールをチームで決めておくことが、セキュリティ的にはとても重要になります。
ARRAYを使うときの思考パターン
「“リストとして扱いたい”のか、“正規化したい”のかを意識する」
最後に、ARRAYを使うかどうか迷ったときの考え方をまとめます。
「タグのように、“リストとしてまとめて扱いたい”」「JOINを増やしたくない」「件数もそこまで多くない」
→ ARRAYで持って、= ANY や @>、unnest でクエリするのは十分アリ。
「1つ1つの要素に対して、別の属性を持たせたい」「要素ごとに更新・削除したい」「要素数がかなり多くなりそう」
→ 素直に別テーブル(user_tags など)に正規化した方が、長期的には安全で柔軟。
PostgreSQLはARRAYもJSONも強いので、「全部正規化」「全部JSON」の二択ではなく、
「ここはARRAYで軽く」「ここはJSONで柔軟に」「ここは正規化して堅く」というグラデーションで設計できます。
Day13 後半のまとめ
ARRAYの実戦的な使い方は、「配列の中身で絞り込む('premium' = ANY(tags) や tags @> ARRAY['premium','beta_user'])」「配列を unnest(tags) で行に展開してからタグごとのユーザー数などを集計する」「tags || ARRAY['new_tag'] で要素を追加し、array_remove(tags,'beta_user') で要素を削除する」という3本柱で押さえると整理しやすい。
配列更新は「元の配列を関数や演算子で加工して、新しい配列としてセットし直す」イメージで考えると理解しやすく、権限リストのようなセキュリティに関わる情報をARRAYで持つ場合は、「rolesに何を入れるか」「どう解釈するか」のルールをアプリ側・チーム側で明確にしておくことが重要になる。
最終的には、「単純なリストで済むならARRAY」「構造が複雑ならJSON」「要素ごとにしっかり管理したいなら別テーブル」という選択肢の中から、要件に合わせて選べるようになることが、Day13 後半の着地点になる。
