TypeScriptでSupabaseのテーブルからリレーションを使ってデータを取得する

## はじめに
TypeScriptでリレーションを使用してSupabaseのテーブルからデータを取得する方法についてまとめました。

リレーションとは、データベース内の複数のテーブル間で関係を構築することを指します。これにより、複数のテーブルから関連データを一度に取得しやすくなります。

この記事では、1対1、1対多、多対多のリレーションを設定し、実際にデータを取得する手順を例を交えて紹介します。

SupabaseはPostgreSQLをベースにしているため、リレーション機能をサポートしています。これにより、データの結合や集約が容易に行え、効率的なデータ操作が可能になります。


## 今回使用するテーブル構成
	• users テーブル
    	• id
    	• username
    	• profile_id
	• profiles テーブル
    	• id
    	• name
	• posts テーブル
    	• id
    	• user_id
    	• title
	• user_roles テーブル
    	• user_id
    	• role_id
	•	roles テーブル
    	• id
    	• role_name

## 外部キーを設定する
Supabaseでリレーションを設定するには、データベーステーブルの定義において適切な外部キー（foreign key）制約を追加する必要があります。

以下のようにusersテーブルにprofile_idカラムを追加し、これをprofilesテーブルのidカラムに関連付けることで、リレーションを貼ることができます。


```sql
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  username VARCHAR(255)
);

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255),
  user_id INTEGER,
  FOREIGN KEY (user_id) REFERENCES users (id)
);
```

こんな感じで、他のテーブルにも外部キーを貼っていきます。

## 実際にリレーションを使ってデータを取得する

### 1対1のリレーション

users と profiles のリレーションを取得する例です。

```ts
const { data, error } = await supabase
  .from('users')
  .select(`
    id,
    username,
    profiles (
      id,
      name
    )
  `)

if (error) {
  console.error('Error fetching users with profiles:', error)
} else {
  console.log('Users with profiles:', data)
}
```

レスポンス例
```json
[
  {
    "id": 1,
    "username": "user1",
    "profiles": {
      "id": 1,
      "name": "Profile 1"
    }
  },
  {
    "id": 2,
    "username": "user2",
    "profiles": {
      "id": 2,
      "name": "Profile 2"
    }
  }
]
```

### 1対多のリレーション

users と posts のリレーションを取得する例です。

```ts
const { data, error } = await supabase
  .from('users')
  .select(`
    id,
    username,
    posts (
      id,
      title
    )
  `)

if (error) {
  console.error('Error fetching users with posts:', error)
} else {
  console.log('Users with posts:', data)
}
```

レスポンス例
```json
[
  {
    "id": 1,
    "username": "user1",
    "posts": [
      {
        "id": 1,
        "title": "Post 1"
      },
      {
        "id": 2,
        "title": "Post 2"
      }
    ]
  },
  {
    "id": 2,
    "username": "user2",
    "posts": [
      {
        "id": 3,
        "title": "Post 3"
      }
    ]
  }
]
```


### 多対多のリレーション

users と roles の多対多のリレーションを user_roles テーブルを通じて取得する例です。

```ts
const { data, error } = await supabase
  .from('users')
  .select(`
    id,
    username,
    user_roles (
      roles (
        id,
        role_name
      )
    )
  `)

if (error) {
  console.error('Error fetching users with roles:', error)
} else {
  console.log('Users with roles:', data)
}
```

レスポンス例
```json
[
  {
    "id": 1,
    "username": "user1",
    "user_roles": [
      {
        "roles": {
          "id": 1,
          "role_name": "Admin"
        }
      },
      {
        "roles": {
          "id": 2,
          "role_name": "User"
        }
      }
    ]
  },
  {
    "id": 2,
    "username": "user2",
    "user_roles": [
      {
        "roles": {
          "id": 2,
          "role_name": "User"
        }
      }
    ]
  }
]
```


### 複数のリレーションの組み合わせ

複数のリレーションを組み合わせてデータを取得することも可能で、users、profiles、postsを同時に取得する場合は以下のようになります。

```ts
const { data, error } = await supabase
  .from('users')
  .select(`
    id,
    username,
    profiles (
      id,
      name
    ),
    posts (
      id,
      title
    )
  `)

if (error) {
  console.error('Error fetching users with profiles and posts:', error)
} else {
  console.log('Users with profiles and posts:', data)
}
```

レスポンス例
```json
[
  {
    "id": 1,
    "username": "user1",
    "profiles": {
      "id": 1,
      "name": "Profile 1"
    },
    "posts": [
      {
        "id": 1,
        "title": "Post 1"
      },
      {
        "id": 2,
        "title": "Post 2"
      }
    ]
  },
  {
    "id": 2,
    "username": "user2",
    "profiles": {
      "id": 2,
      "name": "Profile 2"
    },
    "posts": [
      {
        "id": 3,
        "title": "Post 3"
      }
    ]
  }
]
```

## INNER JOINとLEFT JOINで取得する場合

SupabaseはPostgreSQLを基盤としているため、PostgreSQLでサポートされているJOINの種類を利用できます。

### INNER JOIN
INNER JOINは、両方のテーブルに共通するデータのみを取得します。

```ts
const { data, error } = await supabase
  .from('users')
  .select(`
    id,
    username,
    profiles!inner (
      id,
      name
    )
  `)

if (error) {
  console.error('Error fetching users with profiles:', error)
} else {
  console.log('Users with profiles:', data)
}
```

### LEFT JOIN
LEFT JOINは、左側のテーブルのすべてのデータと、右側のテーブルに一致するデータを取得します。一致しない場合、右側のテーブルのデータはNULLになります。

```ts
const { data, error } = await supabase
  .from('users')
  .select(`
    id,
    username,
    profiles!left (
      id,
      name
    )
  `)

if (error) {
  console.error('Error fetching users with profiles:', error)
} else {
  console.log('Users with profiles:', data)
}
```

## おわりに

リレーションを利用することで、複数のテーブルにまたがる複雑なデータを効率的に扱うことができます。

Supabaseの柔軟なクエリ機能を活用することで、INNER JOINやLEFT JOINなどの様々なJOINを使ってデータを取得することもできます。

TypeScriptとSupabaseを使ってリレーションを設定し、データを取得する方法について個人的な備忘録としても兼ねて紹介しました。