Laravel: Query the models where the relationship is greater than 0 - laravel

I'm doing a Laravel query to query a list of elements that his relationship is grater than 0.
The table concerts:
| ID | NAME |
|----|-----------|
| 1 | Concert A |
| 2 | Concert B |
| 3 | Concert C |
And the Position table.
| ID | concert_id | user_id | Content |
|----|----------------|------------|----------|
| 1 | 1 | 1 | xxx |
| 2 | 1 | 2 | yyy |
| 3 | 3 | 1 | zzz |
| 4 | 3 | 2 | www |
| 5 | 1 | 3 | xyx |
| 6 | 3 | 3 | rer |
The query that I need to do is, get the concerts where their position has in the content a value like $some_query$.
The code I've is the following:
$result = $this->model->whereHas('positions')
->with(['positions' => function ($query) {
$query->where('content', 'like', "%{$content}%");
}])->get();
But as far as I can tell, this will bring all the concerts, and also this is going to bring only the positions that has the desired content.
So I've two problems, the first one is I need to get the concerts that his queried positions are greather than 0.
And the second one is that also I need to bring all the positions, not only the queried ones.
Basically the query is just a way to know which concerts I need to bring.
Is there possible to achieve this on a single query?

You'll have to move your query to the whereHas.
If I understood well, this is what you want:
$result = $this->model
// Take the Concerts that has positions with a similar content
->whereHas('positions', function ($query) use ($content) {
$query->where('content', 'like', "%{$content}%");
})
// Take (all) the Positions of these concerts
->with('positions')
->get();

Related

Laravel | Return rows in order based off pivot

I have the following setup:
stages:
| id | name | order |
commands:
| id | name | body |
------------------------------=
| 1 | test | phpunit |
| 2 | style | echo "style" |
| 3 | deploy | deploy |
command_stage:
| command_id | stage_id | order |
---------------------------------
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 1 | 2 |
Basically, I would like to create a method on the stage model which allows me to get all of the commands back based off of the order, but commands have a specific order and so do the stages.
So each command is stored under a stage so we know which part to run but each command also has an order in that stage. Now I know I can do something like the following:
$inOrder = collect();
Stage::get()->each(function ($stage) {
$commands = $stage->commands()->orderByPivot('order')->get();
$inOrder->push($commands);
});
return $inOrder;
But I was wondering if there is a nicer way to do this? Or even a way to do this solely on one database hit?
Pre-load and sort the relationship:
$stages = Stage::with(['commands' => function ($subQuery) {
$subQuery->orderBy('command_stage', 'order');
}])->get();
foreach($stages as $stage) {
$inOrder->push($stage->commands);
}
This method of Eager-Loading will reduce the N+1 query issue of doing $stage->commands() inside the foreach() loop.

Laravel - Eloquent filter where Left Join does not exist

I am trying to figure out how to make this query work. These two tables do not have a direct relation (i.e. hasOne, hasMany, etc). I am looking to only get back records from client_vendor_relationship that do NOT have a collection_opt_in. Since I don't have a way of doing ->whereDoesntHave(), I am not sure how to get this data back.
client_vendor_relationship
| id | client_id | vendor_id | active |
|----|-----------|-----------|--------|
| 1 | 23484 | 1872 | 1 |
| 2 | 5643 | 345 | 1 |
| 3 | 431 | 4443 | 1 |
collection_opt_in
| id | client_id | vendor_id | year |
|----|-----------|-----------|------|
| 1 | 23484 | 23484 | 2020 |
| 2 | 23484 | 23484 | 2019 |
| 3 | 431 | 4443 | 2019 |
Current Query
$relationships = ClietVendorRelationship::where('active', 1)
->leftJoin('collection_opt_in', function($join){
$join->on('client_vendor_relationship.client_id', '=', 'collection_opt_in.client_id')
->on('client_vendor_relationship.vendor_id', '=', 'collection_opt_in.vendor_id');
})
->where() // Not sure what to put here to ONLY get rows back that the left join didnt find entries for
->groupBy('client_vendor_relationship.id')
->get();
The end goal of this above query would to be to only get the row back from client_vendor_relationship with the id of 2. I need to do this in Eloquent. I know I could easily just do a collection filter but the front end table I am using requires an eloquent query to be returned.
why using left join? while there is another straightforward ways?
you can use 'whereNotExists':
$relationships = ClietVendorRelationship::where('active', 1)
->whereNotExists(function ( $query){
$query->select('collection_opt_in.id')->from('collection_opt_in')->
whereColumn('collection_opt_in.client_id', '=', 'client_vendor_relationship.client_id')->
whereColumn('collection_opt_in.vendor_id', '=', 'client_vendor_relationship.vendor_id');
})
->groupBy('client_vendor_relationship.id')
->get();
also i must say i don't recommend using group by without aggregation

Laravel - Get Data from Table where clause in other table

