概要説明
こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。
リニューアルプロジェクト通して溜まったLaravelのEloquentのナレッジを活用して逆引きリファレンスにしてみようかと思います。皆様の実装の助けになれば幸いです。
テーブル
今回は店舗テーブルとカテゴリテーブルおよび中間テーブルを例に出して解説をしていきます。テーブル設計、サンプルデータおよびEloquentモデルは以下の通りです。
ER図
shop(店舗テーブル)
shop_id | name | prefecture_id | review_count | photo_count |
---|---|---|---|---|
1 | エキテンマッサージ | 1 | 100 | 90 |
2 | エキテン整骨院 | 1 | 90 | 90 |
3 | エキテン接骨院 | 2 | 90 | 100 |
category(カテゴリテーブル)
category_id | name |
---|---|
1 | マッサージ |
2 | 整体 |
3 | 整骨 |
4 | 接骨 |
shop_category(中間テーブル)
id | shop_id | category_id |
---|---|---|
1 | 1 | 1 |
2 | 2 | 1 |
3 | 2 | 2 |
4 | 2 | 3 |
5 | 3 | 1 |
6 | 3 | 2 |
7 | 3 | 4 |
DDL/DML
CREATE TABLE `shop` (
`shop_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_bin NOT NULL,
`prefecture_id` int(11) NOT NULL,
`review_count` int(11) NOT NULL,
`photo_count` int(11) NOT NULL,
PRIMARY KEY (`shop_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `shop` VALUES (1, 'エキテンマッサージ', 1, 100, 90);
INSERT INTO `shop` VALUES (2, 'エキテン整骨院', 1, 90, 90);
INSERT INTO `shop` VALUES (3, 'エキテン接骨院', 2, 90, 100);
CREATE TABLE `category` (
`category_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`category_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `category` VALUES (1, 'マッサージ');
INSERT INTO `category` VALUES (2, '整体');
INSERT INTO `category` VALUES (3, '整骨');
INSERT INTO `category` VALUES (4, '接骨');
CREATE TABLE `shop_category` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`shop_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `shop_category` VALUES (1, 1, 1);
INSERT INTO `shop_category` VALUES (2, 2, 1);
INSERT INTO `shop_category` VALUES (3, 2, 2);
INSERT INTO `shop_category` VALUES (4, 2, 3);
INSERT INTO `shop_category` VALUES (5, 3, 1);
INSERT INTO `shop_category` VALUES (6, 3, 2);
INSERT INTO `shop_category` VALUES (7, 3, 4);
モデル
Shop
<?php namespace App\Adapter\Gateway\Dao\Eloquent\Model; use Illuminate\Database\Eloquent\Model; class Shop extends Model { protected $table = 'shop'; protected $primaryKey = 'shop_id'; }
Category
<?php namespace App\Adapter\Gateway\Dao\Eloquent\Model; use Illuminate\Database\Eloquent\Model; class Category extends Model { protected $table = 'category'; protected $primaryKey = 'category_id'; }
ShopCategory
<?php namespace App\Adapter\Gateway\Dao\Eloquent\Model; use Illuminate\Database\Eloquent\Model; class ShopCategory extends Model { protected $table = 'shop_category'; protected $primaryKey = 'id'; }
A AND (B OR C)
shopテーブルから prefecture_id
が 1かつ、 review_count
もしくは photo_count
が100以上のレコードを取得するとします(該当するレコードはshop_id = 1
のレコード)。
単純に考えると以下のような考えに至るかと思います。
<?php Shop::query() ->where("prefecture_id", "=", 1) ->where("review_count", ">=", 100) ->orWhere("photo_count", ">=", 100) ->get();
しかし、これだと shop_id = 1
のレコードと、 shop_id = 3
のレコードが取得されてしまいます。上記の実装のWhere句を生のSQLで書くと
prefecture_id = 1 AND review_count >= 100 OR photo_count >= 100
となり、評価の順番が prefecture_id = 1
, AND review_count >= 100
, OR photo_count >= 100
となるため prefecture_id
の値に関わらず photo_count >= 100
のレコードが全て取得されてしまうためです。
A AND (B OR C)のように明示的に評価の順番を指定するためには以下のようにクロージャで定義する必要があります。
<?php Shop::query() ->where("prefecture_id", "=", 1) ->where(function ($query) { $query->where("review_count", ">=", 100) ->orWhere("photo_count", ">=", 100); }) ->get();
クロージャで定義した場合はクロージャ内が先に評価されます。そのため review_count >= 100
, OR photo_count >= 100
が先に評価され、その後に AND prefecture_id = 1
が評価されることとなり、無事 shop_id = 1
のレコードのみが取得されます。
GROUP BYごとにCOUNTする
shop_categoryテーブルから category_id
ごとのレコード数(店舗数)を取得するとします。
直感的には以下のように groupBy
メソッドと count
メソッドを利用すれば取得できそうです。
<?php ShopCategory::query() ->groupBy("category_id") ->count();
しかし、これでは 3
が取得されます。上記の記述ではshopテーブルを category_id
でGROUP BYした結果の1行目の値のみが取得されてしまうためです(今回の場合では category_id = 1
のレコード数)。
shop_categoryテーブルから category_id
ごとのレコード数を取得するためには以下のように DB::raw
を利用して生のSQLを併用する必要があります。
<?php ShopCategory::query() ->select(DB::raw('category_id, COUNT(*) AS count')) ->groupBy("category_id") ->get();
上記の記述によりGROUP BYごとにCOUNTすることが可能となります。
関連テーブルと結合するレコードを取得
category_id = 2
の shop_category のレコードと shop_id
で結合する shop テーブルのレコードを取得するとします。
まずは Shop
モデルに ShopCategory
モデルとの リレーション である category
メソッドを定義します。
<?php namespace App\Adapter\Gateway\Dao\Eloquent\Model; use Illuminate\Database\Eloquent\Model; class Shop extends Model { protected $table = 'shop'; protected $primaryKey = 'shop_id'; public function category(): HasMany { return $this->hasMany(ShopCategory::class, 'shop_id', 'shop_id'); } }
リレーションを定義した上でクエリは以下のように記述します。
<?php Shop::query() ->whereHas("category", function ($query) { $query->where("category_id", "=", 2); }) ->get();
whereHas
メソッドは第一引数に指定した名前のリレーションを持つことを条件とします。さらに第二引数にクロージャを定義することができ、クロージャ内でリレーションに条件を追加することができます。今回の場合は shop.shop_id = shop_category.shop_id
の条件に AND shop_category.category_id = 2
を追加しています。
これにより関連テーブルと結合するレコードを取得が可能となります。
おわりに
仲間を募集しております
募集中の職種については以下を御覧ください。