Query on Eloquent based on relationship - laravel

I have 2 models, Transaction, TrackingNumber.
The relationships and fields are:
Transaction: hasMany TrackingNumber
Transaction fields: id | carrier
TrackingNumber fields: id | transaction_id
I am trying to query TrackingNumbers but I want to return only where the carrier is ups. In this sense:
a) If I use eager loading, it will return all the tracking numbers regardless of shipper and only return transaction relationship based on the where:
$collection = TrackingNumber::with(['transaction' => function($q) {
$q->where('carrier', 'ups')
}])->get();
So this is not what I want. What I want is that the $collection has only the TrackingNumbers with given carrier instead.
b) If I do something like this, the result is what I want, but it is very inefficient (as it makes 2 queries and stores data in array; also if the array is too big, it fails on whereIn.)
$transactionIds = Transaction::where('carrier', 'ups')->pluck('id');
$trackingNumbers = TrackingNumber::whereIn('id', $transactionIds)->get();
// The result is what I want but it is inefficient.
c) I also tried something like this but it doesn't care about the where :/
\DB::table('tracking_numbers')
->join('transactions', 'tracking_numbers.transaction_id', '=', 'transactions.id')
->where('transactions.carrier', 'ups')
->get();
d) I don't want to use whereHas() because it gets extremely slow with a lot of data (I have +1m rows in my tables)
What is the Laravel way to achieve this?

Using whereHas should get you the results you want:
TrackingNumber::whereHas('transaction', function ($query) {
$query->where('carrier', 'ups');
})->get();
Or, try restructuring the query from c like:
\DB::table('tracking_numbers')
->join('transactions', function ($join) {
$join->on('tracking_numbers.transaction_id', '=', 'transactions.id')
->where('transactions.carrier', 'ups');
})
->get();

Related

How return all rows for groupBy

I use the query to get the models that the user has, but only 1 model is returned for each user. How to get all? If I set $count=3 I should receive 3 or more models in group, but only first row is returned
$items->where(/*.....*/)
->groupBy('user_id')
->havingRaw("COUNT(*) >= {$count}")->get()
UPDATE
I solved it. I created a separate function for preparing the query and used it 2 times.
I think this may be an incorrect solution, but it works
$items = Items::query();
$this->prepareQuery($request, $items)
$items->whereHas('user', function ($q) use ($count, $request){
$q->whereHas('items', function ($query) use ($request){
$this->prepareQuery($request, $query);
}, '>=', $count);
})
->paginate(4);
This may work.Sometimes take can help.
$items->where(/*.....*/)
->groupBy('user_id')
->take({{$count}});
If you have relationships set up you can quite simply call:
$model->models()
$model being the model you're wanting the list for.
models() being the name of the relationship between the two items.
For example, a post may have many comments. You can call $post->comments() to get a list of the posts comments.
You may also query this from the database directly with some eloquent magic.
Post::with('comments')->where(/*....*/)->get();
EDIT:
You can check to see if a model has X number of related models like so.
Post::has('comments', '>=', 3)->where(/*...*/)->get();

Laravel eloquent query with nested whereDate

I currently have a table called operators
The columns are:
id, user_id, item_clicked, created_at, updated_at
I can confirm on one of these it has today's update_at
2019-08-05 showing as today.
This is a relational table.
operators belongs to users.
I'm attempting to get operators of users whereDate is today.
My code is:
$ordersClicked = User::with('operators')
->whereDate('updated_at', \Carbon\Carbon::today())
->get();
This returns an empty array.
Testing this works so I know the data I need is there.
$ordersClicked = User::with('operators')
->get();
$operator = Operator::whereDate('updated_at', \Carbon\Carbon::today())->get();
Any idea what I'm doing wrong?
According to the docs https://carbon.nesbot.com/docs/ Carbon::today() returns Y-m-d H:i:s, if used like you are doing in a where clause and the database stores the value in Y-m-d format.
Why don't you try this:
$ordersClicked = User::with('operators')
->whereDate('updated_at', \Carbon\Carbon::today()->format('Y-m-d'))
->get();
if that doesn't work; then it is possible the eloquent is applying the where clause on updated_at on the User table, instead of Operator. Did you mean to do something like this:
$ordersClicked = User::with(['operators' => function($query) {
$query->where('updated_at', \Carbon\Carbon::today()->format('Y-m-d'))
}])->get();
i.e. filter on the eager loaded collection? if this is what you intended, what will happen is it will return all users and only some operators that has the updated_at matching the criteria will have values, where others will be null.
Wouldn't it be better to reverse the query? i.e. make sure Operator has a belongsTo relationship on User model and query the data like this:
$operator = Operator::whereDate('updated_at', \Carbon\Carbon::today())
->with('user')
->get();

