Laravel多表关联查询:菜品销量排名报表实现问题
解决方案:Laravel模型关联与菜品销量统计
我来帮你搞定这个关联和统计的问题,核心是先理清三个表的层级关系,再用合适的Eloquent关联或者查询构建器实现需求,咱们一步步来:
一、先理清楚模型关联逻辑
你的表关系链条是:Record(销量记录)→ 属于 DayRecipeeShopWorkshift(日期-菜品-门店-班次关联)→ 属于 Recipee(菜品)。基于这个链条,我们给每个模型定义正确的关联:
1. Record 模型(app/Models/Record.php)
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasOneThrough; class Record extends Model { protected $table = 'records'; // 可选,表名符合Laravel复数规则的话可以省略 // 关联到中间表 public function dayRecipeeShopWorkshift(): BelongsTo { return $this->belongsTo(DayRecipeeShopWorkshift::class); } // 直接关联到菜品(通过中间表) public function recipee(): HasOneThrough { return $this->hasOneThrough( Recipee::class, DayRecipeeShopWorkshift::class, 'id', // 中间表的主键 'id', // 菜品表的主键 'day_recipee_shop_workshift_id', // 当前表关联中间表的外键 'recipee_id' // 中间表关联菜品表的外键 ); } }
2. DayRecipeeShopWorkshift 模型(app/Models/DayRecipeeShopWorkshift.php)
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; class DayRecipeeShopWorkshift extends Model { protected $table = 'day_recipee_shop_workshifts'; public function recipee(): BelongsTo { return $this->belongsTo(Recipee::class); } public function records(): HasMany { return $this->hasMany(Record::class); } }
3. Recipee 模型(app/Models/Recipee.php)
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; class Recipee extends Model { protected $table = 'recipees'; public function dayRecipeeShopWorkshifts(): HasMany { return $this->hasMany(DayRecipeeShopWorkshift::class); } // 关联所有对应的销量记录 public function records(): HasManyThrough { return $this->hasManyThrough(Record::class, DayRecipeeShopWorkshift::class); } }
二、获取单条销量记录并按销量排序
如果你需要取出所有records数据,同时带上对应的菜品名称,并按单条记录的count降序排列,可以这么写:
use App\Models\Record; $recordsWithRecipee = Record::with('recipee') ->orderBy('count', 'desc') ->get(); // 遍历输出示例 foreach ($recordsWithRecipee as $record) { echo "菜品名称:{$record->recipee->name},本次售出:{$record->count}"; }
三、生成菜品销量排名报表(核心需求)
这应该是你最需要的:按菜品分组统计总销量,然后生成排名。这里有两种优雅的实现方式:
方式1:使用查询构建器(Join方式)
适合需要自定义SQL逻辑的场景:
use App\Models\Record; use Illuminate\Support\Facades\DB; $recipeeRanking = Record::select( 'recipees.name', DB::raw('SUM(records.count) as total_sold') ) ->join('day_recipee_shop_workshifts', 'records.day_recipee_shop_workshift_id', '=', 'day_recipee_shop_workshifts.id') ->join('recipees', 'day_recipee_shop_workshifts.recipee_id', '=', 'recipees.id') ->groupBy('recipees.id', 'recipees.name') // 按菜品ID+名称分组,确保唯一性 ->orderBy('total_sold', 'desc') ->get(); // 输出排名示例 foreach ($recipeeRanking as $rank => $item) { echo "第".($rank+1)."名:{$item->name},总销量:{$item->total_sold}"; }
方式2:使用Eloquent关联统计(更符合Laravel风格)
利用菜品模型的关联直接统计总销量:
use App\Models\Recipee; $recipeeRanking = Recipee::withCount([ 'records as total_sold' => function ($query) { $query->select(DB::raw('SUM(count)')); } ]) ->orderBy('total_sold', 'desc') ->get(); // 输出排名示例 foreach ($recipeeRanking as $rank => $item) { echo "第".($rank+1)."名:{$item->name},总销量:{$item->total_sold}"; }
为什么之前的关联尝试失败?
你提到用hasOneThrough和belongsToMany没成功,大概率是这两个原因:
belongsToMany是用来处理多对多直接关联的,而你的场景是三层层级关联(Record→中间表→菜品),属于一对多+一对一的组合,完全不适用多对多。- 使用
hasOneThrough时可能参数顺序搞错了,这个方法的参数顺序非常关键,必须严格对应你的表字段,我上面的代码已经帮你把参数对应正确了。
内容的提问来源于stack exchange,提问作者Hmorv
相关产品推荐
相关产品推荐

