3

How to order laravel eloquent query using parent model? I mean I have an eloquent query where I want to order the query by its parent without using join relationship? I used whereHas and order by on it, but did not work.

Here is a sample of my code:

$query = Post::whereHas('users')->orderBy('users.created_at')->get();
Ahmad Reza Azimi
  • 534
  • 5
  • 16
  • afaik the `whereHas` adds a `where exists` clause in the where without using joins. you could replace the `->get()` to `->toSql()` to see the generated sql query. alternatively, you could use the [`DB` logging function](https://stackoverflow.com/questions/27753868/how-to-get-the-query-executed-in-laravel-5-dbgetquerylog-returning-empty-ar). – Bagus Tesa Jun 18 '22 at 06:47

4 Answers4

2

If you want to order Post by a column in user you have to do a join in some way unless you sort after you retrieve the result so either:

$query = Post::select('posts.*')
             ->join('users', 'users.id', 'posts.user_id')
             ->orderBy('users.created_at')->get();

Note that whereHas is not needed anymore because the join (which is an inner join by default) will only result in posts that have a user.

Alternatively you can do:

$query = Post::has('users')
    ->with('users')
    ->get()
    ->sortBy(function ($post) { return $post->users->created_at; });

The reason is that eloquent relationships are queried in a separate query from the one that gets the parent model so you can't use relationship columns during that query.

apokryfos
  • 38,771
  • 9
  • 70
  • 114
1

You can simply orderBy in your Post model.

public function users(){
    return $this->belongsTo(User::class, "user_id")->orderByDesc('created_at');
}

I hope this helps you.

1

I have no clue why you wanted to order Posts based on their User's created_at field. Perhaps, a different angle to the problem is needed - like accessing the Post from User instead.

That being said, an orderBy() can accept a closure as parameter which will create a subquery then, you can pair it with whereRaw() to somewhat circumvent Eloquent and QueryBuilder limitation*.

Post::orderBy(function($q) {
    return $q->from('users')
      ->whereRaw('`users`.id = `posts`.id')
      ->select('created_at');
})
->get();

It should generate the following query:

select *
  from `posts`
  order by (
            select `created_at`
            from `users`
            where `users`.id = `posts`.id
           ) asc

A join might serve you better, but there are many ways to build queries.

*As far as I know, the subquery can't be made to be aware of the parent query fields

Bagus Tesa
  • 1,317
  • 2
  • 19
  • 42
0

You can try

Post::query()
  ->has('users')
  ->orderBy(
    User::select('created_at')
        ->whereColumn('id', 'posts.user_id')
        ->orderBy('created_at')
  )
  ->get();

The sql generated would be like

select * from `posts` 
    where exists (select * from `users` where `posts`.`user_id` = `users`.`id`) 
    order by (select `created_at` from `users` where `id` = `posts`.`user_id` order by `created_at` asc) asc

But I guess join would be a simpler approach for this use case.

Laravel Docs - Eloquent - Subquery Ordering

Donkarnash
  • 12,433
  • 5
  • 26
  • 37