4 level laravel eloquent relationship - php

I have four tables and i want to make a 4 level relationship. My table looks like this:
------------|------------|------------|------------|
City | Category |Subcategory | Company |
------------|------------|------------|------------|
id | id | id | id |
name | name | name | name |
------------|------------|------------|------------|
I did something like this:
City.php
public function categories()
{
return $this->belongsToMany(Category::class);
}
Category.php
public function subcategories()
{
return $this->belongsToMany(Subcategory::class);
}
Subcategory.php
public function category()
{
return $this->belongsTo(Category::class);
}
But it's not giving the result I need. I have many cities that can have many companies by their category and subcategories. For example I can save city_id, category_id, subcategory_id to company table and get the right companies doing the query:
$company->where('city_id', 1)->where('category_id', 2)->where('subcategory_id', 3)->get();
But I think it's not a best practice. I hope somebody helps. Thanks.
The main question is how can i make a normal relationship from this query:
I have many Cities that can has many categories that has many Subcategories and the subcategories i have companies for the specific city->category->subcategory.
For example in Company model i have city_id, category_id, subcategory_id and when i'm fetching categories for the specific city there will subcategories and in subcategories will be companies.
So the query will be something like:
App\Company::where('city_id', 1)->where('category_id', 2)->where('subcategory_id, 3)->get();
Sorry for bad explanation. I hope you get it...

Related

Laravel Relationship with integer[] type of postgresql column

I have categories table and products table. in products table have category_id column type of integer[].
ex: {1,2,3}
.
And I need products list with category relation which categories.id exist products.category_id
I tried in model Product:
public function category()
{
return $this->belongsTo(Category::class, \DB::raw("ANY(category_id)"), 'id');
}
no get category is null.
you should use belongs to many relation.
because integer[] type is for saving arrays of ints.
try to set it in your model like this:
in your Product(model) you will get this relation method:
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
And in your Category(model):
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class);
}
Refrence
You can try this using laravel query builder
public function category()
{
return DB::table('products')
->join('categories', 'products.category_id', '=', 'categories.id')
->get();
}
First of all, I dont think it's possible to do this with the Laravel Relationship Methods.
Second of all, if you are using Postgres, which is a relational Database, you should definitely read up on the fundamentals of database normalization.
I would recommend you have a so called pivot table, that links your products to your categories, which could look something like this:
Disclaimer: You dont need to create a Model for this. Just make a migration with php artisan make:migration create_categories_products_table
categories_products
| id | category_id | product_id |
|---------------------|------------------|---------------------|
| 55 | 1 | 5 |
| 56 | 2 | 5 |
| 57 | 3 | 5 |
| 58 | 1 | 6 |
This table links your tables and this is much more easy to handle than some arrays stored as json.. maybe think about it, it is not that much work to do. You can read upon it on the Laravel Documentation or try searching on google for pivot tables and normalization.
When you have done that:
Now you can just use the Laravel belongsToMany Relationship like so:
// Product.php
public function categories()
{
return $this->belongsToMany(Category::class, 'categories_products');
}
// Category.php
public function products()
{
return $this->belongsToMany(Product::class, 'categories_products');
}
I can't relation but with attribute i can get categories
firstly cast category_id to array and
public function getCategoriesAttribute()
{
return Category::whereIn('id',$this->category_id)->get();
}
and it works

Laravel Multiple Relationship for a sum column

