Retrieve entries from table via pivot table Octobercms - php

can anyone help me writing query to retrieve words from my words table in such way that words are having a belongsToMany relationship to Type model via types pivot table?
Here's how relationship looks like in Word.php
public $belongsToMany = [
'typeswb' => [
'Yeoman\Wordbank\Models\Type',
'table' => 'yeoman_wordbank_types_pivotb',
'order' => 'typesofwordbank'
]
];
Here is how types table looks like
mysql> select * from yeoman_wordbank_types;
+----+--------------------+--------------------+
| id | typesofwordbank | slugoftypes |
+----+--------------------+--------------------+
| 1 | Common Words | common-words |
| 2 | Common Expressions | common-expressions |
| 3 | Common Proverbs | common-proverbs |
| 4 | Common Idioms | common-idioms |
+----+--------------------+--------------------+
4 rows in set (0.00 sec)
and wheres how types pivot table looks like
mysql> select * from yeoman_wordbank_types_pivotb;
+---------+---------+
| word_id | type_id |
+---------+---------+
| 18 | 2 |
| 5 | 4 |
| 9 | 3 |
| 19 | 1 |
| 31 | 1 |
+---------+---------+
5 rows in set (0.00 sec)
As you can see type_id are connected to words_id. where types_id's are from types table and words_id's are from word table.
I need to find a way to get the words using types_id.
I have tried following
// $query = Word::all();
// foreach ($query->typeswb as $typeswb) {
// $queryy = $typeswb->pivot->type_id = 1;
// }
and some other combination like that but all in vain, strangely I get Word::find(1) null on this while Word::all() return an array of 6 items in the collection.
Thank you for reading, I will appreciate any hint or help very much.

You can provide pivot table:
public $belongsToMany = [
'typeswb' => [
'Yeoman\Wordbank\Models\Type',
'table'=>'yeoman_wordbank_types_pivotb',
'key' =>'word_id ',
'otherKey' => 'type_id ',
'pivot' => ['word_id ', 'type_id '],
],
];

ok so i solved this, normally via builder component, this functionality is very easy to achieve but was kinda of unclear how to do on my own in one of my own component. So, here's how i solved this:
$query = Word::whereHas('typeswb', function($q)
{
$q->where('type_id', '=', post('typesofwordsearch')); //post('typesofwordsearch') return integer which are id's of types according to the table
})->limit(5)->get();
Breakdown about how it is working:
So, there could be another way but after trying long, this one worked.
Firstly i am using my Word model where in the word model, i have typeswb which is defining belongToMany relation (see question for that). I am using whereHas read this for more info on that. Basically using wherehas i am instructing query to look up with relation. Afterwards, In function closure, I'm queries using one of key of my types table i.e type_id. For keys of types table again see my question above.
Hope that this will help someone. Cheers!

Related

How to sum a colum from a related model efficiently on Laravel

Ok I got this table
affiliates_referral_clicks
id | affiliate_id | clicks | date
1 | 1 | 10 | 2021-07-14
2 | 1 | 2 | 2021-07-11
3 | 2 | 1 | 2021-07-11
4 | 2 | 14 | 2021-07-10
...
Of course my Model Affiliate has a relationship with referralClicks
Affiliate.php
public function referralClicks(){
return $this->hasMany(AffiliateReferralClick::class,'affiliate_id');
}
Now I want to bring all Affiliates with the SUM of all their clicks that have a date between a given date. I implemented it like this
$affiliate = Affiliate::with(['referralClicks' => function($query) use($params) {
$query->whereDate('date','>=', $params['dateFrom'])
->whereDate('date','<=', $params['dateTo'])
->select('clicks')
;
}])->get();
foreach ($affiliates as $affiliate){
$affiliate->totalClicks = $affiliate->referralClicks->sum('clicks');
}
this works fine, but since the affiliates_referral_clicks table is waaaay too big and the request ends up being too slow, I think if you do the query without using Eloquent's helpers you can get a much faster query.
So my question would be...how can I do the same I just did but with raw querys (or whatever the most efficient way is)? Im using a MySQL DB I hope you guys can help me!
Haven't tried that yet but that's how I'd solve this (if we assume, you only need the sum and nothing else from the relationship):
$affiliate = Affiliate::withSum(['referralClicks.clicks as totalClicks' => function($query) use($params) {
$query->whereDate('date','>=', $params['dateFrom'])
->whereDate('date','<=', $params['dateTo'])
->select('clicks')
;
}])->get();

