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
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;
});
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.
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
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.
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();