Four-to-Many Relationship with Eloquent - php

I am using Laravels Eloquent ORM and i ran into a little problem with a special relationship. Lets assume i have the following table:
Recipe:
id | ... | ingredient1 | ingredient2 | ingredient3 | ingredient4
Every recipe has exactly 4 ingredients and i get the data from an external source in this specific format, thats why i have the ingredients as columns and not as a normal many-to-many relation.
I could set these up as 4 one-to-many relations, but i want to be able to write $ingredient->usedInRecipes()->get().
With 4 one-to-many relations i would have to write $ingredient->usedInRecipesAsIngredient1()->get(), [...], $ingredient->usedInRecipesAsIngredient4()->get() and merge them after afterwards, which would result in 4 queries.
If you know a good way to join these before querying the database or how to make a 4-to-many relation work please answer!

From the question I can't tell if you have already attempted this or not although as I see it you just need to use a single many-to-many relationship.
Each ingredient presumably has a common set of properties that can all be handled in one table ingredients.
id name created_at updated_at
1 Paprika 01/01/1970 00:00:00 01/01/1970 00:00:00
1 Rosemary 01/01/1970 00:00:00 01/01/1970 00:00:00
1 Basil 01/01/1970 00:00:00 01/01/1970 00:00:00
1 Oregano 01/01/1970 00:00:00 01/01/1970 00:00:00
Then your recipes table
id name created_at updated_at
1 Herb Soup 01/01/1970 00:00:00 01/01/1970 00:00:00
To hold the relationships, a pivot table ingredient_recipe
id recipe_id ingredient_id
1 1 1
2 1 2
3 1 3
4 1 4
Now all you require is a belongsToMany relationship on both your Recipe and Ingredient model.
You can code safeguards to make sure one recipe only ever has 4 relationships with ingredient if you wish but to keep it simple:
Recipe::with('ingredients')->get();
Would retrieve all the ingredients along with the recipe.
You can read more about this relationship in the documentation here.
Without Pivots
If you kept the columns ingredient_1, ingredient_2 and so on in the recipes table you could add something like this to your Recipe model.
public function scopeGetWithIngredients($query)
{
$query->leftJoin('ingredients i1', 'recipes.ingredient_1', '=', 'i1.id')
->leftJoin('ingredients i2', 'recipes.ingredient_2', '=', 'i2.id')
->leftJoin('ingredients i3', 'recipes.ingredient_3', '=', 'i3.id')
->leftJoin('ingredients i4', 'recipes.ingredient_4', '=', 'i4.id')
->select('recipes.name', 'i1.name AS ing_1', 'i2.name AS ing_2');
}
You can then just get the ingredients in your model with
Recipe::getWithIngredients();

I found a solution that seems to work in all my use cases.
In the Recipe model i defined the 4 ingredients as One-to-Many relations and made two helper scope functions.
class Recipe extends Eloquent {
public function ingredient1()
{ return $this->belongsTo('Ingredient', 'ingredient1'); }
public function ingredient2()
{ return $this->belongsTo('Ingredient', 'ingredient2'); }
public function ingredient3()
{ return $this->belongsTo('Ingredient', 'ingredient3'); }
public function ingredient4()
{ return $this->belongsTo('Ingredient', 'ingredient4'); }
public function scopeHasIngredient( $query, Ingredient $ingredient ) {
return $query-> where( 'ingredient1', '=', $ingredient->id )
->orWhere( 'ingredient2', '=', $ingredient->id )
->orWhere( 'ingredient3', '=', $ingredient->id )
->orWhere( 'ingredient4', '=', $ingredient->id );
}
public function scopeWithIngredients( $query ) {
return $query->with('ingredient1', 'ingredient2',
'ingredient3', 'ingredient4');
}
}
class Ingredient extends Eloquent {
public function ingredientForRecipes() {
return Recipe::hasIngredient( $this )->withIngredients();
}
}
To get all recipes for an Ingredient i can now call $ingredient->ingredientForRecipes()->get() and use the ingredients without extra queries.

Related

Laravel filament, is there any way to mutate TextColumn value in table?

How to get only one value in the filament table for with hasMany relationship?
I have two DB tables:
products
id
sku
1
SKU_1
2
SKU_2
product_descriptions
id
product_id
translation_id
name
1
1
1
Opel
2
1
2
Vauxhall
In my Product model I have hasMany relationship
public function productDescriptions(): HasMany
{
return $this->hasMany(ProductDescription::class);
}
When I do Tables\Columns\TextColumn::make('productDescriptions.name') it return all values separated by comma. In my example "Opel, Vauxhall"
Is there any way to manipulate/mutate return value using callback? Let say, return only first value "Opel"?
You can use calculated states.
Tables\Columns\TextColumn::make('productDescriptions.name')
->getStateUsing( function (Model $record){
return $record->productDescriptions()->first()?->name;
});

Many to many relationship from different column other than id in Laravel