Laravel Query Optimization (SQL)

My user_level database structure is
| user_id | level |
| 3 | F |
| 4 | 13 |
| 21 | 2 |
| 24 | 2 |
| 33 | 3 |
| 34 | 12+ |
I have another table users
| id | school_id |
| 3 | 3 |
| 4 | 4 |
| 21 | 2 |
| 24 | 2 |
| 33 | 3 |
| 34 | 1 |
What I have to achieve is that, I will have to update the level of each user based on a certain predefined condition. However, my users table is really huge with thousands of records.
At one instance, I only update the user_level records for a particular school. Say for school_id = 3, I fetch all the users and their associated levels, and then increase the value of level by 1 for those users (F becomes 1, 12+ is deleted, and all other numbers are increased by 1).
When I use a loop to loop through the users, match their user_id and then update the record, it will be thousands of queries. That is slowing down the entire application as well as causing it to crash.
One ideal thing would be laravel transactions, but I have doubts if it optimises the time. I tested it in a simple query with around 6000 records, and it was working fine. But for some reason, it doesnt work that good with the records that I have.
Just looking some recommendation on any other query optimization techniques.
UPDATE
I implemented a solution, where I would group all the records based on the level (using laravel collections), and then I would only have to issue 13 update queries as compared to hundreds/thousands now.
$students = Users::where('school_id', 21)->get();
$groupedStudents = $students->groupBy('level');
foreach ($groupedStudents as $key => $value) :
$studentIDs = $value->pluck('id');
// condition to check and get the new value to update
// i have used switch cases to identify what the next level should be ($NexLevel)
UserLevel::whereIn('userId', $studentIDs)->update(["level" => $nextLevel]);
endforeach;
I am still looking for other possible options.
First defined a relationship in your model, like:
In UserLevel model:
public function user() {
return $this->belongsTo(\App\UserLevel::class);
}
And you can just update the level without 12+ level's query, only by one query, and delete all 12+ level by one query.
UserLevel::where('level', '<=', 12)->whereHas('user', function($user) {
$user->where('school_id', 3);
})->update(['level' => DB::raw("IF(level = 'F', 1, level+1)")]);
UserLevel::whereHas('user', function($user) {
$user->where('school_id', 3);
})->where('level', '>', 12)->delete();
If your datas is too huge. you can also use chunk to split them for reduce memory consumption.
like this:
UserLevel::where('level', '<=', 12)->whereHas('user', function($user) {
$user->where('school_id', 3);
})->chunk(5000, function($user_levels) {
$user_levels->update(['level' => DB::raw("IF(level = 'F', 1, level+1)")]);
});
UserLevel::whereHas('user', function($user) {
$user->where('school_id', 3);
})->where('level', '>', 12)->delete();

Laravel Eloquent GroupBy Name to Create array of ID's

I have a table in my Laravel application which I wish to query.
id | company_name | contact |
-----------------------------
1 | Tesco | Name 1 |
2 | Tesco | Name 2 |
3 | Asda | Name 3 |
4 | Tesco | Name 4 |
5 | Asda | Name 5 |
I'm trying to get an array of all unique company names with all ID numbers.
'Tesco' => [1,2,4]
'Asda' => [3,5]
I have tried
$companies = Contact::select('company_name','id')->groupBy('company_name')->get();
However this requests the 'id' to be included in the group by which defeats the purpose. I understand it's asking for this because it's not a SUM or COUNT etc.
The above table may seem unusual and I know I should have a relation to a companies table however this is necessary at this stage.
You could use GROUP_CONCAT()
$companies = Contact::select('company_name', DB::raw('GROUP_CONCAT(id) as ids'))
->groupBy('company_name')
->get();
This would return something like:
company_name | ids
Tesco | 1,2
Edit: if you want the ids in the form an array, you could just map over the collection to convert it:
$companies->map(function($column) {
$column->ids = explode(',', $column->ids);
});
That should do the trick.

