〜エンジニア初学者の開発日記〜 七の巻

こんにちは、つかさです
今回もLaravelのクエリビルダについてまとめていこかなと思います。

## 前提条件

### Usersテーブル
|id|name|email|status|age|created_at|updated_at|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 1 | John Doe | john@example.com | active | 22 | 2024-01-01 10:00:00 | 2024-01-02 12:00:00 |
| 2 | Jane Smith | jane@example.com  | active | 30 | 2024-01-03 14:00:00 | 2024-01-04 16:00:00 |
| 3 | Mike Brown | mike@example.com  |inactive| 55 | 2024-02-01 08:30:00 | 2024-02-02 09:45:00 |
| 4 | SUZUKI Taro | trao@example.com  |inactive| 40 | 2024-02-10 09:30:00 | 2024-03-02 12:40:00 |

### Postsテーブル
|id|user_id|title|content|status|created_at|updated_at|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 1 | 1 | 初めての投稿 | これは最初の投稿です。| published | 2024-06-01 12:00:00 | 2024-06-01 12:00:00 |
| 2  | 1 | 旅行の思い出 | 先週の旅行について書きました。| draft | 2024-06-05 14:30:00 | 2024-06-06 10:15:00 |
| 3  | 2 | Laravelの使い方 | Laravelの基本を説明します。| published | 2024-06-10 09:00:00 | 2024-06-10 09:00:00 |
| 4  | 3 | 今日の出来事 | 今日は良い一日でした。| published | 2024-06-15 18:20:00 | 2024-06-15 18:45:00 |
| 5  | 5 | Vue.jsの基本 | Vue.jsの使い方を学ぼう。| draft | 2024-06-20 11:10:00 | 2024-06-21 08:30:00 |

## JOIN

### join()
- join() は INNER JOIN を実行します
これは 両方のテーブルに一致するデータがある場合のみ 結果を返します。

```php:例
 $results = DB::table('users')
    ->join('posts', 'users.id', '=', 'posts.user_id')
    ->select('users.id', 'users.name', 'posts.title')
    ->get();
```

```sql:
SELECT users.id, users.name, posts.title
FROM users
INNER JOIN posts ON users.id = posts.user_id;
```
✅ 共通部分のみ取得する（users と posts の両方にデータがある場合のみ）

❌ posts にデータがない users（例えば users.id = 4）は表示されない

### leftJoin() 
leftJoin() は LEFT OUTER JOIN を実行します。
これは 左側（users）の全データを保持し、右側（posts）に一致するデータがない場合は NULL を返す という動作になります。

```php:例
 $results = DB::table('users')
    ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
    ->select('users.id', 'users.name', 'posts.title')
    ->get();
```
```sql:
SELECT users.id, users.name, posts.title
FROM users
LEFT JOIN posts ON users.id = posts.user_id;
```
✅ users の全データを取得（posts にデータがなくても表示される）

✅ posts にデータがない users の場合、NULL になる
useIdが４のレコードは以下のような形になる。
|users.id|users.name|posts.title|
|:-:|:-:|:-:|
|4|SUZUKI Taro|NULL|


### rightJoin() 
rightJoin() は RIGHT OUTER JOIN を実行します。
これは 右側（posts）の全データを保持し、左側（users）に一致するデータがない場合は NULL を返す という動作になります。

```php:例
 $results = DB::table('users')
    ->rightJoin('posts', 'users.id', '=', 'posts.user_id')
    ->select('users.id', 'users.name', 'posts.title')
    ->get();
```
```sql:
SELECT users.id, users.name, posts.title
FROM users
RIGHT JOIN posts ON users.id = posts.user_id;
```
✅ posts の全データを取得（users にデータがなくても表示される）

✅ users にデータがない posts の場合、NULL になる

useIdが5のレコードは以下のような形になる。
|users.id|users.name|posts.title|
|:-:|:-:|:-:|
|NULL|NULL|Vue.jsの基本|

### crossJoin() 
- クロス結合（Cross Join） を実行するためのメソッドです
クロス結合とは、2つのテーブル（または配列）の 全組み合わせ を生成する結合方法です。

```php:例
DB::table('table1')->crossJoin('table2')->get();
```

```plaintext:例
users テーブル
+----+--------+
| id | name   |
+----+--------+
| 1  | Alice  |
| 2  | Bob    |
+----+--------+

products テーブル
+----+------------+
| id | product   |
+----+------------+
| 1  | Laptop    |
| 2  | Phone     |
| 3  | Tablet    |
+----+------------+
```

