SQL

Laravel クエリスコープを使おう!🐱 Query Scope

Laravel

まずはSQLとQuery Builderの比較

中間テーブルとの結合のSQLを書いてみる

 

SQLで書いた場合

<?php

namespace App\Repositories\Status\Member;

use App\Http\Models\Members;
use App\Http\Models\Section;
use Illuminate\Http\Request;
use App\Repositories\Status\Member\PerformanceListRepositoryInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\Paginator;

class PerformanceListRepository implements PerformanceListRepositoryInterface
{
    public function execute()
    {
       return self::getMembersWithSection();
    }

    public function getMembersWithSection()
    {
        $sql = <<< SQL
        SELECT
        tb_member.member_id,
        tb_member.fullname,
        tb_member.count_burning,
        tb_member.count_join_project,
        tb_member.count_pm,
        tb_member.count_member,
        tb_member.count_sales,
        tb_member.skill,
        tb_sectiosection_name
        FROM
        tb_member
        INNER JOIN
        (
            SELECT
            member_id,
            section_id,
            created
            FROM
            tb_member_section
            WHERE
            deleted = 0
            AND
            created = (
                SELECT
                  MAX(created)
                FROM
                  tb_member_section AS tmp_tb_member_section
                WHERE
                  tb_member_section.member_id = tmp_tb_member_section.member_id
                AND 
                  tb_member_section.deleted = 0
                )
            ) as ud
        ON tb_member.member_id = ud.member_id
        LEFT JOIN
        tb_section
        ON tb_section.section_id = ud.section_id
        AND tb_section.deleted = 0
        WHERE (tb_member.deleted = 0)
        GROUP BY ud.member_id

SQL;

    }
}

 

QueryBuilderで書いた場合

<?php

namespace App\Repositories\Status\Member;

use App\Http\Models\Members;
use App\Repositories\Status\Member\PerformanceListRepositoryInterface;
use Illuminate\Support\Facades\DB;

class PerformanceListRepository implements PerformanceListRepositoryInterface
{
    public function execute()
    {
       return self::getMembersWithSection();
    }

    public function getMembersWithSection()
    {
        $query = Members::query()->where(function ($query) {
            return $query->where('tb_member.deleted', '=', '0');
        });

        $subQuery = DB::table('tb_member_section')
                                  ->select('member_id', 'section_id', 'created')
                                  ->where('deleted', '=', '0')
                                  ->whereRaw('created = (
                                                  SELECT
                                                    MAX(created)
                                                  FROM
                                                    tb_member_section AS tmp_tb_member_section
                                                  WHERE tb_member_section.member_id = tmp_tb_member_section.member_id
                                                  AND tb_member_section.deleted = 0
                                              )
                                  ');
        //var_dump($subQuery->toSql()); // string(629) "select `member_id`, `section_id`, `created` from `tb_member_section` where `deleted` = ? and created = ( SELECT MAX(created) FROM tb_member_section AS tmp_tb_member_section WHERE tb_member_section.member_id = tmp_tb_member_section.member_id AND tb_member_section.deleted = 0 )"
        //exit();

        return $query->select(
                                'tb_member.member_id',
                                'tb_member.fullname',
                                'tb_member.count_burning',
                                'tb_member.count_join_project',
                                'tb_member.count_pm',
                                'tb_member.count_member',
                                'tb_member.count_sales',
                                'tb_member.skill',
                                'tb_section.section_name'
                              )->join(DB::raw('('. $subQuery->toSql() .') as ud'), function ($join) {
                                  $join->on('tb_member.member_id','=','ud.member_id');
                              })
                              ->mergeBindings($tb_member_section)
                              ->leftjoin('tb_section', function ($join) {
                                  $join->on('tb_section.section_id', '=', 'ud.section_id')
                                      ->where('tb_section.deleted', '=', '0');
                              })
                              ->groupBy('ud.member_id')
                              ->paginate(20);

        //var_dump($query->toSql()); // string(1128) "select `tb_member`.`member_id`, `tb_member`.`fullname`, `tb_member`.`count_burning`, `tb_member`.`count_join_project`, `tb_member`.`count_pm`, `tb_member`.`count_member`, `tb_member`.`count_sales`, `tb_member`.`skill`, `tb_section`.`section_name` from `tb_member` inner join (select `member_id`, `section_id`, `created` from `tb_member_section` where `deleted` = ? and created = ( SELECT MAX(created) FROM tb_member_section AS tmp_tb_member_section WHERE tb_member_section.member_id = tmp_tb_member_section.member_id AND tb_member_section.deleted = 0 )) as ud on `tb_member`.`member_id` = `ud`.`member_id` left join `tb_section` on `tb_section`.`section_id` = `ud`.`section_id` and `tb_section`.`deleted` = ? where (`tb_member`.`deleted` = ?) group by `ud`.`member_id` limit 20 offset 0"
        //exit();
    }
}
  • ->join() 内部結合
  • ->leftJoin() 左外部結合
  • ->mergeBindings($tb_section) サブクエリのバインドの値をマージ

行数が少なめになりました🐱
サブクエリ使っちゃってるのでムズいが。

// モデルでリレーションを定義してあげるともっと良いです。

Query Builderの何が嬉しいの?

 

クエリスコープを利用することで保守がしやすくなります

<?php
namespace App\Http\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use DB;
use Illuminate\Support\Collection;

class Plan extends Model
{

・・・

    /**
     * 生きているデータ。論理削除されていない
     *
     * @param Builder $query
     * @return Builder
     */
    public function scopeIsNotDeleted(Builder $query)
    {
        return $query->where('deleted', '0');
    }

    /**
     * 炎上案件かどうか
     *
     * @param Builder $query
     * @return Builder
     */
    public function scopeIsBurning(Builder $query)
    {
        return $query->where('deleted', '=', 0)
                   ->where(function ($query) {
                       $query->where('is_burning', '=', 1);
                   });
    }

    /**
     * 担当か関係者として関わっている
     *
     * @param Builder $query
     * @return Builder
     */
    public function scopeisMyPlan(Builder $query, $memberId)
    {
        return $query->where('deleted', '=', 0)
                   ->where(function ($query) use($memberId) {
                        $query->orWhere('member_id', '=', $memberId)
                            ->orWhere('member_ids', 'like', '%,'.$memberId.',%');
                   });
    }

・・・

}

 

定義したクエリスコープは次のように利用できます

isNotDeleted(), isMyPlan(), isBurning()として利用しています。

        // 会員の集計ステータス更新
        $members = Members::query()->where(function ($query) {
            return $query->where('tb_member.deleted', '=', '0');
        })->get();

        foreach ($members as $member) {
            $countPm = Plan::isNotDeleted()->where('member_id', $member->member_id)->count();
            $countUser = Plan::isNotDeleted()->where('member_ids','like','%,'.$member->member_id.',%')->count();
            $countJoinPlan = Plan::isMyPlan($member->member_id)->count();
            $countSales = Plan::isMyPlan($member->member_id)->sum('plan_total_price');
            $countBurning = Plan::isMyPlan($member->member_id)
                                ->isBurning()
                                ->count();

            $memberInstance = Members::find($member->member_id);
            $memberInstance->count_join_project = $countJoinPlan;
            $memberInstance->count_burning = $countBurning;
            $memberInstance->count_pm = $countPm;
            $memberInstance->count_member = $countUser;
            $memberInstance->count_sales = $countSales;
            $memberInstance->save();
        }

スコープとして関数にすることでパッと見で抽象的にクエリの内容を理解しやすくなります🐱

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)