Lean Eloquent Results ->with() Eager Loading

I have a query I am working on that feeds into a javascript engine where there is a lot of information returned that isn't used in the javascript. The results are over 1MB and some of that is because of some eager loading. Here is the query:
$customers = Customer::where('customers.office_id', $officeid)
->where("customers.parent_id", null)
->with('lastAppointment')
->with('nextAppointment')
->select("customers.id","customers.family_id", "customers.gender", "customers.family_size", "family_value")
->get();
The relationship of lastAppointment creates a returned nested object with all the columns from the appointments table, where I really only want a single column of start_at
If I do a ->leftJoin() I can limit my results using the final select like this:
->leftJoin(DB::raw("(select customer_id, MAX(start_at) as lastAppointment from appointments group by customer_id) as appt"), 'customers.id', '=', 'appt.customer_id')
->select("customers.id","customers.family_id", "customers.gender", "customers.family_size", "family_value", "appt.lastAppointment")
I am just wondering if there is a way of doing something similar using ->with()?
You can use this code
->with('lastAppointment:_relation_id,start_at')
where _relation_id is customer_id or primary key of lastAppointment correspond model: depends on your table relation. See docs part of Nested Eager Loading
https://laravel.com/docs/5.5/eloquent-relationships#eager-loading p
The with function will accept a callback as the array value of the relationship key. You then have access to the underlaying query builder instance, I think this is what you want:
->with(['lastAppointment' => function($query) {
return $query->latest()->select('customer_id', 'start_at')
->groupBy('customer_id');
}])

Laravel Eloquent - How Do You Add a Where Condition to a Distantly Related Model Using the ORM?

I currently have the following:
Cars::with('cases')->with(['parts', 'parts.engines', 'parts.engines.metals'])
->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');
The above will list all rows in my cars table along with the metal associated with the engine in each of those cars. The metals table is related to the cars table through the parts and then the engines tables.
I've tried using:
Cars::with('cases')->whereHas(['parts', 'parts.engines', 'parts.engines.metals'], function($query){
$query->where('weight', '=', 45)
})->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');
But this errors out since whereHas() does not accept an array for its first parameter and I don't see a way to link to distant relationships with it.
How do I apply a WHERE conditional on a column in the metals table using the built-in ORM?
whereHas() only needs the name of the relationship for which you'd like to add the conditions. So, if you're trying to add the condition to the metals, you just need to restrict the parts.engines.metals relationship.
On a side note, when you eager load nested relationships, you don't need to also specify to load the intermediate relationship. That is, when you eager load parts.engines, you don't need to also eager load parts.
So, your query would look something like:
Cars::with(['cases', 'parts.engines.metals'])
->whereHas('parts.engines.metals', function($query) {
$query->where('weight', '=', 45)
})
->orderBy('car_name', 'DESC')
->orderBy('id', 'DESC');
This query will only retrieve cars that have a related metal with a weight of 45. Additionally, for those cars that are retrieved, it will also eager load all of the cases, parts, engines, and metals related to those cars.
I think you mean this:
Cars::with(['cases', 'parts', 'parts.engines', 'parts.engines.metals' => function($query){
$query->where('weight', '=', 45);
}])->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');

Eloquent ORM, how to avoid redundant queries?

Suppose I have the field status in all my database tables. When I want to delete a record, instead of purging it I set the value of field status to 0. This means of course all my queries will always use a where clause such as:
WHERE status = 1
However, this means I have to write and append where('status', '=', 1) to all methods of my eloquent model. It will always be like:
Post::find(1).where('status', '=', 1)
Post::where('status', '=', 1)->get()
Post::find(1).where('status', '=', 1).comments().where('status', '=', 1)->get()
Is there a way to define something as a default scope to have where status = 1 always present in all methods of my model and all the time?
I appreciate any help!
This should do the trick, at least regarding soft delete:
http://four.laravel.com/docs/eloquent#soft-deleting
You could use the scope methods.
public function scopeActive($query)
{
$query->where('status', '=', 1);
}
Then, you use like this:
Post::active()->get();

Resources