I have got 3 models associated with this question; Country, Manufacturer and Region.
I've simplified the tables for the sake of this question. I don't think anything else in the tables or any of the other models are anything to do with the problem.
My tables are set up like so;
manufacturers
- id
- name
countries
- id
- name
regions
- id
- name
- manufacturer_id
- country_id
What I want to be able to do is to write $manufacturer->countries in my blade and have it spit out the countries that are associated with a given manufacturer.
The models are currently related to each other like this;
Country.php
public function manufacturers()
{
return $this->hasMany(Manufacturer::class);
}
public function regions()
{
return $this->hasMany(Region::class);
}
Region.php
public function manufacturer()
{
return $this->belongsTo(Manufacturer::class);
}
public function country()
{
return $this->belongsTo(Country::class);
}
and where I'm having the problem, Manufacturer.php
I think I need a hasMany relationship. I've already got;
public function regions()
{
return $this->hasMany(Region::class);
}
and I would have thought I would have needed;
public function countries()
{
return $this->hasManyThrough(Country::class,Region::class);
}
but that leads to this error;
Column not found: 1054 Unknown column 'countries.region_id' in 'on clause' (SQL: select `countries`.*, `regions`.`manufacturer_id` as `laravel_through_key` from `countries` inner join `regions` on `regions`.`id` = `countries`.`region_id` where `regions`.`manufacturer_id` = 4)
so I tried swapping the classes round to give;
public function countries()
{
return $this->hasManyThrough(Region::class,Country::class);
}
but that leads to;
Column not found: 1054 Unknown column 'countries.manufacturer_id' in 'field list' (SQL: select `regions`.*, `countries`.`manufacturer_id` as `laravel_through_key` from `regions` inner join `countries` on `countries`.`id` = `regions`.`country_id` where `countries`.`manufacturer_id` = 4)
Does anyone know how I should be setting my relationships up in order to achieve what I want?
I also tried a belongsToMany relationship, which did bring back the countries, but multiple instances of the same country. I just want one instance of each country that appears in the regions table for any given manufacturer.
You are actually dealing with the many-to-many relationship.
In your case regions is a pivot table.
Kindly check belongsToMany.
In Laravel, the best relationship to apply is many to many relationship. What this means as applied to your case is that 1 country can have multiple manufacturers and 1 manufacturer can be in multiple countries.
If that is the case, you don't need to create a regions table, but rather a pivot table. The default naming convention in laravel is (singular and om alphabetic order), that is, country_manufacturer table and it will contain (You can always add an extra variable known as pivot value):
country_manufacturer
- id
- name // pivot value
- manufacturer_id
- country_id
Then, in the models, add belongsToMany relationship, i.e.
In Manufacturer model (without pivot) :
public function countries()
{
return $this->belongsToMany(Manufacturer::class);
}
In Country Model (With pivot):
public function manufacturers()
{
return $this->belongsToMany(Country::class)->withPivot('name');
}
Hence, you will be able to call $country->manufacturers() which will give you a list of all the manufacturers in the $country and the reverse is true: $manufacturer->countries will give you all the countries the manufacturer is based in.
Related
I have a ManyToMany relationship between Election and Party to connect parties to multiple elections.
public function parties(): BelongsToMany
{
return $this
->belongsToMany(Party::class)
->using(ElectionParty::class)
->withPivot('has_no_answers', 'published', 'program_pdf', 'program', 'id');
}
On the pivot table election_party I added an auto incrementing id.
Parties can give answers to each election, for which reason I created a hasMany relationship to Answer on the pivot table, referenced by electionparty_id
class ElectionParty extends Pivot
{
public function answers(): HasMany
{
return $this->hasMany(Answer::class);
}
}
Now, to get the answers or in this case it's count, i do this in Blade:
#foreach($election->parties as $party)
{{ $party->pivot->answers->count() }}
#endforeach
This however, does not work, because it does not try to get answers by the pivot table id, which I assumed it would be but by the id of the election:
SQLSTATE[42703]: Undefined column: 7 ERROR: column answers.election_id does not exist LINE 1: select * from "answers" where "answers"."election_id" = $1 a... ^ HINT: Perhaps you meant to reference the column "answers.question_id". (SQL: select * from "answers" where "answers"."election_id" = 16 and "answers"."election_id" is not null)
Am I missing something here or doing something not how it's supposed to be done?
The default keys are generated differently in pivot models. Specify the foreign key:
class ElectionParty extends Pivot
{
public function answers(): HasMany
{
return $this->hasMany(Answer::class, 'electionparty_id');
}
}
i'm trying to get all the lead_id inside my pivot table but i can't make it work.
controller:
$levels = Level::all();
$levels->lead()->attach('lead_id');
return $levels;
Model Level:
public function lead(){
return $this->belongsToMany(Lead::class, 'level_students')->withPivot('level_id', 'lead_id');
}
Model Lead:
public function level(){
return $this->belongsToMany(Level::class, 'level_students')->withPivot( 'lead_id', 'level_id');
}
If you mean all lead_id of a Level, then you can use pluck().
$level->lead->pluck('lead_id');
I'm not really sure what you are trying to achieve because it seems that you want to retrieve all lead_id associated with any Level. But if that is the case, then you can create a model for the pivot table (e.g. LevelLead) and use distinct() or with Query Builder:
$leadIds = DB::table('level_students')->select('lead_id')->distinct()->get();
If you want to get the referenced table's column (e.g. leads table's name column) then you can use JOIN. Check Laravel's doc for more options. For example, assuming the table name for Lead is leads, then:
$leads = DB::table('level_students')->join('leads', 'level_students.lead_id', '=', 'leads.lead_id')->select('level_students.lead_id', 'leads.name')->distinct()->get();
I have two tables in Laravel 5.4. Orders and Product which are joined by a many-to-many relationship
I'm trying to fetch all the orders together with the associated products like so:
$orders = Orders::with('products')->get();
My Order Model has this set up as
public function products()
{
return $this->belongsToMany('App\Product')
->withPivot('qty')
->withTimeStamps();
}
When I output the log I get the following error message:
Next Illuminate\Database\QueryException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'order_product.order_order_id' in 'field list' (SQL: select `products`.*, `order_product`.`order_order_id` as `pivot_order_order_id`, `order_product`.`product_product_id` as `pivot_product_product_id`, `order_product`.`qty` as `pivot_qty`, `order_product`.`created_at` as `pivot_created_at`, `order_product`.`updated_at` as `pivot_updated_at` from `products` inner join `order_product` on `products`.`product_id` = `order_product`.`product_product_id` where `order_product`.`order_order_id` in (66, 67, 70, 72, 73, 74) and `products`.`deleted_at` is null) in /home/vagrant/Projects/vcc-backoffice/vendor/laravel/framework/src/Illuminate/Database/Connection.php:647
The issue is that Laravel is looking for a table in the Pivot called "order_order_id" which doesn't exist. I haven't declared the column to be called that. My pivot table columns are called "order_id" and "product_id"
I don't know how Laravel assumes the pivot table names. Is there a way to specifically declare the column names in the pivot? Or have I made a mistake in my naming conventions?
Edit Order Model and try following code :
public function products()
{
return $this->belongsToMany('App\Product','order_product','order_id','product_id')
->withPivot('qty')
->withTimeStamps();
}
If you have two model named Order & Product and many_to_many relationship between them and their database table like orders & products then Laravel will search for a pivot table named order_product (model name in alphabetic order and joined by '_' ).
order_product table will composed of two columns order_id,product_id . This is the basic naming convention for pivot table in a nutshell :) .
I have the following models in my application
User
Group
Task
which have the following relationships
User and Group have a many-to-many relationship
Task and Group have a many-to-many relationship
So basically a user can belong to more than one group and each group can have more than one task.
Following is the table structure.
users
id
name
groups
id
name
tasks
id
name
group_user
id
group_id (foreign key with groups table)
user_id (foreign key with users table)
group_tasks
id
group_id (foreign key with groups table)
task_id (foreign key with tasks table)
Now I want to retrieve all the tasks for the user.
I tried the following approaches and both didn't work.
Approach 1
$user->groups() gives the list of groups for a user
$group->tasks() gives the list of tasks for a group
So I tried
$user->groups()->tasks() but it didn't work.
Approach 2
I tried Has Many Through by adding this to my User model
public function tasks()
{
return $this->hasManyThrough(Task::class, Group::class);
}
but even that didn't work. The following is the error that I am getting
QueryException in Connection.php line 713:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'groups.user_id' in 'field list' (SQL: select `tasks`.*, `groups`.`user_id` from `tasks` inner join `groups` on `groups`.`id` = `tasks`.`group_id` where `groups`.`user_id` = 1)
My guess is that this is happening because it is expecting one-to-many relationship, but I have a many-to-many relationship.
So is there a way to retrieve it without getting all groups and then looping through them?
User Model
public function groups()
{
return $this->belongsToMany('App\Group');
}
Group Model
public function tasks()
{
return $this->belongsToMany('App\Task');
}
Task Model
public function groups()
{
return $this->belongsToMany('App\Group');
}
Retrieving all tasks for a user.
$user = User::find(1);
$user->load('groups.tasks');
$tasks = $user->groups->pluck('tasks')->collapse();
You can also take a look at the extension of the HasManyThrough here: https://github.com/staudenmeir/eloquent-has-many-deep
It helps you to retrieve many sub-levels of your relationships.
In your case, it would be
User -> belongsToMany(Groups) -> blongsToMany (Tasks)
just add your method to the user model like:
public function tasks()
{
return $this->hasManyDeep(
'App\Task',['App\Group']
);
}
I have a Pivot table thats used to join two other tables that have many relations per hotel_id. Is there a way I can eagerload the relationship that pulls the results for both tables in one relationship? The raw SQL query, works correctly but when using belongsToMany the order is off.
Amenities Pivot Table
id
hotel_id
distance_id
type_id
Distance Table
id
name
Type Table
id
name
RAW Query (This works fine)
SELECT * FROM amenities a
LEFT JOIN distance d ON a.distance_id = d.id
LEFT JOIN type t ON a.type_id = t.id WHERE a.hotel_id = ?
My "Hotels" Model is using belongsToMany like so
public function distance() {
return $this->belongsToMany('Distance', 'amenities', 'hotel_id', 'distance_id');
}
public function type() {
return $this->belongsToMany('Type', 'amenities', 'hotel_id', 'type_id');
}
This outputs the collection, but they are not grouped correctly. I need to loop these into select fields side by side as entered in the pivot table, so a user can select a "type" and the "distance", but the order is off when using the collection. The raw query above outputs correctly.
Hotels::where('id','=','200')->with('distance', 'type')->take(5)->get();
Ok Solved it. So apparently you can use orderBy on your pivot table. Incase anyone else has this issue this is what I did on both relationships.
public function distance() {
return $this->belongsToMany('Distance', 'amenities', 'hotel_id', 'distance_id')->withPivot('id')->orderBy('pivot_id','desc');
}
public function type() {
return $this->belongsToMany('Type', 'amenities', 'hotel_id', 'type_id')->withPivot('id')->orderBy('pivot_id','desc');
}
It's not really a great practice to include other query building steps in the relationship methods on your models. The relationship method should just define the relationship, nothing else. A cleaner method is to apply eager load constraints. (scroll down a bit) Consider the following.
Hotels::where('id', 200)->with(array(
'distance' => function ($query)
{
$query->withPivot('id')->orderBy('pivot_id','desc');
},
'type' => function ($query)
{
$query->withPivot('id')->orderBy('pivot_id','desc');
},
))->take(5)->get();
If you find that you are eagerly loading this relationship in this way often, consider using scopes to keep things DRY. The end result will allow you to do something like this.
Hotels::where('id', 200)->withOrderedDistance()->withOrderedType()->take(5)->get();
P.S. Your models should be singular. Hotel, not Hotels. The model represents a single record.
Solved by using ->withPivot('id')->orderBy('pivot_id','desc');
Posted answer in the question.