
ORM?流行りものでしょ
SQLで良くね?😨→
クエリスコープで保守しやすくなるぞ🐱●抽象的に内容がわかるから保守性アップ
●条件分岐と組みあわせてクエリの組み立ての柔軟性
●コードが短く目と頭にやさしい#駆け出しエンジニア #プログラミング初心者 https://t.co/5sbnGp5iYW— 優さん🌷わくわく開発YouTuber (@yuu13n6) May 16, 2020
もくじ
まずは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();
        }
スコープとして関数にすることでパッと見で抽象的にクエリの内容を理解しやすくなります🐱

