Laravel how to join one to many relationship tables - mysql

I am using Laravel 5.5 and I want to display list of data by joining tables that have one to many relationship.
Currently, I do this by going through the loop and make queries to retrieve data. This way, I think, is very inefficient, because if I were to display 1000 rows of data record, I will have to go 1000 loops to append other data with one-to-many relationship.
I am thinking to get around this problem using cache but it does not seem to solve fundamental problem.
For more understanding I have shared tables that I want do join as below.
Post Table
| id | comment_id | status |
|----|------------|--------|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 3 | 1 |
| 4 | 4 | 0 |
| 5 | 5 | 1 |
| 6 | 6 | 1 |
Comment Table
| id | order_id | content |
|----|----------|----------|
| 1 | 1 | hi |
| 2 | 1 | hellow |
| 3 | 1 | yes |
| 4 | 1 | okay |
| 5 | 2 | bye |
| 6 | 2 | good bye |
If I were to join Table Post with Table Comment, because they have one to many relationship, rows would not match. How would I join these two tables to show the list of post with comments?
Sample List Controller
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function list(Request $request)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = new PostModel;
$posts->find(1);
$displayPosts = [];
foreach ( $posts->find(1)->get() as $post ) {
$displayPosts->comments = $post->comment()->get();
}
return $displayPosts;
}
Post Model
namespace App\Model\Post;
use SoftDeletes;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function comments()
{
return $this->hasMany('App\Model\Post\Comment’, ‘post_id', 'id');
}
}

Use with() for eager loading your comments.
$posts = PostModel::with('comments')->find($id);
So your function will be like-
public function list(Request $request)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = PostModel::with('comments')->find(1);
return $displayPosts;
}
You can filter your comments with comment_id using whereHas() like the following-
$comment_id = $request->input('comment_id');
$posts = PostModel::with('comments')->whereHas('comments', function ($query) use($comment_id)
{
$query->where('id', '=', $comment_id);
})->find(1);

https://laravel.com/docs/5.1/eloquent-relationships
Firstly, you may refer to this documentation.
To setup one-to-many relationship for Post and Comment table:
A Post has Many Comments
So in you Comment table there should be a column named post_id
Inside your Post.php
public function comments()
{
return $this->hasMany('App\Comment’);
}
Inside your controller
public function list(Request $request){
$posts = Post::where('id', 1)
->with('comments')
->get()
return $posts;
}

public function list(Request $request, $id)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = PostModel::find($id);
return $posts->comments;
}
try this... Hope this can help you
or you can try like this
public function list(Request $request, $id)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = PostModel::find($id)->comments()->get();
return $posts;
}

public function list(Request $request)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = new PostModel;
$results = $posts->find(1)->with('comments')//comments is the function name you defined in the App\Model\Post
return resurts;
}
collection results contain the infomation of the post and another extra comment collection that belong to the post

Related

Laravel nested relations eager load respect ancestor id

