在 Laravel 模型中加入子查询
Join subquery in Laravel model
请为这个问题建议一个更好的标题。我无法正确命名我的问题。
背景
我正在创建供个人使用的漫画数据库,以跟踪我的漫画阅读情况。每部漫画都属于一个系列。每部漫画都有发布日期。系列的发行日期是相应漫画的首次发行。为此,我有一个 eloquent 函数 seriesByDate()
:
class Series extends Model
{
public $timestamps = false;
protected $primaryKey = 'series_id';
protected $fillable = ['series_name', 'publisher_id'];
public function publisher()
{
return $this->belongsTo(Publisher::class, 'publisher_id');
}
public function comics()
{
return $this->hasMany(Comic::class, 'series_id', 'series_id');
}
// instead of saving the release date of a complete series
// we look for the first comic in this series and get the
// comic's release date.
public static function seriesByDate()
{
$firstRelease = DB::table('comics')
->select('series_id', DB::raw('MIN(comic_release_date) as first_release'))
->groupBy('series_id');
$seriesByDate = DB::table('series')
->leftJoinSub($firstRelease, 'first_release', function ($join) {
$join->on('series.series_id', '=', 'first_release.series_id');
})
->join('publishers', 'publishers.publisher_id', '=', 'series.publisher_id')
->select('series.series_id', 'series.series_name', 'first_release', 'publishers.publisher_name')
->get();
return $seriesByDate;
}
我想要什么
我希望 release_date
以某种方式永久保留在我的系列模型中。含义:当我执行 App\Series::all()
时,我已经希望将 release_date 作为返回数据中的一列。类似于 App\Series::with('publishers')->get()
使用上面的解决方案,我必须明确执行 App\Series::seriesByDate()
这可能吗?你能给我一个提示吗?
编辑/更新
@Musa 的链接视频显示了如何在模型中正确执行此操作:
您可以定义一个 accessor 然后附加值
class Series extends Model
{
protected $appends = ['series_date'];
public function getSeriesDateAttribute()
{
return self::seriesByDate();
//OR build 'seriesByDate' manually, returning whatever you like.
}
}
你不能。这没有什么魔力。您最终可能会编写自己的自定义 Relation,但这会不必要地复杂,只是为了拥有一个漂亮的 related
/accessor。这两种解决方案在性能方面都不是很好。
不知道你为什么选择这样的结构。没有任何进一步的 context/explanation,我强烈建议您也直接在 Series
模型中有一个 release_date
列。这将比您当前的结构快得多。
如果您仍想坚持使用该结构,我会亲自检索 release_date
"php side" 而不是 "database side" :
$series = App\Series::query()
->with([
'publishers',
'comics' => function ($query) {
$query->orderBy('created_at');
},
])
->get();
foreach ($series as $serie) {
$serieTitle = $serie->title;
$releaseDate = $serie->comics->first()->created_at;
echo $serieTitle.' was first released '.$releaseDate->diffForHumans().'<br/>';
}
(未测试)
唯一的缺点是它将 return 每个 "comics" 一个 "serie" 的集合。如果您没有每个系列 10k 的漫画并且您不加载每页 1k 系列,那应该没问题。无论如何,这看起来比 seriesByDate
方法更优雅 optimized/faster。
编辑:另外,我相信你应该在 Laracon 2018 上观看 Jonathan Reinink 的 "Advanced Querying With Eloquent"。他讨论了您需要的子查询。我 100% 确定您会找到最好和最优化的 Eloquent 子查询,可以为您想要实现的目标打造:https://vimeo.com/showcase/7060635/video/255049572
请为这个问题建议一个更好的标题。我无法正确命名我的问题。
背景
我正在创建供个人使用的漫画数据库,以跟踪我的漫画阅读情况。每部漫画都属于一个系列。每部漫画都有发布日期。系列的发行日期是相应漫画的首次发行。为此,我有一个 eloquent 函数 seriesByDate()
:
class Series extends Model
{
public $timestamps = false;
protected $primaryKey = 'series_id';
protected $fillable = ['series_name', 'publisher_id'];
public function publisher()
{
return $this->belongsTo(Publisher::class, 'publisher_id');
}
public function comics()
{
return $this->hasMany(Comic::class, 'series_id', 'series_id');
}
// instead of saving the release date of a complete series
// we look for the first comic in this series and get the
// comic's release date.
public static function seriesByDate()
{
$firstRelease = DB::table('comics')
->select('series_id', DB::raw('MIN(comic_release_date) as first_release'))
->groupBy('series_id');
$seriesByDate = DB::table('series')
->leftJoinSub($firstRelease, 'first_release', function ($join) {
$join->on('series.series_id', '=', 'first_release.series_id');
})
->join('publishers', 'publishers.publisher_id', '=', 'series.publisher_id')
->select('series.series_id', 'series.series_name', 'first_release', 'publishers.publisher_name')
->get();
return $seriesByDate;
}
我想要什么
我希望 release_date
以某种方式永久保留在我的系列模型中。含义:当我执行 App\Series::all()
时,我已经希望将 release_date 作为返回数据中的一列。类似于 App\Series::with('publishers')->get()
使用上面的解决方案,我必须明确执行 App\Series::seriesByDate()
这可能吗?你能给我一个提示吗?
编辑/更新
@Musa 的链接视频显示了如何在模型中正确执行此操作:
您可以定义一个 accessor 然后附加值
class Series extends Model
{
protected $appends = ['series_date'];
public function getSeriesDateAttribute()
{
return self::seriesByDate();
//OR build 'seriesByDate' manually, returning whatever you like.
}
}
你不能。这没有什么魔力。您最终可能会编写自己的自定义 Relation,但这会不必要地复杂,只是为了拥有一个漂亮的
related
/accessor。这两种解决方案在性能方面都不是很好。不知道你为什么选择这样的结构。没有任何进一步的 context/explanation,我强烈建议您也直接在
Series
模型中有一个release_date
列。这将比您当前的结构快得多。如果您仍想坚持使用该结构,我会亲自检索
release_date
"php side" 而不是 "database side" :
$series = App\Series::query()
->with([
'publishers',
'comics' => function ($query) {
$query->orderBy('created_at');
},
])
->get();
foreach ($series as $serie) {
$serieTitle = $serie->title;
$releaseDate = $serie->comics->first()->created_at;
echo $serieTitle.' was first released '.$releaseDate->diffForHumans().'<br/>';
}
(未测试)
唯一的缺点是它将 return 每个 "comics" 一个 "serie" 的集合。如果您没有每个系列 10k 的漫画并且您不加载每页 1k 系列,那应该没问题。无论如何,这看起来比 seriesByDate
方法更优雅 optimized/faster。
编辑:另外,我相信你应该在 Laracon 2018 上观看 Jonathan Reinink 的 "Advanced Querying With Eloquent"。他讨论了您需要的子查询。我 100% 确定您会找到最好和最优化的 Eloquent 子查询,可以为您想要实现的目标打造:https://vimeo.com/showcase/7060635/video/255049572