【Supabase】複数テーブルの関連データを取得したい

# はじめに
Supabaseから複数の関連テーブルからデータを取得しようとした際につまりました。

# 問題
以下の３つのテーブルを定義していました。
下記の手順でデータを取得したかったです。

* usersテーブルのuser_idを元に、user_skillテーブルのuser_idを参照
* user_skillテーブルのskill_idを元に、skillsテーブルのidを参照
* skillsテーブルのnameを取得
<br>

* **usersテーブル**

| user_id: varchar | name: varchar | description: text | a_id: varchar | b_id: varchar | c_id: varchar | created_at: timestamptz |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| sample-id | なまえ | \<h1>なまえ\</h1> | aのid | bのid | cのid | 2025-XX-XX |

* **user_skillテーブル**

| id: int8 | user_id: varchar | skill_id: int8 | created_at: timestamptz |
|:-:|:-:|:-:|:-:|
| 1 | sample-id | 1 | 2025-XX-XX |

* **skillsテーブル**

| id: int8 | name: varchar | created_at: timestamptz |
|:-:|:-:|:-:|
| 1 | React | 2025-XX-XX |
| 2 | TypeScript | 2025-XX-XX |
| 3 | GitHub | 2025-XX-XX |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/9b300f86-da16-4200-978f-a022d1ba7f8f.png)



# 解決方法
## user_skillテーブルに外部キーを設定する

* user_skillテーブルの`user_id`を外部キーとして設定
（参照先はusersテーブルの主キー: `user_id`）
* user_skillテーブルの`skill_id`を外部キーとして設定
（参照先はskillsテーブルの主キー: `id`）

### 外部キーとは
あるテーブルのカラム（列）が別のテーブルの`主キー`を参照すること

### なぜ外部キーの設定が必要？
外部整合制約のメリットをもらうため

#### データの整合性保持
skillsテーブルのid=1のレコードを削除した場合、user_skillテーブルのskill_id=1はどこも参照できず、ただのデータになってしまいます。

#### 誤削除の防止
外部キーの設定を行うことで、意図せずレコードを削除してしまうことを防ぐことができます。

試しに、skill_id=1を消そうとすると、エラーとなり削除を防ぐことができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/6a6e8553-b3e4-43a1-afb6-5f9b42764fec.png)


## Supabaseのdashbordから外部キーを設定する
1. user_skillテーブルのuser_idをクリック、`Edit column`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/b99f4be4-60a9-46a1-82ae-0fd27cd8fdd1.png)

2\. `Add foreign key`を押下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/01c12a4b-3a2f-4efc-a396-3899c0bfdad3.png)


3\. 参照するテーブルを選択（今回は`usersテーブル`）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/a07e574c-f5e0-4ae4-ab93-9ae23cd6fc1a.png)

4\. 参照するカラムを選択（今回は`user_idカラム`）選択後、`Save`を押下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/5ded0301-377f-4fb6-9762-778d1d01f452.png)


## リレーションで取得するように修正

Supabaseでは外部キーの関係を自動的に検出して、簡単にデータを取得できます

* 公式ドキュメントより引用
>The data APIs automatically detect relationships between Postgres tables. Since Postgres is a relational database, this is a very common scenario.
>> (日本語訳)
データAPIは自動的にPostgresテーブル間の関係を検出します。Postgresはリレーショナルデータベースなので、これは非常に一般的なシナリオです。

```ts:
  const { data, error } = await supabase
    .from("users")
    .select("*, user_skill(skills(name))")
    .eq("user_id", id);
```

* `from("users")`: usersテーブルから取得する
* `.select("* `: `*`でusersテーブルの全てのカラムを取得する
* `user_skill()`: usersのuser_idと紐づくものを取得
* `skills(name)`：user_skillと紐づくidからskillsテーブルのidと紐づくものを取得

以下のSQLと同じことをしています。

```sql
SELECT 
  users.*,
  skills.name
FROM 
  users
LEFT JOIN 
  user_skill ON users.id = user_skill.user_id
LEFT JOIN 
  skills ON user_skill.skill_id = skills.id
WHERE 
  users.user_id = [id値]
```



# おわりに
外部キーをなぜ指定しないといけないのかわかっていない状態でした。
SQLの学習も進めていきたいです。

# 参考
https://qiita.com/kitao6/items/7de2e73c3fff82a43da8

https://supabase.com/docs/guides/database/joins-and-nesting?queryGroups=output-format&output-format=table&queryGroups=language&language=js

https://supabase.com/docs/reference/javascript/select