I'm having two models Project and StartYear I'm having a project_technical_details table which holds most of the project information. So I've following table structure in project_technical_details:
project_id construction_start construction_area floors .....
When we were developing we were storing construction_start as year. i.e. it was hard coded for ex 2012, 2013, 2019 etc...
Now we want to establish a relationship by which we can manipulate data, so we created a model StartYear and we have following table structure:
id year created_at updated_at
so in this model I defined relationship as:
public function projects()
{
return $this->belongsToMany(
'App\Project', 'project_technical_details', 'construction_start', 'project_id');
}
But in this case I want to relate with year column not with the id. How can I achieve it. Thanks.
Your pivot table should look like this:
|------------------------|
| year | project_id | ...|
|------------------------|
Then, in your StartYear model:
public function projects()
{
return $this->belongsToMany(
'App\Project', 'project_techinical_details', 'project_id', 'year'
)
->whereColumn('project_techinical_details.construction_start', 'start_year.year');
}
Hope it helps.

How to get ID from table only if it contains multiple values needed, from different rows

I have a channel_members table containing channel_id, user_id. I need to get the channel_id if there are multiple rows using that same channel_id which contains multiple user_id that I will provide.
Example:
If there are 5 rows in the table
CHANNEL_ID | USER_ID
2 | 2
2 | 3
2 | 4
3 | 2
3 | 4
I need to get the channel_id which are being used by user_id 2, 3, and 4. So based in the table above that I provided. I should get channel_id 2.
I assume you have already defined your relations uisng models, As per your provided description, channel has many to many relation with user
class Channel extends Model
{
public function members()
{
return $this->belongsToMany('App\User', 'channel_members', 'channel_id', 'user_id');
}
}
Using eloquent realtions you can check existance of related models as
$channels = App\Channel::whereHas('members', function ($query) {
$query->where('id', '=', 2);
})->whereHas('members', function ($query) {
$query->where('id', '=', 3);
})->whereHas('members', function ($query) {
$query->where('id', '=', 4);
})->get();
Above will return you only channels who have associations with all 3 users based on supplied id
Querying Relationship Existence
SELECT CHANNEL_ID
FROM channel_members
WHERE USER_ID in (2,3,4)
GROUP BY CHANNEL_ID
HAVING COUNT(CHANNEL_ID) > 0

How to get data from another table using ID from another table

I would like to get a data from a table to another table using it's primary key.
products_table:
id name type_id date_added
1 item1 2 1234
2 item2 2 5678
3 item3 1 0000
product_type_table:
id type_name
1 type1
2 type2
3 type3
I am querying the first table using this code:
Products::select('name','type_id')->where('id',$id)->get()
Now, how do I automatically get the type_name from the product_type_table using the type_id from the products_table?
As Jack Skeletron pointed out, the Laravel documentation has examples for joining 2 or multiple tables.
In your case
$products = DB::table('products_table')
->join('product_type_table', 'products_table.type_id', '=', 'product_type_table.type_id')
->select('products_table.name, products_table.type_id')
->where('id', $id)
->get();
you can use left join.
DB::table('product_type_table')
->leftJoin('products_table', 'product_type_table.id', '=', 'products_table.type_id ')->where('product_type_table.id',$id)
->get();
In Product Model add Below code for joining 2 tables:
use App\Models\ProductType;
public function type()
{
return $this->belongsTo(ProductType::class);
}
and change the line:
Products::select('name','type_id')->where('id',$id)->get()
to
Products::select('name','type_id')->with(['type' => function($q){
return $q->select('type_name');
}])->where('id',$id)->get()
Hope this works for you.

Laravel 4.2 : How to use order by SUM in Laravel

I have 3 tables:
Posts
--id
--post
Points
--id
--user_id
--post_id
--points
User(disregard)
--id
--username
My models are like this.
Class Posts extends Eloquent {
function points(){
return $this->hasMany('points', 'post_id');
}
}
Class Points extends Eloquent {
function posts() {
return $this->belongsTo('posts', 'post_id');
}
How can I order it so that the returning results would be ordered by highest sum of points.I also need to know how I can get the sum of points per post.
Post_id | Post | Points<-- SumPoints
5 |Post1 | 100
3 |Post2 | 51
1 |Post3 | 44
4 |Post4 | 32
Here is my Code:
$homePosts = $posts->with("filters")
->with(array("points" => function($query) {
$query->select()->sum("points");
}))->groupBy('id')
->orderByRaw('SUM(points) DESC')
->paginate(8);
May I know how it would be solved by using query builder and/or model relationships
Eloquent way:
$posts = Post::leftJoin('points', 'points.post_id', '=', 'posts.id')
->selectRaw('posts.*, sum(points.points) as points_sum')
->orderBy('points_sum', 'desc')
->paginate(8);
The Query\Builder way is exactly the same, only the result won't be Eloquent models.
I think the following query-builder should get you started..
DB::table('posts')
->join('points', 'posts.id', '=', 'points.post_id')
->orderBy('sum(points)')
->groupBy('points.post_id')
->select('points.post_id', 'posts.post', 'sum(points.points) as points')
->paginate(8)
->get();

Categories