product filters with AND condition in eloquent laravel

I am working around with pivot table in Laravel Eloquent which is great but I am stuck in a point where I am unable to find the solution.
I have three tables shown below
## plant ##
id | name
1 | plant_1
2 | plant_2
3 | plant_3
seasons
id | season_name
1 | Summer
2 | Winter
3 | Mid-Summer
plant_to_season
id | plant_id | season_id
1 | 1 | 1
2 | 2 | 2
3 | 3 | 2
4 | 3 | 3
Now if I apply filters like
$filters = array(1,2,3);
\App\Plant::with(['seasons' => function($query) use ($filters) {
return $query->wherein('seasons.id', $filters);
}])->groupBy('id')->get();
This code returns me all three plants. But what I actually want is those plants which exactly have these 3 given filters.
I have searched everywhere from laravel documentation to Stackoverflow but there is no help regarding this.
This seems more tricky than expected, You should def use the whereHas method instead of with and then loop through your filters: Here is my suggestion on how to solve this, it works, but maybe there is a shorter solution:
$filters = array(1,2,3);
$outquery = \App\Plant::where('id','>',1);
foreach( $filters as $filter){
$outquery = $outquery->whereHas('seasons', function($query) use ($filter){
$query->where('id','=',$filter);
});
}
return $outquery->get();

Using nested queries and many to many relationships in Doctrine's QueryBuilder

So I'm having a bit of trouble thinking of how to approach this using a query builder. Currently, I have three objects that are the following:
HelpRequest
id
...
status
Filter
id
name
statuses -> ManyToMany(targetEntity="Status")
Status
id
name
A filter can have multiple statuses so there is a table that is keeping track what statuses are part of a specific filter.
Sample Data
help_requests
---
| id | content | status |
| 1 | hello | 3 |
filters
---
| id | name |
| 1 | Active |
| 1 | Inactive |
statuses
---
| id | name |
| 1 | Open |
| 2 | Closed |
| 3 | Waiting User Response |
status_filter
---
| status_id | filter_id |
| 1 | 1 |
| 3 | 1 |
| 2 | 2 |
The status_filter table is automatically generated from a ManyToMany relationship in doctrine between a Status object and a Filter object.
Based on the given information, I've written this SQL query but now I'm having troubles writing this with QueryBuilder.
SELECT * FROM help_requests WHERE status IN (SELECT status_id FROM status_filter WHERE filter_id = 1)
If there's any more information I can give, let me know. I've read multiple questions on SO and have tried a number of things but I can't seem to get it right. I'm aware I could just hard coded that query but I'd like the experience using QueryBuilder
Thanks for the help!
Update
In the end, since I couldn't get it to work with QueryBuilder and I didn't want to create a new entity solely to map two other entities together, I decided to use createQuery() instead and this is what I came up with:
SELECT
hr
FROM
HelpRequest hr
WHERE
hr.status
IN (
SELECT
s.id
FROM
Filter f
JOIN
f.statuses s
WHERE
f.name = :name
)
Thank you everyone for the help.
Try this query, and put is in your HelpRequestsRepository class:
$subquery = $this->->select('st.status_id')
->from('/path/to/StatusFilter', 'st')
->where('st.filter_id = 1');
$query = $this->createQueryBuilder('hr')
->select('*')
->where('hr.status IN (' . $subquery->getDQL() . ')')
->getQuery();
Try this approach in the HelpRequestsRepository class:
$qb = $this->createQueryBuilder('hr');
$qb->select("hr");
$qb->join("::Status","s",Expr\Join::INNER_JOIN, "hr.status=s" );
$qb->join("::Filter","f",Expr\Join::INNER_JOIN, "s.filters=f" );
$qb->where("f.name = :name");
$qb->setParameter('name', $nameOfTheFilterToBeFound)
Hope this help

Categories