Laravel (5.3) Eloquent - Relationship issue - php

I have the following 3 tables which are normalised:
`Table: TheMovies`
id | MovieName
---------------------
1 | Zootopia
2 | Moana
3 | Toy Story
`Table: TheGenres`
id | GenreName
---------------------
21 | Action
22 | Animation
23 | Adventure
`Table: mMoviesGenres`
movieID | genreID
---------------------
1 | 21
1 | 23
2 | 22
2 | 21
3 | 23
3 | 21
As you can see in the 3rd table a movie has multiple genres, and a genre has multiple movies.
I've created TheMovies and TheGenres models in laravel.
I made sure that the relationship is made inside the models using the following code:
class TheMovies extends Model
{
public function TheGenres() {
return $this->belongsToMany('App\TheGenres', 'mMoviesGenres', 'seriesID', 'genreID');
}
}
class TheGenres extends Model
{
public function TheGenres() {
return $this->belongsToMany('App\TheMovies', 'mMoviesGenres', 'genreID', 'seriesID');
}
}
I've tested everything, and I succeeded displaying a list of genres for a particular movie, and I also succeeded displaying a list of movies for a particular genre.
The actual problem is that I want to display related movies for a particular movie based on genre.
Let's take TheMovies.id = 1 which is similar with TheMovies.id = 3, they are both Action and Adventure as you can see in the third table.
I've found out the query which is needed based on the following post:
SQL Query based on other table.
SELECT m2.movieId
FROM mMoviesGenres m1
INNER JOIN mMoviesGenres m2
ON m1.genreID = m2.genreID
WHERE m1.movieId = 1 AND
m2.movieId <> 1
GROUP BY m2.movieId
HAVING COUNT(*) >= 2
But I don't know how to transform this query in Eloquent style, and yes I can make a raw query in Eloquent, but I want to make use of the relationship created.
Please give me some advice.

You can try as:
// returns array of genre_ids associate with the TheMovies.id => 1
$genre_ids = TheGenres::whereHas('TheMovies', function($q) {
$q->where('id', 1);
})->pluck('id')->toArray();
Then use those $genre_ids to fetch the related movies as:
TheMovies::whereHas('TheGenres', function($q) use($genre_ids) {
$q->whereIn('id', $genre_ids);
})->get();
Update
Assuming you have:
$genre_ids = [21, 23];
then your query can be as:
TheMovies::whereHas('TheGenres', function($q) use($genre_ids) {
$q->whereIn('genreID', $genre_ids)
->groupBy('movieID')
->havingRaw('COUNT(DISTINCT genreID) = 2');
})->get();
Note - I have not tested it but you can give it a try.

Related

Multiple relationships on same table with two column Laravel

If the bookstore does not have any books in stock, this bookstore can purchase books from the other bookstore it is contracted with.
book_transfers table is as follows.
ID | sender_bookstore_id | delivery_bookstore_id
1 | 2 | 3
2 | 1 | 2
3 | 3 | 1
books table
ID | store_name
1 | London Garden Book Store
2 | Englaland Cafe Book
3 | Domesday Book Store
Two-column sender_bookstore_id and delivery_bookstore_id belong to the same table. The name of this table books.
My Controller code is below:
$data = BookTransfer::join('books', 'books.id', '=', 'book_transfers.sender_bookstore_id')
->select(
'book_transfers.*',
'book_transfers.sender_bookstore_id as sender_id',
'book_transfers.delivery_bookstore_id as delivery_id',
'books.store_name as sender_store_name'
)
->groupBy('book_transfers.id')
->get();
return $data;
I can only get sender_store_name here. I need to add extra code for delivery_store_name in the above controller But I don't know how to get delivery_store_name.
Best Regards,
Using relation would be much more simpler.
// BookTransfer Model
public function sender(){
return $this->belongsTo(Book::class, 'sender_bookstore_id');
}
public function delivery(){
return $this->belongsTo(Book::class, 'delivery_bookstore_id ');
}
Your controller would look like this
$data = BookTransfer::with(['sender', 'delivery'])->get();
If you want to get the datas by join, you can join the same books table again, and alias it another name:
BookTransfer::join('books AS sender_books', 'sender_books.id', '=', 'book_transfers.sender_bookstore_id')
->join('books AS delivery_books', 'deliery_books.id', '=', 'book_transfers.delivery_bookstore_id')
->select(
'book_transfers.*',
'book_transfers.sender_bookstore_id AS sender_id',
'book_transfers.delivery_bookstore_id AS delivery_id',
'sender_books.store_name AS sender_store_name',
'delivery_books.store_name AS delivery_store_name'
)

