前置き
Supabase + フロントエンド(Reactなど)でアプリを作っていて、データの「ステータス」を 「inactive(キャンセル)」 に更新したはずが、なぜか再度 「searching(募集状態)」 に戻ってしまう
そんな現象に悩まされた場合の原因と対処法を解説します。
結論:トリガーがステータスを「searching」or「full」に強制更新していた
DB 側に、以下のようなテーブルとトリガー関数が設定されているケースです。
- テーブル例:
activities
- 参加者を管理する中間テーブル:
activity_participants
- ステータス管理用カラム:
phase
- トリガー関数:
on_activity_created or on_participant_change
このトリガー関数の中で、参加者数をカウントし、
UPDATE activities
SET
current_people = <参加者数>,
phase = CASE
WHEN <参加者数> >= max_people THEN 'full'
ELSE 'searching'
END
WHERE id = ...
…という処理が走っていると、いくらフロントエンドで phase = 'inactive'
に更新しても、その後に参加者が増減すると 再度 phase = 'searching'(または full)
に書き戻されてしまう のです。
なぜ「inactive」にしても「searching」に戻ってしまうのか
1.フロントエンドで UPDATE activities SET phase = 'inactive'
する
2.しかしその後、参加者の追加・削除など、activity_participants に変化があるとトリガーが発火
3.トリガー内で無条件に
phase = CASE
WHEN participant_count >= max_people THEN 'full'
ELSE 'searching'
END
と書き換えてしまい、“inactive” が “searching” に上書きされる
4.結果として、「1回更新したのに、いつの間にかステータスが戻っている」状況になる
対処法
- すでに「inactive」や「done」になったレコードは上書きしない
トリガー関数のロジックを変更し、「現在すでに phase が “inactive” (or “done”) の場合は更新しない」 という分岐を追加します。
DECLARE
participant_count integer;
current_phase text;
BEGIN
-- まず現在のステータスを取得
SELECT phase
INTO current_phase
FROM activities
WHERE id = COALESCE(NEW.activity_id, OLD.activity_id);
-- すでにinactive or done なら上書きしない
IF current_phase IN ('inactive', 'done') THEN
RETURN NULL;
END IF;
-- 参加者数をカウント
SELECT COUNT(*) INTO participant_count
FROM activity_participants
WHERE activity_id = COALESCE(NEW.activity_id, OLD.activity_id);
-- 参加者数に応じてステータスを更新
UPDATE activities
SET
current_people = participant_count,
phase = CASE
WHEN participant_count >= max_people THEN 'full'
ELSE 'searching'
END
WHERE id = COALESCE(NEW.activity_id, OLD.activity_id);
RETURN NULL;
END;
これで、一度「inactive」や「done」にしたアクティビティは、参加者数が変わっても自動更新の対象外 となり、ステータスが勝手に戻ることはありません。
2.フロントエンドでも「キャンセルされたデータへの操作」を制御する
いったん「inactive」にした後は、参加受付を無効にする
UI 上からは「inactive」状態のアクティビティを表示しない・操作できないようにする
など、ユーザーがキャンセル後に再度参加/離脱リクエストを送れない仕組み を組み込みましょう。こうすることで、上記トリガーが発火するシーン自体がなくなり、アクティビティが「inactive」状態のまま保たれます。
3.そもそもトリガーを分割 or 廃止する
「アクティビティ作成時に初期設定をする」「参加者数に応じて自動的にステータスを切り替える」という機能があまり必要なければ、思い切ってトリガーをなくしてしまう のも選択肢です。カウントやステータス制御をすべてフロントロジックで行う方が分かりやすくなる場合もあります。
まとめ
-
ステータスが意図せず上書きされている理由: 裏で走っているトリガー関数が、参加者数をもとに一律 「searching」「full」に更新していた
-
解決策: トリガー内で「すでに phase が “inactive” や “done” になっている場合は上書きしない」などの条件を追加
-
注意点: キャンセル後に参加者が動かないよう、フロントエンドでも操作制御を行う
Supabase や Postgres でトリガーを使う際は、中の関数が「無条件にカラムを更新していないか」を必ずチェックしましょう。一度ステータスを更新しても、今回のように勝手に戻る現象が起きる場合は、「実はトリガーが裏で書き換えていた」 ことがよくある原因です。