Laravel eloquent/collection select only matching records for all requested users - laravel

I have a polymorphic "Relationships" many to many relation that looks like that:
users
id | first_name | last_name
groups
id | group_name
regions
id | region_name
relationships
id | user_id | relationship_id | relationship_type | relationship_level
4 | 13 | 25 | App\Group | Group Manager
5 | 20 | 18 | App\Region | Participant
And the relationship looks like that:
public function regions()
{
return $this->morphedByMany('App\Region', 'relationship')->withPivot('relationship_level')->withTimestamps();
}
public function groups()
{
return $this->morphedByMany('App\Group', 'relationship')->withPivot('relationship_level')->withTimestamps();
}
And vice versa.
When I have an array of IDs, I want to select the relationships that exactly match between the users, this means that all columns (except user_id) match.
And they should only be rows that match among all users. If only 10 out of 12 selected users have the same relationship it should not be included.
If I select 10 users I need to select a pivot record that all those 10 users have (if any).
It doesn't matter if done in eloquent or via the collection after fetching from Database.

Related

How to find only last row from second table, along with its first table row

I have two tables.
journals and volumes.
Journal Table has unique rows, and volumes table have rows based on journal table id, name is journal_id(may be multiple).
journals table is:
id | journal_name
1 | journal1
2 | journal2
volumes table is:
id | journal_id | volume_name
1 | 1 | volume1
2 | 1 | volume2
3 | 1 | volume3
4 | 2 | volume4
5 | 2 | volume5
Now I need join with row from journal table and only last rows of volumes based on journal_id.
Result should be:
id | journal_name | journal_id | volume_name
1 | journal1 | 1 | volume3
2 | journal2 | 2 | volume5
Not all the rows from volumes table. (Need only last rows from each group of journal_id).
required result from mysql query is:
SELECT J.journal_name,V.id,V.journal_id FROM journals AS J
INNER JOIN (SELECT *
FROM volumes
WHERE id IN (
SELECT MAX(id)
FROM volumes
GROUP BY journal_id
)) AS V ON J.id = V.journal_id
Now my try in laravel is:
controller is:
public function index()
{
$volumes = volume::with('volumes')->orderBy('id','desc')->limit(1)->get();
return view('welcome',compact('volumes'));
}
volume model is:
function volumes()
{
return $this->belongsTo(journal::class, 'journal_id');
}
But it is giving only one row from entire volume table. I need last one row from each group of journal_id in volume table.
You can use a HasOne relationship:
class Journal extends Model
{
public function latestVolume()
{
return $this->hasOne(Volume::class)->orderByDesc('id');
}
}
$journals = Journal::with('latestVolume')->get();
Be aware that this will still fetch all volumes from the database. It then discards the "old" ones.
If you want to improve the performance by really only fetching the latest volume, you can use this package I've created: https://github.com/staudenmeir/eloquent-eager-limit
The package allows you to apply limit() to the relationship:
class Journal extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function latestVolume()
{
return $this->hasOne(Volume::class)->orderByDesc('id')->limit(1);
}
}
The right way to define the models would be:
Journal model
function volumes()
{
return $this->belongsTo(Volume::class);
}
Volume model
function journals()
{
return $this->hasMany(Journal::class);
}
Now $journal->volumes should return all the volumes that belong to that journal.

Laravel 5.8 How to order by sum of pivot table value in foreach?

I have the following tables:
parts:
id | name
1 | Lock
2 | Light
shelves:
id | name
1 | 1A
2 | 1B
part_shelf
id | part_id | shelf_id | stock
1 | 1 | 1 | 5
2 | 1 | 2 | 10
3 | 2 | 1 | 4
The relations:
In Part model:
public function shelves()
{
return $this->belongsToMany( Shelf::class )->withPivot( 'stock' );
}
In shelf model:
public function parts()
{
return $this->belongsToMany(Part::class)->withPivot('stock');
}
What I would like to do is order all the parts based on the total stock of the pivot table.
What I have tried is the following:
$parts = Part::all(); // This is inside controller
#foreach($parts->shelves()->orderBy('pivot_stock', 'desc') as $part)
#foreach($parts->shelves()->orderBy('part_shelf.stock', 'desc') as $part)
I do know that I can get the total stock for individual parts with:
$part->shelves()->sum('stock')
I just don't know how to approach it for all parts in the foreach-loop.
Also looked at other questions but that didn't really help:
How to order by pivot table data in Laravel's Eloquent ORM
Order by pivot table created_at in Laravel
Laravel 5.4 Eloquent SUM and ORDER BY pivot table column I think is the closest to what I'm looking for
I had similar problem. I had three tables. Players (in my code "Igrac"), tournaments(in my code "Turnir_Piramida"), and players_tournaments pivot table(in my code "Nastup_piramida"). In pivot table was also column point for points for each player in each tournament. And i needed to summarize points from each player and then sort table by sum of points.
I manage to do that with third link you posted. You can see my solution on this link

Create associations issue in inserting in associated table