I have Three Models name Invoices, Invoiceitems and Products.
Each Invoices HasMany Relationship with Invoiceitems Model.
Each Invoiceitems HasOne Relationship with Products Model.
I need to sum of Invoiceitem product amount where the Product has category 4.
Table Structure
Invoice
id | date | total_amt
Invoiceitem
id | invoiceid | product_id | product_amt | quantity | total_amt
Product
id | product_name | category_id
Relationship
Invoice Model
public function invoiceitems()
{
return $this->hasMany('App\Invoiceitems', 'invoiceid', 'id');
}
Invoiceitem Model
public function products()
{
return $this->hasOne('App\Products', 'id', 'product_id');
}
Expected Report
Invoice No | Date | Veg Category Product Amt | NonVeg Category Product Amt | Total Amt
KL0001 | 15-05-2021 | 0.00 | 190.366 | 190.366
KL0002 | 16-05-2021 | 20.00 | 350.000 | 370.000
Currently we use following Helper Function to get Particular category Products Total Amount
function getInvdiscsumamt($inv_id, $prdtype)
{
$totaldisamt = Invoiceitems::Where('invoice_id', $inv_id)->whereHas('products', function ($query) use ($prdtype) {
$query->where('category_id', $prdtype);
})->sum('total_amt');
return $totalpdtamt;
}
How to display particular category products total amount using Elequoent method
You can try one of the aggregate functions that already exist in Eloquent, see https://laravel.com/docs/8.x/eloquent-relationships#other-aggregate-functions, for instance withSum:
$invoiceItem = InvoiceItem::query()
->where('invoice_id', $invId)
->withSum(['products', function($query) use ($productType) {
$query->where('category_id', $productType);
}, 'frequency'])
->first();
Your property will then be available using the property {relation}_{function}_{column}, so in this case products_sum_frequency.
Note that the withSum does not share anything with the with (or whereHas) function, that means that if you use a subselection in your query like ->with(['products', function($query){...}]) that will be a separate selection from the withSum query.
I also recommend to use proper camelcasing in your functions and also your models. I'd also use singular names for all your model class names. So Invoiceitems would become InvoiceItem (models/InvoiceItem.php). This is the default way to define it in laravel as far as I'm aware.

Attaching extra model to BelongstoMany relations

I have two tables (users and drinks) with a pivot table, the user has a hasOne relation with profiles table, is there a way to attach the profile table to the pivot table and get all data.
Table user
id | name | email
Table profile
id | user_id | picture
Table drinks
id | name | price
Pivot Table user_drinks
id | user_id | drink_id | quantity | price | status
Drink Model
public function users()
{
return $this->belongsToMany('App\User', 'user_drinks', 'drink_id', 'user_id')->withPivot('price', 'quantity')->withTimestamps();
}
User Model
public function drinks()
{
return $this->belongsToMany('App\Drink', 'user_drinks', 'drink_id', 'user_id')->withPivot('price', 'quantity')->withTimestamps();
}
public function profile() {
return $this->hasOne('App\Profile');
}
PS: I don't wanna write this with raw sql query, it's driving me nuts.
I wouldn't change the tables relation.
I'd use the ->with() function to get the profile information within the existing relations.
So $user->drinks()->where('drink_id', $drink_id)->with('profile')->get(); would be my guess.

Laravel Query Builder, Where based on condition?