Laravel Eloquent Inner Join on Self Referencing Table

I'm trying to inner join a users table to itself using an eloquent model. I've looked everywhere but can't seem to find a solution to this without creating two queries which is what I am currently doing.
A users table has a many to many relationship itself through the pivot table friends
I tried and failed inner joining Users::class to itself. The best I can get at an inner join is by running two queries and seeing if there is an overlap. Thus one person has reached out to the other and vice versa.
friends | users
----------|------
send_id | id
receive_id| name
is_blocked|
sample data & expected result
users.id | name
---------|------
1 | foo
2 | bar
3 | baz
friends
send_id | receive_id | is_blocked
--------|------------|-----------
1 | 2 | 0
2 | 1 | 0
1 | 3 | 0
3 | 1 | 1
2 | 3 | 0
The user should have an eloquent relationship called friends. It should be what you expect comes out of requestedFriends or receivedFriends just joined.
foo->friends
returns `baz`
bar->friends
returns `foo`
baz->friends
returns empty collection
currently using
// User.php
public function requestedFriends()
{
$left = $this->belongsToMany(User::class, 'friends','send_id','receive_id')
->withPivot('is_blocked')
->wherePivot('is_blocked','=', 0)
->withTimestamps();
return $left;
}
public function receivedFriends()
{
$right = $this->belongsToMany(User::class, 'friends','receive_id','send_id')
->withPivot('is_blocked')
->wherePivot('is_blocked','=', 0)
->withTimestamps();
return $right;
}
public function friends()
{
$reqFriends = $this->requestedFriends()->get();
$recFriends = $this->receivedFriends()->get();
$req = explode(",",$recFriends->implode('id', ', '));
$intersect = $reqFriends->whereIn('id', $req);
return $intersect;
}
Research so far
Laravel Many to many self referencing table only works one way -> old question, but still relevant
https://github.com/laravel/framework/issues/441#issuecomment-14213883 -> yep, it works… but one way.
https://laravel.com/docs/5.8/collections#method-wherein
currently the only way I have found to do this in eloquent.
https://laravel.com/docs/5.7/queries#joins -> Ideally I would find a solution using an innerjoin onto itself, but no matter which way I put the id's I couldn't get a solution to work.
A solution would
A solution would inner join a self referencing table using eloquent in laravel 5.7 or 5.8, where a relationship only exists if send_id & receive_id are present on multiple rows in the friends table.
OR
Somehow let the community know that this can't be done.
Thanks in advance!
I have not checked this solution in every detail yet, but I have written a "ManyToMany" Class extending the "BelongsToMany" Class shipped with laravel, which appears to work.
The class basically just overrides the "get" method, duplicating the original query, "inverting" it and just performing a "union" on the original query.
<?php
namespace App\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class ManyToMany extends BelongsToMany
{
/**
* Execute the query as a "select" statement.
*
* #param array $columns
* #return \Illuminate\Database\Eloquent\Collection
*/
public function get($columns = ['*'])
{
// duplicated from "BelongsToMany"
$builder = $this->query->applyScopes();
$columns = $builder->getQuery()->columns ? [] : $columns;
// Adjustments for "Many to Many on self": do not get the resulting models here directly, but rather
// just set the columns to select and do some adjustments to also select the "inverse" records
$builder->addSelect(
$this->shouldSelect($columns)
);
// backup order directives
$orders = $builder->getQuery()->orders;
$builder->getQuery()->orders = [];
// clone the original query
$query2 = clone($this->query);
// determine the columns to select - same as in original query, but with inverted pivot key names
$query2->select(
$this->shouldSelectInverse( $columns )
);
// remove the inner join and build a new one, this time using the "foreign" pivot key
$query2->getQuery()->joins = array();
$baseTable = $this->related->getTable();
$key = $baseTable.'.'.$this->relatedKey;
$query2->join($this->table, $key, '=', $this->getQualifiedForeignPivotKeyName());
// go through all where conditions and "invert" the one relevant for the inner join
foreach( $query2->getQuery()->wheres as &$where ) {
if(
$where['type'] == 'Basic'
&& $where['column'] == $this->getQualifiedForeignPivotKeyName()
&& $where['operator'] == '='
&& $where['value'] == $this->parent->{$this->parentKey}
) {
$where['column'] = $this->getQualifiedRelatedPivotKeyName();
break;
}
}
// add the duplicated and modified and adjusted query to the original query with union
$builder->getQuery()->union($query2);
// reapply orderings so that they are used for the "union" rather than just the individual queries
foreach($orders as $ord)
$builder->getQuery()->orderBy($ord['column'], $ord['direction']);
// back to "normal" - get the models
$models = $builder->getModels();
$this->hydratePivotRelation($models);
// If we actually found models we will also eager load any relationships that
// have been specified as needing to be eager loaded. This will solve the
// n + 1 query problem for the developer and also increase performance.
if (count($models) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
}
/**
* Get the select columns for the relation query.
*
* #param array $columns
* #return array
*/
protected function shouldSelectInverse(array $columns = ['*'])
{
if ($columns == ['*']) {
$columns = [$this->related->getTable().'.*'];
}
return array_merge($columns, $this->aliasedPivotColumnsInverse());
}
/**
* Get the pivot columns for the relation.
*
* "pivot_" is prefixed ot each column for easy removal later.
*
* #return array
*/
protected function aliasedPivotColumnsInverse()
{
$collection = collect( $this->pivotColumns )->map(function ($column) {
return $this->table.'.'.$column.' as pivot_'.$column;
});
$collection->prepend(
$this->table.'.'.$this->relatedPivotKey.' as pivot_'.$this->foreignPivotKey
);
$collection->prepend(
$this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->relatedPivotKey
);
return $collection->unique()->all();
}
}
I came across the same problem quite some time ago and have thus been following this problem closely and have made a lot of research. I have come across some of the solutions you have also found, and some more, and also have thought of other solutions that I summed here, mostly how to get both user_ids in the same column. I am afraid they will all not work well. I am also afraid that using any custom classes will stop you from using all of Laravel's handy relation features (especially eager loading). So I still thought what one could do, and, until one comes up with a hasMany-function on many columns, I think I have come up with a possible solution yesterday. I will show it first and then apply it to your project.
My project
Initial solution
In my project, one user partners with another one (= partnership) and then later will be assigned a commission. So I had the following tables:
USERS
id | name
---------|------
1 | foo
2 | bar
17 | baz
20 | Joe
48 | Jane
51 | Jim
PARTNERSHIPS
id | partner1 | partner2 | confirmed | other_columns
----|-----------|-----------|-----------|---------------
1 | 1 | 2 | 1 |
9 | 17 | 20 | 1 |
23 | 48 | 51 | 1 |
As each user should always have only one active partnership, the non-active being soft-deleted, I could have helped myself by just using the hasMany function twice:
//user.php
public function partnerships()
{
$r = $this->hasMany(Partnership::class, 'partner1');
if(! $r->count() ){
$r = $this->hasMany(Partnership::class, 'partner2');
}
return $r;
}
But if I had wanted to lookup all partnerships of a user, current and past, this of course, wouldn't have worked.
New solution
Yesterday, I came up with the solution, that is close to yours, of using a pivot table but with a little difference of using another table:
USERS
(same as above)
PARTNERSHIP_USER
user_id | partnership_id
--------|----------------
1 | 1
2 | 1
17 | 9
20 | 9
48 | 23
51 | 23
PARTNERSHIPS
id | confirmed | other_columns
----|-----------|---------------
1 | 1 |
9 | 1 |
23 | 1 |
// user.php
public function partnerships(){
return $this->belongsToMany(Partnership::class);
}
public function getPartners(){
return $this->partnerships()->with(['users' => function ($query){
$query->where('user_id', '<>', $this->id);
}])->get();
}
public function getCurrentPartner(){
return $this->partnerships()->latest()->with(['users' => function ($query){
$query->where('user_id', '<>', $this->id);
}])->get();
}
// partnership.php
public function users(){
return $this->belongsToMany(User::class);
}
Of course, this comes with the drawback that you always have to create and maintain two entrances in the pivot table but I think this occasional extra load for the database -- how often will this be altered anyway? -- is preferable to having two select queries on two columns every time (and from your example it seemed that you duplicated the entries in your friends table anyway).
Applied to your project
In your example the tables could be structured like this:
USERS
id | name
---------|------
1 | foo
2 | bar
3 | baz
FRIENDSHIP_USER
user_id | friendship_id
---------|------
1 | 1
2 | 1
3 | 2
1 | 2
FRIENDSHIPS
id |send_id* | receive_id* | is_blocked | [all the other nice stuff
--------|---------|-------------|------------|- you want to save]
1 | 1 | 2 | 0 |
2 | 3 | 1 | 0 |
[*send_id and receive_id are optional except
you really want to save who did what]
Edit: My $user->partners() looks like this:
// user.php
// PARTNERSHIPS
public function partnerships(){
// 'failed' is a custom fields in the pivot table, like the 'is_blocked' in your example
return $this->belongsToMany(Partnership::class)
->withPivot('failed');
}
// PARTNERS
public function partners(){
// this query goes forth to partnerships and then back to users.
// The subquery excludes the id of the querying user when going back
// (when I ask for "partners", I want only the second person to be returned)
return $this->partnerships()
->with(['users' => function ($query){
$query->where('user_id', '<>', $this->id);
}]);
}

Search based on many to many relation with multiple condition on same column

I have a many to many relation on my laravel 5.3 project.
There is two models associated with this problem. They are Job and Tag.
The model Job can have many Tag associated with it and also the model Tag related to many Job.
I have assigned the relation Many To Many using the key word "belongsToMany" in both model classes.
The database of the relation is :
table: jobs
________________
id | title |
____|___________|
1 | Developer|
____|___________|
2 | Designer |
____|___________|
3 | Tester |
____|___________|
4 | manager |
____|___________|
table : tags
_________________
id | tag_name |
____|___________|
1 | php |
____|___________|
2 | html |
____|___________|
3 | css |
____|___________|
and the pivot table : job_tag
_____________________
job_id | tag_id |
________|___________|
1 | 1 |
________|___________|
1 | 2 |
________|___________|
2 | 1 |
________|___________|
3 | 1 |
________|___________|
3 | 2 |
________|___________|
4 | 2 |
________|___________|
The user can pass multiple tag names to retrieve the jobs which are related to the both tags which are passed by the user.(AND condition)
Example: when a user passes the tag_name "php" and "html" i want to display the details of the jobs [id:1 title:developer] and [id:2 title:designer].
Hint : retrieve the job records which have the tags both "php" and "html"
NB: the number of the tag names passed by the user is not defined ie, user can pass as much as they can.
Here is something i have tried:
$jobs = Job::whereHas('tags', function ($query) use($params) {
foreach ($params['tags'] as $tag) {
$query->where('tag', $tag);
}
});
The above code doesnt returns anything but generates the following sql:
select * from `jobs` where exists (select * from `tags` inner join `job_tag` on `tags`.`id` = `job_tag`.`tag_id` where `job_tag`.`job_id` = `jobs`.`id` and (`tag` = css) and (`tag` = javascript))
I was tried to get results based on this sql, which doesn't work.
Hope you people understand my problem and will help me find a solution.
Thanks in advance.
One option is to use multiple whereHas as:
$query = Job::query();
foreach ($params['tags'] as $tag) {
$query->whereHas('tags', function ($q) use($tag) {
$q->where('tag', $tag);
});
}
$jobs = $query->get();
OR
Assuming you have an array of tag id as:
$tag_ids = [2, 4];
Then you can try as:
Job::whereHas('tags', function($q) use($tag_ids) {
$q->whereIn('tag_id', $tag_ids)
->groupBy('job_id')
->havingRaw('COUNT(DISTINCT tag_id) = '.count($tag_ids));
})->get();
In your foreach, you need the tags to be compared with OR instead of AND.
$jobs = Job::whereHas('tags', function ($query) use ($params) {
$query->where(function ($query2) use ($params) {
foreach ($params['tags'] as $tag) {
$query->orWhere('tag', $tag);
}
})
});
I'm not sure if the second level of closure is necessary, but it will help if you end up adding on any other WHERE conditions to your query after the fact.

Laravel table relationship for getting user list

I'm working on a laravel 5 application.
I have 3 tables:
table A: 'users'
id | name
————————————————
1 | Dave
2 | Robet
3 | Jimmy
4 | Daniel
and Table B classroom_users
user_id | classroom_id
1 | 5
4 | 5
3 | 6
4 | 5
I need to return all users 'name' for classroom 5
Please let me know what is the best way to do that. Trough model or Database join
Any help appreciated.
Assuming you have models and its relationships will be defined as below:
Classroom Model:
public function users(){
return $this->belongsToMany(User::class,'classroom_users','classroom_id','user_id');
}
User Model:
public function classrooms(){
return $this->belongsToMany(UserClassrooms::class,'classroom_users','user_id','classroom_id');
}
Now, in controller you can retrieve the users belonging to the classroom id 5 as below:
$classroom_id = 5 ;//change this to the id you want
$users = Classroom::find($classroom_id)->users()->get();
Laravel has introduced Pivot tables, which works like this
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
You can read more pivot on Official Docs

Joining Tables in Laravel 4.2 Eloquent

I'm not so good at making queries using Laravel Eloquent. I've two tables
stories table
------------------------------------------------------------------
id | title | body | author_id |...
------------------------------------------------------------------
1 | Story 1 | Once a.. | 2
2 | Story 2 | There is | 4
3 | Something | You are. | 2
activities table
------------------------------------------------------------------
id | story_id | liker_id |...
------------------------------------------------------------------
1 | 2 | 2
Here author_id & liker_id are actually user_id. I want to get the Stories authored and liked by a specific user to display these stories in his profile.
I want to use the Eloquent ORM. I tried something like this using query builder
$stories = DB::table('stories')
->join('activities', function($join)
{
$join->on('stories.author_id', '=', 'activities.liker_id')
})
->where('stories.author_id', $author_id)
->get();
return $stories;
I can get story_id for a specific liker_id by join but couldn't get the details from stories table using story_id in a Single query.
Here is simple method with query builder to get Stories authored and liked by a specific user
$author_id = 1;
$stories = DB::table('stories')
->join('activities', 'stories.author_id', '=', DB::raw('activities.liker_id AND stories.id = activities.story_id'))
->Where('stories.author_id', $author_id)
->get();
//select * from `stories` inner join `activities` on `stories`.`author_id` = activities.liker_id AND stories.id = activities.story_id where `stories`.`author_id` = 1"
with Eloquent you can do as following create 2 model file
1. Story model (Story.php)
2. Activity Model (Activity.php)
Story.php
class Story extends Eloquent {
public function activities()
{
return $this->hasMany('Activity');
}
}
Activity.php
class Activity extends Eloquent {
public function story()
{
return $this->belongsTo('Story');
}
}
than you can write function within Story to get data as your need
$stories = Story::with('activities')
->where(DB::raw('stories.id = activities.story_id'))
->Where('stories.author_id', $author_id)
->get();
// haven't tested with eloquent but it should work

Categories