43

I have the following query:

$items = UserItems::with('item')
        ->where('user_id','=',$this->id)
        ->where('quantity','>',0)
        ->get();

I need to order it by item.type so I tried:

$items = UserItems::with('item')
        ->where('user_id','=',$this->id)
        ->where('quantity','>',0)
        ->orderBy('item.type')
        ->get();

but I get Unknown column 'item.type' in 'order clause'

What I am missing?

TheUnreal
  • 23,434
  • 46
  • 157
  • 277
  • 1
    Is your table named item or items? My guess is that your relationship is called item but the table is items so it should be `orderBy('items.type')` – rypskar Jul 08 '16 at 07:55
  • I tried both `Column not found: 1054 Unknown column 'items.type' in 'order clause'` – TheUnreal Jul 08 '16 at 07:58
  • Look at your query log, your main query and relationship are separate from each other, they don't cross over. You would need to do some type of join to be able to order by another table like that more than likely. – lagbox Jul 08 '16 at 08:04
  • 1
    It is probably because `with()` are using eager loading, which turns this into two queries. Try to use `join()` instead of `with()` – rypskar Jul 08 '16 at 08:05

9 Answers9

64

join() worked fine thanks to @rypskar comment

$items = UserItems
        ::where('user_id','=',$this->id)
        ->where('quantity','>',0)
        ->join('items', 'items.id', '=', 'user_items.item_id')
        ->orderBy('items.type')
        ->select('user_items.*') //see PS:
        ->get();

PS: To avoid the id attribute (or any shared name attribute between the two tables) to overlap and resulting in the wrong value, you should specify the select limit with select('user_items.*').

N69S
  • 16,110
  • 3
  • 22
  • 36
TheUnreal
  • 23,434
  • 46
  • 157
  • 277
21

Well, your eager loading is probably not building the query you're expecting, and you can check it by enabling the query log.

But I would probably just use a collection filter:

$items = UserItems::where('user_id','=',$this->id)
        ->where('quantity','>',0)
        ->get()
        ->sortBy(function($useritem, $key) {
          return $useritem->item->type;
        });
Repox
  • 15,015
  • 8
  • 54
  • 79
15

You can use the withAggregate('relationship', 'column') function.

It creates an 'item_type' attribute to the UserItems instances which you can then target with orderBy('item_type').
It can also be used with pagination like limit(), skip() etc. as it's not using sortBy() after the DB call.

UserItems::withAggregate('item','type')
    ->where('user_id','=',$this->id)
    ->where('quantity','>',0)
    ->orderBy('item_type')
    ->get();

This way you can still use your relationships in the eloquent query and order the collection without making joins or orderByRaw() calls.

Joy-Kevin
  • 13
  • 4
Rashid
  • 491
  • 1
  • 7
  • 13
6

I know it's an old question, but you can still use an "orderByRaw" without a join.

$items = UserItems
        ::where('user_id','=',$this->id)
        ->where('quantity','>',0)
        ->orderByRaw('(SELECT type FROM items WHERE items.id = user_items.item_id)')
        ->get();  
23zane
  • 61
  • 1
  • 1
2

For a one to many relationship, there is an easier way. Let's say an order has many payments and we want to sort orders by the latest payment date. Payments table has a field called order_id which is FK.

We can write it like below

$orders = Order->orderByDesc(Payment::select('payments.date')->whereColumn('payments.order_id', 'orders.id')->latest()->take(1))->get()

SQL Equivalent of this code:

select * from orders order by (
    select date
    from payments
    where order_id = payments.id
    order by date desc
    limit 1
) desc

You can adapt it according to your example. If I understood right, order's equivalent is user and payment's equivalent is item in your situation.

Further reading

https://reinink.ca/articles/ordering-database-queries-by-relationship-columns-in-laravel

Ahmet Firat Keler
  • 2,603
  • 2
  • 11
  • 22
1

I found another way of sorting a dataset using a field from a related model, you can get a function in the model that gets a unique relation to the related table(ex: table room related to room category, and the room is related to a category by category id, you can have a function like 'room_category' which returns the related category based on the category id of the Room Model) and after that the code will be the following:

Room::with('room_category')->all()->sortBy('room_category.name',SORT_REGULAR,false);

This will get you the rooms sorted by category name

I did this on a project where i had a DataTable with Server side processing and i had a case where it was required to sort by a field of a related entity, i did it like this and it works. More easier, more proper to MVC standards.

In your case it will be in a similar fashion:

User::with('item')->where('quantity','>',0)->get()->sortBy('item.type',SORT_REGULAR,false);
  • 6
    note that this will return everything as a collection then sort it which might be an issue if you have a large dataset – ctf0 May 25 '20 at 12:55
  • Also serverside pagination will not work as this approach sorts only loaded items (per page in case of pagination).. – Dirk Jan Apr 19 '23 at 09:42
1

you can simply do it by

UserItems::with('item')
  ->where('user_id','=',$this->id)
  ->where('quantity','>',0)
  ->orderBy(
      Item::select('type') 
      ->whereColumn('items.useritem_id','useritems.id')
      ->take(1),'desc'
    )    
  ->get();
0
$users
->whereRole($role)       
->join('address', 'users.id', '=', 'address.user_id')  
->orderByRaw("address.email $sortType")    
->select('users.*')
  • 1
    Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, **can you [edit] your answer to include an explanation of what you're doing** and why you believe it is the best approach? That's especially important here as there are several established answers that have been validated by the community. – Jeremy Caney Aug 19 '22 at 00:15
0
$items = UserItems::with('item')
        ->where('user_id','=',$this->id)
        ->where('quantity','>',0)
        ->with(['item' => function($q) {
          $q->orderBy('type', 'asc');
        ]})
        ->get();

Pass an array of relationships to the 'with' method where:

  • the array key is a relationship name, in this case is 'item'
  • the array value is a closure that adds additional constraints to the eager loading query