I have a dropdown to select a parent, which is self-referencing to the Page. I want to limit the results of that dropdown, so that it won't allow me to nest a Page more than one level.
If I edit the 'Son' Page, and the 'Son' has a 'Grandson', than I shouldn't be allowed to select 'Dad' as parent for the son, since it would create a nest that is to deep
In the folllowing case, When I'm editing the Son record, I shouldn't be able to select Dad as it's parent, since the son has children.
+----+-----------+----------+
| id | parent_id | title |
+----+-----------+----------+
| 1 | NULL | Dad |
| 2 | NULL | Son |
| 3 | 2 | Grandson |
+----+-----------+----------+
Now in this case, I should be able to select Dad as the parent when I'm editing the Son record, since the Son doesn't have any children
+----+-----------+----------+
| id | parent_id | title |
+----+-----------+----------+
| 1 | NULL | Dad |
| 2 | NULL | Son |
| 3 | NULL | Grandson |
+----+-----------+----------+
I'm struggling to get my head around this, and on how to wrap this all in the query builder.
What I have so far
The following code works if the Son has a Child of it's own, I won't be able to select Dad, which is good. But it fails when there's no children.
It comes down to this: My parent select should also allow pages where parent_id is null to be shown, but only if the current record (Son) doesn't have any children.
Recap: Only show the record if it doesn't occur in any parent_id, so has no children, if it does however no records are to be shown.. If it doesn't, it should show the records where parent_id is null. Is this possible in one query?
$query->where('id', '<>', $page->id);
$query->where('parent_id', '<>', $page->id);
You can create parent and children relationship in the Page model.
public function parent()
{
return $this->belongsTo('Page', 'parent_id');
}
public function children()
{
return $this->hasMany('Page', 'parent_id');
}
Now, you can check if Page has children or not and the pick up the records with parent id null like this:
$pages = Page::where('parent_id', null)->doesntHave('children')->get();
This will give you records with parent id null and no children. I hope you understand.
You can achieve this with query builder sub-query with whereRaw function
Check the code
$result = DB::table('parents as p')
->whereRaw("parent_id is null and id not in (SELECT parent_id FROM parents WHERE parent_id is not null)")
->get();
dd($result);
The o/p looks like this
1st scenario
2nd Scenario
It may help you :)
You need to join the table on itself, as the where-clause is a per-row basis.
Join the table on itself with a left-join, where the ID matches the parent ID. Then select only the rows where the joined table's IDs are null.
$pages = DB::table('pages AS p')
->leftJoin('pages AS p1', 'p.id', '=', 'p1.parent_id')
->where('p.id', '<>', $page->id)
->whereNull('p1.id')
->select('p.*')
->get();
DB fiddle showing the actual query: https://www.db-fiddle.com/f/pwW1aPmYyVqvWjKgXhisbZ/2
Hmmm... I don't think it is possible to break this to just one line because am like you will have to loop the parent_id column checking whether the current id is in the parent_id column or not:
But not to worry: a different approach would be easier: two separate tables and thus different relationship:
Pages model: has one parent
Parents model: belongs to a page
With above relationship you it is easy using eloquent to get what has no parent by just checking where has not the given relationship:
Breaking this down:
Pages model:
public function parents () {
return $this->hasOne(Parent::class, 'page_id');
}
to parents model:
Parents model:
protected $fillable = ['page_id'];
public function pages () {
return $this->belongsTo(Page::class, 'page_id');
}

Laravel: use Eloquent belongsto , get data from both tables?

In Controller, I would like to pass the only one variable with specifies column from parent in it. Now,I'm using
View::make('store.product')->with('products', Product::find($id)
->join('design','product.design_id','=','design.id')
->join('user','design.user_id','=','user.id')
->select('user.name','design.title','product.price')
->get();
My question is
1.Is there a better way to do this by using Belongsto?
2.If can do, Is it work the same with Hasmany?
This is my table structure.
User
id | name
1 | 'John'
Design
id | user_id | title
1 | 1 | 'Chill'
2 | 1 | 'Mad'
Product
id | design_id | price
1 | 1 | 20
2 | 1 | 30
And Model be like this
Product belongsto Design ,
Design belongsto User
Add a method to your Users like so for your designs that the user has;
public function designs(){
$this->hasMany('Design');
}
For the designs model add the following methods;
public function user(){
$this->belongsTo('User');
}
public function products(){
$this->hasMany('Product');
}
For your products model
public function design(){
$this->belongsTo('Design');
}
These will set up the relationship allowing you to eager load the data on your models.
This can be done like so;
$variable = Product::with('designs')->find(1); //This will load the product with the related designs
If you want all the designs and the users that belong to the designs do the following;
$variable = Product::with('designs', 'design.user')->find(1); //This will load the designs that relate to the Product and include the user that that design belongs to on the Design model.
To access the properties use the following;
$variable->designs //This will return a collection of all the designs.
$variable->designs->first()->user //This will return the user model that the design belongs to.
An example of displaying the information;
#foreach ($variable->designs as $design)
{{ $design->user->username }}
#endforeach
Please note: i have not tested this code.

Categories