Laravel 5 relationships with two tables - laravel-5

This question is kind of related to this
Laravel 5 get name based on ID
The above questions problem is solved, works perfectly. Now I am trying to do something similar but with more relationships.
So I have a users table
id | name | email | departmentId
-------------------------------------------------------
1 | Nick | nick#email.com | 2
-------------------------------------------------------
I also have a clients table
id | clientName
-----------------
18 | McDonalds
-----------------
So these tables are straight forward. Then I have a projects table
id | projectName | clientId | userId
-------------------------------------------------------
18 | Test Project | 18 | 1
-------------------------------------------------------
So this table links to the clients and users tables. A project can have one client and many users. So in Project.php I have
public function client()
{
return $this->hasOne('App\Client', 'clientId');
}
public function user()
{
return $this->hasMany('App\user', 'userId');
}
And then in Client.php
public function project()
{
return $this->belongsTo('App\Project');
}
And User.php
public function project()
{
return $this->belongsTo('App\Project');
}
So I think the relationships are set up ok. I have some data in the database but when I try to view the index page I get the error.
Column not found: 1054 Unknown column 'clients.clientId' in 'where clause' (SQL: select * from `clients` where `clients`.`clientId` = 2 and `clients`.`clientId` is not null limit 1)
My mitegations seem to have the correct names
$table->integer('clientId')->unsigned()->default(0);
$table->foreign('clientId')->references('id')->on('clients')->onDelete('cascade');
$table->integer('userId')->unsigned()->default(0);
$table->foreign('userId')->references('id')->on('users')->onDelete('cascade');
Is there anything in the information I have shown which might be causing this error? Or is there anything else I can add?
Thanks

Actually, according to your given projects table below, the relationship is opposite:
id | projectName | clientId | userId
-------------------------------------------------------
18 | Test Project | 18 | 1
-------------------------------------------------------
The above projects table dictates as:
Client hasOne Project
User hasOne Project
Project belongsTo user
Project belongsTo Client
If you use one user_id/client_id in more than one project than it'll be hasMany. If you want to do what you asked for then you need to alter the database schema. For making it sounds like this:
Project hasOne Client (Use project_id in clients table as foreign key)
Project hasMany User (Use project_id in users table as foreign key)
Hope you got the idea.

Related

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

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.

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.

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"

Laravel DB Schema when referencing to two tables

There a able, called 'interests' which hold the id(s) of 'categories' and/or 'brands' that each user has interest in.
To prevent creating two separate tables for user_category_interests and user_brand_interest I added an enum column called type.
Now I can't figure out how should I create a migration in Schema Builder, How to set relations and foreign keys,... to take advantage of using Eloquent methods.
Schema::create('user_interests', function (Blueprint $table) {
$table->increments('id');
$table->enum('type', ['categories', 'brands']);
$table->integer('reference_id')->unsigned();
$table->timestamps();
});
Is there any better way instead of creating two separate tables and models?
P.S. I use laravel 5.4
Brands Table:
id | name
-----------
1 | Adidas
2 | Nike
3 | Puma
Categories Table:
id | name
-----------
1 | Clothes
2 | Luxury
3 | Sport Wear
User_Interests Table:
id | user_id | type | reference_id
-----------------------------------------
1 | 113 | 'brand' | 2
1 | 113 | 'brand' | 3
2 | 113 |'category'| 3
3 | 224 | 'brand' | 1
Yes - read the docs on polymorphic relations.
It works similar to what you have, but you might have columns called interestable_id and interestable_type (you can configure this to something else if you prefer). The interestable_type will literally be the string representation of the referenced class name, like App\Brand or App\Category and interestable_id will be the primary key of that model.
Best part is as it's all done through Eloquent it's already good to go with associations, eager-loading etc.
Edit: I should add that your 3 table setup is still the best way to represent this structure, just making suggestions on the column names you use and how to hook it in with Eloquent.

How to insert data to the additional field which is inside pivot table with laravel eloquent

My pivot table has additional field...
id | user_id | language_id | level
---------------------------------------
and my code:
User::find($this->userId)->languages()->attach(['language_id' => $lang_id, 'level' => $level]);
but the result is:
id | user_id | language_id | level
---------------------------------------
1 1 1 null
1 1 2 null
actually, second line's language_id must be first line's level...
how can i do it properly like this?
id | user_id | language_id | level
---------------------------------------
1 1 1 2
attach() works a bit differently. The first parameter is the id or an instance of the other model and the second parameter are other pivot fields:
User::find($this->userId)->languages()->attach($lang_id, ['level' => $level]);
As #ceejayoz mentioned you also don't have withPivot() defined in your relationship. That means level won't be available in the result. Change that by adding withPivot() to both sides of the relation:
public function languages() {
return $this->belongsToMany('Language')->withPivot('level');
}
Per the docs, you need to define the pivot table's data columns with the relationship.
public function languages() {
return $this->belongsToMany('User')->withPivot('level');
}

Resources