Let's say I have a model called Research. Each research belongsToMany Location models. And each Location model BelongsToMany Contact models. BUT, each Contact is also related to Research.
class Research extends Model {
protected $table = 'researches';
public function locations()
{
return BelongsToMany( Location::class, 'research_locations_list', 'research_id', 'location_id' );
}
}
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
class Contact extends Model {
protected $table = 'contacts';
public function locations()
{
return BelongsToMany( Location::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
researches table:
+----+------------+
| id | research |
+----+------------+
| 1 | Research 1 |
| 2 | Research 2 |
+----+------------+
locations table:
+----+---------------+
| id | location |
+----+---------------+
| 1 | United States |
| 2 | Great Britain |
| 3 | Germany |
+----+---------------+
contacts table:
+----+---------+
| id | contact |
+----+---------+
| 1 | Jack |
| 2 | John |
| 3 | Hanz |
+----+---------+
research_locations_list table:
+----+-------------+-------------+
| id | research_id | location_id |
+----+-------------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 2 |
| 4 | 2 | 3 |
+----+-------------+-------------+
So Research 1 is being conducted in United States and Great Britain, Research 2 in Great Britain and Germany
location_contacts_list table:
+----+-------------+------------+-------------+
| id | location_id | contact_id | research_id |
+----+-------------+------------+-------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | 1 | 2 |
| 4 | 3 | 3 | 2 |
+----+-------------+------------+-------------+
Research 1 should have Jack and John as contacts in United States and no contacts elsewhere;
Research 2 should have John as contact in Great Britain and Hanz in Germany;
Now, with lazy load I can achieve that:
$researches = Research::all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts()->wherePivot( 'research_id', $research->id )->get();
// Will return John and Jack in United States for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
Now, the question is: how do I achieve this with eager loading?
$researches = Research::with( 'locations.contacts' )->all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts;
// Will return John and Jack in United States, John in Great Britain ( which is not supposed to happen ) for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
Perhaps I can instruct somehow for contacts to respect ancestor id? Like:
$research = Research::with( 'locations.contacts' )->where( 'researches.id = location_contacts_list.research_id' )->all();
UPDATE
The closest I came up to solving this is modifying the Location model like this:
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
// Modify contacts attribute getter
public function getContactsAttribute()
{
$contacts = $this->contacts();
if( !empty( $this->pivot->research_id ) )
{
$contacts = $contacts->wherePivot( 'research_id', $this->pivot->research_id );
}
return $contacts->get();
}
}
But it looks kind of dirty...
In your solution you get N+1 query problem. I can suggest the following solution:
class Research extends Model
{
protected $table = 'researches';
public function locations(): BelongsToMany
{
return $this->belongsToMany(Location::class, 'research_locations_list');
}
public function contacts(): BelongsToMany
{
return $this->belongsToMany(Contact::class, 'location_contacts_list')
->withPivot('location_id');
}
public function contactsByLocationAttribute(int $locationId): Collection
{
return $this->contacts
->filter(static function ($contact) use ($locationId) {
return $contact->pivot->location_id === $locationId;
});
}
}
$researches = Research::with(['locations', 'contacts'])->get();
foreach ($researches as $research) {
foreach ($research->locations as $location) {
$contacts = $research->contactsByLocation($location->id);
}
}
here there will always be only 3 queries to the database. And only necessary models will be loaded
If I got it right, you want to add some conditions inside your with statement. If you want to use eloquent syntax, you can do it like this:
$research = Research::with(['YOUR RELATION' => function ($query) {
$query->where('YOUR COLUMN', 'EQUALS TO SOMETHING');
}])->get();
Keep in mind that since inside with you use nested relationships, like locations.contacts, the where function inside the query, will filter only the last model (in this case that would be contacts). If you want to filter both locations and contacts based on some conditions, you have to write something similar to this (just an example):
$research = Research::with(['locations' => function ($query) {
$query->where('id', 1)->with(['contacts' => function ($query) {
$query->where('name', 'Tim');
}]);
})->get();
In order to do that though, you need to create a relationship also with your pivot table (if you want to use it also inside the conditions). Otherwise, you have to use a different syntax, using joins. Check this page from docs for query builders https://laravel.com/docs/9.x/queries#main-content
Perhaps, this https://laravel.com/docs/9.x/eloquent-relationships#has-many-through helps you. You should try to this

Laravel 7 select from table with two columns having ids from a Single Table

My requirement?
If i am the logged in user with Id = 1, then through the Messages Table i want to select users from Users Table to whom i sent the message or from whome i received the message.
Table 1: Users
+----+------+-------+
| id | name | email |
+----+------+-------+
| 1 | a | ??? |
| 2 | b | ??? |
| 3 | c | ??? |
| 4 | d | ??? |
| 5 | e | ??? |
| 6 | f | ??? |
| 7 | g | ??? |
| 8 | h | ??? |
| 9 | i | ??? |
| 10 | j | ??? |
+----+------+-------+
Table 2: Messages
+----+---------+-------------+
| id | user_id | receiver_id |
+----+---------+-------------+
| 1 | 1 | 2 |
| 2 | 3 | 1 |
| 3 | 4 | 1 |
| 4 | 1 | 5 |
| 5 | 1 | 3 |
+----+---------+-------------+
User Model
public function messages()
{
return $this->belongsToMany(User::class, 'messages', 'user_id', 'receiver_id');
}
Message Model
public function user()
{
return $this->belongsTo(User::class);
}
So what i have tried so far?
$id = Auth::id();
$users = User::with(['messages' => function($query) use($id){
$query->where('user_id', $id)
->orWhere('received_id', $id)
->orderBy('created_at', 'DESC');
}])->get();
dd($users);
What is the expected result?
Using this query, i am getting all of my 10 users. Although i should only get 4 users(those with id's 2,3,4,5).
If the above query is wrong, or i should follow another method or i should created some sort of relationships Please help.
Hopefully you have understood the question, i am new to Laravel but i am learning.
Probably what you need is three relations(one to many) in the User model. One for sent messages, one for received messages and one for both, like this:
public function messagesSent()
{
return $this->hasMany(Message::class, 'user_id');
}
public function messagesReceived()
{
return $this->hasMany(Message::class, 'receiver_id');
}
public function messages()
{
return $this->messagesSent()->union($this->messagesReceived()->toBase());
}
Then you should be able to get user messages like this: User::with('messages')->get();
I think you should use a join statement or "whereHas" to select users who have any messages.
$id = Auth::id();
$users = User::whereHas('messages', function ($query) use($id){
$query->where('user_id', $id)
->orWhere('received_id', $id);
})
->get();
To have access to "messages" you should add "with" statement too.
Adding my own solution(i.e working) to this question.
User Model
public function sent()
{
return $this->hasMany(Message::class, 'user_id');
}
public function received()
{
return $this->hasMany(Message::class, 'receiver_id');
}
Query
$users = User::whereHas('sent', function ($query) use($id) {
$query->where('receiver_id', $id);
})->orWhereHas('received', function ($query) use($id) {
$query->where('user_id', $id);
})->get();
dd($users);

Laravel work with pivot table with 3 or more columns

I'm trying to insert data in a table that has a MANY TO MANY relationship, part of this data must go to a bridge table (customer_works) where there are 4 columns.
the structure of the bridge table is as follows:
id | customer_id | work_id | user_id
--------------------------------------
1 | 3 | 6 | 10
2 | 1 | 9 | 8
this is my public function in controller file:
public function create(Request $request)
{
$data = $request->all();
$validatedData = $request->validate([
'work_type'=>'required',
'dead_line'=>'required',
'finished'=>'required',
'information'=>'required',
]);
$newWork = new Work;
$newWork->fill($validatedData);
$newWork->save();
$newWork->users()->attach('user_id');
$newWork->customers()->attach('customer_id');
}
it doesn't work...
Can you help me?
Thank you

Symfony2 Querybuilder select

I've 3 entities, User, Specialist, Citation.
Specialist.php
/**
*
* #ORM\OneToMany(targetEntity="Citation", mappedBy="specialist")
*/
protected $citation;
User.php
/**
* #ORM\OneToMany(targetEntity="Citation", mappedBy="user", cascade={"remove"})
*/
protected $citation;
Citation.php
/**
* #ORM\ManyToOne(targetEntity="Specialist", inversedBy="citation")
* #ORM\JoinColumn(name="specialist_id", referencedColumnName="id")
*/
protected $specialist;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="citation")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
In my database I have these registries:
Specialist table:
+----------+----------------+
| id | name |
+----------+----------------+
| 1 | Specialist One |
| 2 | Specialist Two |
+----------+----------------+
User table:
+----------+----------+
| id | name |
+----------+----------+
| 1 | User One |
+----------+----------+
Citation table:
+----------+--------------+---------------+---------+
| id | name | specialist_id | user_id |
+----------+--------------+---------------+---------+
| 1 | Citation One | 1 | 1 |
+----------+--------------+---------------+---------+
I need to get all the Specialist that haven't Citations with User 1.
If I try with this one, SpecialistRepository.php:
public function findSpecialistCitationWithUser($user) {
$query = $this->createQueryBuilder('s')
->leftJoin('s.citation', 'c')
->where('c.user = :user')->setParameter('user', $user)
->getQuery()
;
return $query->getResult();
}
See it working at SQL Fiddle.
Return the Specilist which User has Citation.
I'm trying without success with this Querybuilder, SpecialistRepository.php:
public function findSpecialistNoCitationWithUser($user) {
$query = $this->createQueryBuilder('s')
->leftJoin('s.citation', 'c')
->where('c.user <> :user')->setParameter('user', $user)
->getQuery()
;
return $query->getResult();
}
See it working at SQL Fiddle.
Return no results, empty.
with #geoB help, this is the answer:
public function findSpecialistNoCitationWithUser($user) {
$em = $this->getEntityManager();
$rsm = new ResultSetMapping();
$rsm->addEntityResult('AppBundle:Specialist', 's');
$rsm->addFieldResult('s', 'id', 'id');
$rsm->addFieldResult('s', 'name', 'name');
$query = $em->createNativeQuery('SELECT *
FROM specialist c0_
WHERE c0_.id NOT IN (
SELECT c1_.specialist_id
FROM citation c1_
WHERE c1_.user_id = 1
');
$specialist = $query->execute();
return $specialist;
}

How to join these two mysql tables?

Can someone help me with this query.
table users
+------+---------------------+
| id | name |
+------+---------------------+
| 1 | John |
| 2 | Jade |
| 3 | Robbert |
| 4 | Steve |
+------+---------------------+
table friendship
+------+---------------------+
| uid | friend_id |
+------+---------------------+
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
+------+---------------------+
Assume current user id is 1.
Want to get the name of current user friends. ( all of them )
But this code returns only current user name for each friend it finds.
which for above example data, output is : John , John each one for each row.
$friends = DB::table('users')
->join('friendship', function($join)
{
$join->on('users.id', '=', 'friendship.uid');
})
->where('friendship.blocked', '=', '0' )
->where('users.id', '=', '1' )
->get();
Above SQL Code:
select * from `users`
inner join `friendship`
on `users`.`id` = `friendship`.`uid`
where `users`.`id` = 1
You should change your join condition. You're joining on the user id and you want to join on the friend side:
select name from users
join friendship on users.id = friendship.friend_id
where friendship.uid = 1
In short, you're getting 2 jhon because you have 2 friends of jhon but you're getting the userid info of those pieces of data and you want the friend side.
Fiddle here.
Might not be an exact answer to your question, but you should use Eloquent ORM to do things that simple, and it can be something liket this:
class User extends Eloquent {
public function friends()
{
return $this->hasMany('friendship', 'uid');
}
}
class Friendship extends Eloquent {
public function user($query)
{
return $this->belongsTo('User', 'friend_id');
}
public function scopeBlocked($query)
{
return $query->where('blocked', '=', '0');
}
public function scopeNotBlocked($query)
{
return $query->where('blocked', '=', '1');
}
}
Then you just have to use it:
$user = User::find(1);
$friends = $user->friends()->notBlocked()->get();
foreach($friends as $friend)
{
echo $friend->user->name;
}