I have this following 2 tables:
Table: Products
+----+---------+-------------+
| id | fn_id | created_at |
+----+---------+-------------+
| 1 | 4 | SOME TIME |
| 2 | 5 | SOME TIME |
| 3 | 6 | SOME TIME |
| 4 | 10 | SOME TIME |
| 5 | 10 | SOME TIME |
+----+---------+-------------+
Table Fn
+----+---------+-------------+
| id | fn_id | created_at |
+----+---------+-------------+
| 1 | 10 | SOME TIME |
| 2 | 11 | SOME TIME |
| 3 | 12 | SOME TIME |
| 4 | 14 | SOME TIME |
+----+---------+-------------+
And a User Input which is giving me a timestamp ($user_timestamp).
Now I need to get all produtcs, where
products.fn_id is 10
fn.fn_id is 10
fn.created == $user_timestamp
The products model has this relation:
public function fn() {
return $this->hasMany('App\FN', 'fn_id', 'fn_id');
}
Now I've tried multiple things like a where query where I want to check if the fn_id on both are "10" and the created_at value of the fn table is equal to $user_timestamp.
However, I wasn't able to do it.
$products = Products::with('fn')->where('fn.fn_id', function ($query) use ($user_timestamp) {
$query->where([['fn_id', 10],['created_at', $user_timestamp]]);
})->get();
You would have to use whereHas to constrain Products based on fn.
$products = Products::with('fn')->whereHas('fn', function($q) use($user_timestamp){
$q->where('fn_id', 10)->where('created_at', $user_timestamp);
})->get();
try this
$id_to_search=10;
$products = Products::with('fn')->where('fbn_id',$id_to_search)->whereHas('fn', function($q) use($user_timestamp,$id_to_search){
$q->where('fn_id', $id_to_search)->where('created_at', $user_timestamp);
})->get();

Laravel Eloquent GroupBy statement

I have this table and i'm trying to achieve a multiple groupBy and distinct through Laravel with a sum of duration. Basically, i need to find in table multiple ids passed from controller and then if windows, method and type are the same, sum the duration value.
Here is table structure
+-----+---------+--------+----------+------+
| ids | windows | method | duration | type |
+-----+---------+--------+----------+------+
| 1 | 2 | 3 | 5 | 2 |
+-----+---------+--------+----------+------+
| 2 | 2 | 3 | 5 | 2 |
+-----+---------+--------+----------+------+
| 3 | 2 | 3 | 5 | 2 |
+-----+---------+--------+----------+------+
and here is the statement that i try to use:
$tickets_g = \App\Models\Ticked::whereIn('id', $tickets)
->select('categoria', 'tipologia', 'priority', 'durata')
->distinct()
->GroupBy('categoria')
->GroupBy('tipologia')
->GroupBy('priority')
->sum('durata')
->get();
Can someone help me to figure out?
Fixed myself this is the correct query if someone stuck in the same issue:
$tickets_g = \App\Models\Ticked::select('support_ticked.*', DB::raw("SUM(support_ticked.durata) as durata_totale"))
->groupBy('support_ticked.tipologia','support_ticked.priority', 'support_ticked.categoria')
->get();

Laravel/Eloquent: Constrain nested eager load so as not to include empty parents

I have a relatively simple DB structure including countries, regions and depots. Each depot is assigned to an operator and a region:
operators
+----+------------+
| ID | name |
+----+------------+
| 1 | Operator 1 |
| 2 | Operator 2 |
+----+------------+
countries
+----+----------------+------+
| ID | country_id | code |
+----+----------------+------+
| 1 | United Kingdom | gb |
| 2 | France | fr |
+----+----------------+------+
regions
+----+-----------------+-------+
| ID | country_id (FK) | name |
+----+-----------------+-------+
| 1 | 1 | North |
| 2 | 1 | South |
| 3 | 1 | East |
| 4 | 1 | West |
| 5 | 2 | North |
| 6 | 2 | South |
| 7 | 2 | East |
| 8 | 2 | West |
+----+-----------------+-------+
depots
+----+----------------+------------------+-----------+
| ID | region_id (FK) | operator_id (FK) | name |
+----+----------------+------------------+-----------+
| 1 | 1 | 1 | Newcastle |
| 2 | 8 | 2 | Nantes |
+----+----------------+------------------+-----------+
I have set up their eloquent relationships successfully in the respective models.
I want to load each depot grouped into their respective regions and countries, and filtered by a specific operator.
$depots = Country::with('regions.depots')->whereHas('regions.depots', function($query) use ($opID) {
$query->where('operator_id',$opID);
})->get();
This does the trick, however as well as eager loading the depots, it's also eager loading all regions, including those without depots assigned to them. E.G. when the above is performed when $opID = 1, you get this result:
name: United Kingdom,
regions: [
{
name: North,
depots: [{
name: Newcastle
}]
}, {
name: South,
depots: []
}, {
name: East,
depots: []
}, {
name: West,
depots: []
}
]
What I would like is the above returned, but without the regions where there are no depots.
I have played around a lot with the constraints of both with and whereHas but cannot get the desired data structure. Why doesn't the below code have the desired effect?
$depots = Country::with(['regions.depots' => function($query) use ($opID) {
$query->where('depots.operator_id',$opID);
}])->get();
Is there any way at all of not eagerly loading the parent if the child doesn't exist? Or is it a case of performing the above query as I have it and then looping through the result manually?
EDIT
So after a few more hours I finally found a way to get my desired outcome. But it seems really dirty. Is this really the best way?
$depots = Country::whereHas('regions.depots', function($q) use ($opID) {
$q->where('operator_id',$opID);
})->with(['regions' => function($q) use ($opID) {
$q->with('depots')->whereHas('depots', function($q2) use ($opID) {
$q2->where('operator_id',$opID);
});
}])->get();
EDIT 2
So it turns out the first edit was actually querying the operator_id on everything but the depots table, which meant as soon as I added another depot owned by another operator in the same region, that showed up when I didn't want it to. The below seems even messier but does work. It's fun having conversations with myself ;) Hopefully it helps someone one day...
$depots = Country::has('regions.depots')
->with(['regions' => function($q) use ($opID) {
$q->with(['depots' => function($q2) use ($opID) {
$q2->where('operator_id',$opID);
}])->has('depots');
}])->get();
You can always use lazy loading:
$depots = Country::whereHas('regions.depots', function($q) use ($opID) {
$q->where('operator_id',$opID);
})->get()->load('regions.depots');

Resources