type Group struct {
gorm.Model
CreatedBy uint64
GroupOrders []GroupOrder gorm:"many2many:group_orders;association_jointable_foreignkey:group_id;jointable_foreignkey:group_id;"
}
type GroupOrder struct {
gorm.Model
GroupID uint64
OrderID uint64
UserID uint64
Group Group
}
I am trying to insert a record like this
newGroup: = &Group{
CreatedBy: newGroupDetails.UserID,
GroupOrders: []GroupOrder{
{
OrderID: newGroupDetails.OrderID,
UserID: newGroupDetails.UserID,
},
},
}
I am creating a record by using this.
db.Create(newGroup)
It creates a record correctly in Group model but while inserting in GroupOrder model, it is inserting NULL value in group_id column.
After that, it fires a query
INSERT INTO group_orders (group_id) SELECT ? FROM DUAL WHERE NOT EXISTS (SELECT * FROM group_orders WHERE group_id = ?)[30 30] 1 pkg=mysql
And then insert another record in GroupOrder Model with all empty fields but adding the group id field as the previously inserted group_order_id value.
The resultant data in mysql
GroupOrder
| id | group_id | order_id | user_id |
+----+---------------+----------+---------+
| 30 | 0 | 8764822 | 678972 |
| 31 | 30 | NULL | NULL |
Group
| id | created_by |
+----+------------+
| 18 | 678972 |
At least, it should be inserting 18 in place of 30 in the last row group_id column in GroupOrder table.
Why is this happening? Can someone explain if there's a bug.
PS: For brevity, removed a few other columns from both Models.
Found the bug myself. Group has an has-many association with GroupOrder and not many to many. Removed that and it worked clean.
Hope it helps someone :)

Laravel Eloquent Model with multiple IDs

I will have a table that is full of information that involves other tables (relations). Most of the information in this table will only have the ID's of the referencing related table. If I were to use "products" as an example for this table it may look like this for some of the columns:
id | name | type_id | price_id | location_id | sale_id
----------------------------------------------------------------
1 | prod1 | 1 | 1 | 2 | 4
2 | prod2 | 2 | 1 | 1 | 1
3 | prod3 | 3 | 2 | 6 | 2
4 | prod4 | 1 | 2 | 3 | 4
I'm trying to take this "products" table and dump it out into a list. I would need to look up all of the items in these columns as I dump it out (the relation). I know how to do belongsToMany and hasMany, but I'm not sure how I can do this in one shot with an Eloquent model if I have a "products" model? Should I just make the products table just a pivot table? Can I do it with an Eloquent model or should I use query builder directly? I think if I were to use withPivot it would return the extra columns but the raw ID value from the column. I would need the value lookup from their respective table (the relation).
Tried something like this:
public function productItems(){
return $this->belongsToMany(Product::class)->withPivot(["type_id","price_id",...]);
}
As suggested by #BagusTesa, you should eager load your relations:
$products = Product::with(['type', 'price', 'location'])->get();
That will query for the related models allowing you to access them as model properties:
foreach ($products as $product){
// $product->type;
// $product->price;
// $product->location;
}

Retrieving 1 pivot row per relation based on pivot values in Laravel belongsToMany relation

Background - I'm creating a system where administrators can create arbitrary fields, which are then combined into a form. Users then complete this form, and the values input against each field are stored in a table. However, rather than overwrite the previous value, I plan on keeping each past value as individual rows in the table. I then want to be able to display the contents submitted in each form, but only the most recently submitted value.
Problem
I have a model, Service, that features a belongsToMany relationship with another model, Field. This relationship is defined as:
public function fields()
{
return $this->belongsToMany('App\Field')->withPivot('id', 'value', 'date')->withTimestamps();
}
The intermediary table has 3 values I wish to retrieve, id, value and date.
A Service may have 1 or more Fields, and for each field it may also have more than 1 pivot row. That is, a single Service/Field pairing may have multiple entries in the pivot table with different pivot values. For example:
Table field_service
id | service_id | field_id | value | created_at
------------------------------------------------------
1 | 1 | 1 | lorem | 2018-02-01
2 | 1 | 1 | ipsum | 2018-01-01
3 | 1 | 1 | dolor | 2017-12-01
4 | 1 | 2 | est | 2018-03-10
5 | 1 | 2 | sicum | 2018-03-09
6 | 1 | 2 | hoci | 2018-03-08
What I want is to get either:
A specific row from the pivot table for each Field associated with the Service, or
A specific value from the pivot table for each Field associated with the Service.
For example - in the table above, I would like the Service with ID 1 to have 2 Fields in the relationship, with each Field containing an attribute for the corresponding pivot value. The Fields attached would be specified by the corresponding pivot table entry having the most recent date. Something akin to:
$service->fields()[0]->value = "lorem"
$service->fields()[1]->value = "est"
I feel there's an obvious, 'Laravel'ly solution out there, but it eludes me...
Update
Somewhat unbelievably this is another case of me not understanding windowing functions. I asked a question 7 years ago that is basically this exactly problem, but with raw MySQL. The following raw MySQL basically gives me what I want, I just don't know how to Laravelise it:
SELECT services.name, fields.name, field_service.value, field_service.created_at, field_service.field_id
FROM field_service
INNER JOIN
(SELECT field_id, max(created_at) as ts
FROM field_service
WHERE service_id = X
GROUP BY field_id) maxt
ON (field_service.field_id = maxt.field_id and field_service.created_at = maxt.ts)
JOIN fields ON fields.id = field_service.field_id
JOIN services ON services.id = field_service.service_id
Try this:
public function fields()
{
$join = DB::table('field_service')
->select('field_id')->selectRaw('max(`created_at`) as `ts`')
->where('service_id', DB::raw($this->id))->groupBy('field_id');
$sql = '(' . $join->toSql() . ') `maxt`';
return $this->belongsToMany(Field::class)->withPivot('id', 'value', 'created_at')
->join(DB::raw($sql), function($join) {
$join->on('field_service.field_id', '=', 'maxt.field_id')
->on('field_service.created_at', '=', 'maxt.ts');
});
}
Then use it like this:
$service->fields[0]->pivot->value // "lorem"
$service->fields[1]->pivot->value // "est"

Resources