はじめに

Laravel で Has Many や Belongs To Many なリレーションを持つモデルに対して、withCounthaving を使って対応モデルの数を意識した絞り込みを行う方法。

使用イメージとしては、下記などが考えられる。

  • 顧客(customer) が n人以上いるお店(shop)を絞り込む
  • 部屋(Room)の収容可能人数と、利用者(user)数を比較して空きがあるものを絞り込む

TL;DR

  • Model::withCount('xxxx')->having('xxxx_count', '>', 1) のようにすれば良い

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
  5. まとめ
  6. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
$ grep -i pretty /etc/os-release
PRETTY_NAME="Ubuntu 16.04.3 LTS"

$ php -v
PHP 7.2.22-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Sep 2 2019 12:54:12) ( NTS )

$ composer -V
Composer version 1.9.0 2019-08-02 20:55:32

$ composer info laravel/framework
name : laravel/framework
versions : * v5.7.28

詳細

参考: php - Laravel using where clause on a withCount method - Stack Overflow

冒頭に書いた「顧客(customer) が n人以上いるお店(shop)を絞り込む」を例に記述。

モデルは下記、customer_shop のような中間テーブル(交差テーブル)を作成し、belongsToMany で関係づけられている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app/Models/Shop
<?php
namespace App\Models;
use App\Models\Customer;
use Illuminate\Database\Eloquent\Model;

class Shop extends Model
{
public function customers()
{
return $this->belongsToMany(Customer::class);
}
}

// app/Models/Customer
<?php
namespace App\Models;
use App\Models\Shop;
use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
public function shops()
{
return $this->belongsToMany(Shop::class);
}
}

適当にレコードを生成し、Tinker 上で確認してみる。

1
2
3
4
5
6
7
8
9
10
11
12
>>> Shop::withCount('customers')->having('customers_count', '>', 1)->get()
=> Illuminate\Database\Eloquent\Collection {#3388
all: [
App\Models\Shop {#3437
id: 1,
name: "Flower shop",
created_at: "2020-02-12 19:10:07",
updated_at: "2020-02-12 19:10:07",
customers_count: 2,
},
],
}

SQL はこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> Shop::withCount('customers')->having('customers_count', '>', 1)->toSql()
=> "select
`shops`.*,
(
select
count(*)
from
`customers`
inner join `shop_customer` on `customers`.`id` = `shop_customer`.`customer_id`
where
`shops`.`id` = `shop_customer`.`shop_id`
) as `customers_count`
from
`shops`
having
`customers_count` > ?"

あとは適当に Scope を作成すれば良い。(個人的には命名が一番難しいと思っている)

1
2
3
4
5
6
7
8
9
10
11
class Shop extends Model
{
...
public scopeHasCustomersOver($query, $count = 1)
{
return $query
->withCount('customers')
->having('customers_count', '>', $count);
}
...
}

まとめ

  • Model::withCount('xxxx')->having('xxxx_count', '>', 1) のようにすれば良い

参考文献

関連記事