```plaintext:結果
+----+--------+----+------------+
| id | name   | id | product    |
+----+--------+----+------------+
| 1  | Alice  | 1  | Laptop     |
| 1  | Alice  | 2  | Phone      |
| 1  | Alice  | 3  | Tablet     |
| 2  | Bob    | 1  | Laptop     |
| 2  | Bob    | 2  | Phone      |
| 2  | Bob    | 3  | Tablet     |
+----+--------+----+------------+
```

:::note warn
※注意点
理屈上、上記のような結果が返ってくる想定ですが、カラム名が重複すると 後のテーブルの id が上書きされる 可能性があります。その場合idがuserテーブルのものかproductsテーブルのものかわからなくなってしまいます。
:::

この解決策として、`select`でエイリアスを付けるといった事が挙げられます。

```php:例
$results = DB::table('users')
    ->crossJoin('products')
    ->select(
        'users.id as user_id',
        'users.name',
        'products.id as product_id',
        'products.product'
    )
    ->get();
```

```plaintext:結果
[
    {
        "user_id": 1,
        "name": "Alice",
        "product_id": 1,
        "product": "Laptop"
    },
    {
        "user_id": 1,
        "name": "Alice",
        "product_id": 2,
        "product": "Phone"
    },
    {
        "user_id": 2,
        "name": "Bob",
        "product_id": 1,
        "product": "Laptop"
    }
]
```

### サブクエリを JOIN

- サブクエリ（副問合せ）とは、別のクエリの内部で実行されるSQLクエリ のことです

#### 1. selectSub() でサブクエリをカラムに追加
##### 例：ユーザーごとに最新の注文日時を取得

```php:例
$users = DB::table('users')
    ->select('users.id', 'users.name')
    ->selectSub(
        DB::table('orders')
            ->whereColumn('orders.user_id', 'users.id')
            ->selectRaw('MAX(created_at)'),
        'latest_order_date'
    )
    ->get();
```

```sql:
SELECT users.id, users.name,
    (SELECT MAX(created_at) FROM orders WHERE orders.user_id = users.id) AS latest_order_date
FROM users;
```

#### 2. whereIn() にサブクエリを使う
##### 例：注文があるユーザーのみ取得

```php:例
$users = DB::table('users')
    ->whereIn('id', function ($query) {
        $query->select('user_id')->from('orders');
    })
    ->get();
```

```sql:
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);
```

#### 3. whereExists() にサブクエリを使う
- ある条件を満たすデータが存在するかの判定ができる
##### 例：注文があるユーザーのみ取得

```php:例
$users = DB::table('users')
    ->whereExists(function ($query) {
        $query->select(DB::raw(1))
            ->from('orders')
            ->whereColumn('orders.user_id', 'users.id');
    })
    ->get();
```

```sql:
SELECT * FROM users
WHERE EXISTS (
    SELECT 1 FROM orders WHERE orders.user_id = users.id
);
```

#### 4. joinSub() を使ったサブクエリJOIN
- パフォーマンスを向上させつつクエリを最適化できる
##### 例：ユーザーと最新の注文情報を結合

```php:例
$latestOrders = DB::table('orders')
    ->select('user_id', DB::raw('MAX(created_at) as latest_order_date'))
    ->groupBy('user_id');

$users = DB::table('users')
    ->joinSub($latestOrders, 'latest_orders', function ($join) {
        $join->on('users.id', '=', 'latest_orders.user_id');
    })
    ->select('users.name', 'latest_orders.latest_order_date')
    ->get();
```

```sql:
SELECT users.name, latest_orders.latest_order_date
FROM users
JOIN (
    SELECT user_id, MAX(created_at) as latest_order_date
    FROM orders
    GROUP BY user_id
) latest_orders ON users.id = latest_orders.user_id;
```

#### 5. union() でサブクエリを結合
- 2つのクエリの結果を1つに結合できる

##### 例：管理者と一般ユーザーを取得

```php:例
$admins = DB::table('users')->where('role', 'admin');
$regularUsers = DB::table('users')->where('role', 'user');

$users = $admins->union($regularUsers)->get();
```

```sql:
SELECT * FROM users WHERE role = 'admin'
UNION
SELECT * FROM users WHERE role = 'user';
```

## まとめ
今回はクエリビルダの`Join`について整理しました。クエリビルダにかかわらず、生のSQLでもjoinは大切な要素です。ぜひこの機会に覚えちゃいましょう！
クエリビルダは範囲が広いので複数の記事で分割して投稿していく予定です。
読んでいただきありがとうございました！