如何在Laravel的Eloquent ORM中按透视表数据排序

33

我的数据库中有:

  • tops
  • posts
  • tops_has_posts表.

当我从tops表中检索一个顶部时,我也会检索与该顶部相关的posts。 但是如果我想按照特定顺序检索这些帖子呢? 因此,我在我的枢轴表tops_has_posts中添加了一个range字段,并尝试使用Eloquent通过结果进行排序,但不起作用。

我尝试过以下代码:

$top->articles()->whereHas('articles', function($q) {
    $q->orderBy('range', 'ASC');
})->get()->toArray();

还有这个:

$top->articles()->orderBy('range', 'ASC')->get()->toArray();

两者都是拼命的尝试。

提前感谢。

7个回答

44

有两种方法 - 一种是指定table.field,另一种是使用Eloquent别名pivot_field,如果您使用withPivot('field')

有两种方式 - 一种是通过指定 table.field,另一种是使用 Eloquent 别名pivot_field(如果您使用了withPivot('field'))。
// if you use withPivot
public function articles()
{
  return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range');
}

// then: (with not whereHas)
$top = Top::with(['articles' => function ($q) {
  $q->orderBy('pivot_range', 'asc');
}])->first(); // or get() or whatever
这将起作用,因为Eloquent会将withPivot中提供的所有字段别名为pivot_field_name。现在,让我们来看看通用方案:
$top = Top::with(['articles' => function ($q) {
  $q->orderBy('tops_has_posts.range', 'asc');
}])->first(); // or get() or whatever

// or:
$top = Top::first();
$articles = $top->articles()->orderBy('tops_has_posts.range', 'asc')->get();

这将对相关查询进行排序。

注意:不要因为以这种方式命名而让生活变得艰难。 posts 不一定是articles,我会使用其中一个名称,除非确实需要这样做。


33

2
谢谢提示,不幸的是它还没有在文档中... - wittich
2
你真是个冠军!L9文档中仍然没有这个内容:https://github.com/laravel/docs/search?q=orderByPivot - emotality
为什么这不在文档中?目前它还存在漏洞吗?这已经发布将近2年了。 - Shulz
@Shulz的工作非常出色,我不知道为什么它还没有被记录下来。 - Jakub Adamec

26
在 Laravel 5.6+ 中(关于旧版本不确定),使用以下方法很方便:
public function articles()
{
  return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range')->orderBy('tops_has_posts.range');
}

在这种情况下,每当您调用 articles 时,它们将自动按照 range 属性进行排序。


19
在 Laravel 5.4 中,我有以下关系,在 Set 模型中正常运作,该模型包含 Job 模型的 belongsToMany 关系。
public function jobs()
{
    return $this->belongsToMany(Job::class, 'eqtype_jobs')
        ->withPivot(['created_at','updated_at','id'])
        ->orderBy('pivot_created_at','desc');
}

上述关系返回由指定的Set已加入的所有jobs,按枢轴表(eqtype_jobs) 字段 created_at 降序排序。

$set->jobs()->paginate(20) 的SQL打印输出如下:

select
  `jobs`.*, `eqtype_jobs`.`set_id` as `pivot_set_id`,
  `eqtype_jobs`.`job_id` as `pivot_job_id`,
  `eqtype_jobs`.`created_at` as `pivot_created_at`,
  `eqtype_jobs`.`updated_at` as `pivot_updated_at`,
  `eqtype_jobs`.`id` as `pivot_id`
from `jobs`
inner join `eqtype_jobs` on `jobs`.`id` = `eqtype_jobs`.`job_id`
where `eqtype_jobs`.`set_id` = 56
order by `pivot_created_at` desc
limit 20
offset 0

2
这个 ->orderBy('pivot_created_at','desc'); 对我很有用。非常感谢。 - Prateek

4
在你的刀片上尝试这个:
$top->articles()->orderBy('pivot_range','asc')->get();

4

如果你打印出 belongsToMany 关联的 SQL 查询语句,你会发现中间表的列名使用了 pivot_ 前缀作为新别名。

例如,在中间表中,created_atupdated_at 字段的别名分别为 pivot_created_atpivot_updated_at。因此,orderBy 方法应该使用这些别名。

以下是如何实现的示例。

class User {
    ...
    public function posts(): BelongsToMany {
        return $this->belongsToMany(
                   Post::class, 
                   'post_user', 
                   'user_id', 
                   'post_id')
               ->withTimestamps()
               ->latest('pivot_created_at');
    }
    ...
}

如果您喜欢,可以使用orderBy代替latest方法。在上面的例子中,post_user是一个数据透视表,您可以看到用于排序的列名称现在是pivot_created_atpivot_updated_at


0

你可以使用这个:

    public function keywords() {
    return $this->morphToMany(\App\Models\Keyword::class, "keywordable")->withPivot('order');
}
public function getKeywordOrderAttribute() {
    return $this->keywords()->first()->pivot->order;
}

在获取并使用sortby后,将关键字属性附加到模型中

$courses->get()->append('keyword_order')->sortBy('keyword_order');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接