Optimal way to find intersection in Laravel - php

I have User and Chat model in Laravel app. User can be member in many chats so I have many-to-many relation between chats and users - belongsToMany is used.
Let's say I know few user IDs and I want to retrieve all common chats (chats where they are members at the same time, e.g., group chats where all the users with the given IDs are grouped in). How this query should be build using Laravel?

The most optimal way would be to do a join:
Chat::join('chat_user', 'chat_id', 'chats.id')
->whereIn('chat_user.user_id',$listOfUserIds)
->selectRaw('chat_user.chat_id, COUNT(1) as users_count')
->groupBy('chat_user.chat_id')
->having('users_count','=', count($listOfUserIds));
This is optimal because it only joins the pivot.
The elegant way to do this though is to use whereHas with the "cardinality" parameters, i.e., find all chats which those users are involved in as long as all of them are involved.
Chat::whereHas('user', function ($q) {
return $q->whereIn('id', $listOfUserIds);
}, '=', count($listOfUserIds));
this still only does one query but joins the pivot and the users table.

You can use:
$membersWithChats = Member::whereIn('id', $listOfIDs)->with('chats')->get();

Related

Laravel Eloquent - Combining queries to single one using relations

I'm working on a project where I'm using Laravel Eloquent to fetch all my data from my database.
I'm storing basketball clubs, teams and players in different tables.
Now I want to select all basketball players that belong to a team that belongs to a club.
I created a relation in the Club model:
public function basketballPlayers()
{
return $this->hasManyThrough('App\Models\Basketball\BasketballPlayer','App\Models\Basketball\BasketballTeam','id','team_id');
}
And a user relation in the BasketballPlayer model:
public function user()
{
return $this->hasOne('App\User','id','user_id');
}
Now when I execute the following command, DB::getQueryLog() returns 3 Queries.
Club::findOrFail($club_id)->basketballPlayers()->with('user')->get();
But when I execute the following command without using relations, DB::getQueryLog() returns only 1 query.
$players = BasketballPlayer::Join('basketball_teams','basketball_teams.id','=','basketball_players.team_id')
->join('users','users.id','=','basketball_players.user_id')
->where('basketball_teams.club_id','=',$authUser->club_id)
->get(['basketball_players.*','users.firstname','users.lastname','basketball_teams.name']);
I don't need all the data from the club and the user (see ->get(...) in the Join query).
Is it possible to achieve the same result like in the "long" manual Join-query using the much cleaner and simpler Eloquent relation approach?
Thanks for your help!
You can read less data using with (not all user columns as example) by this
Club::findOrFail($club_id)
->basketballPlayers()
->with('user:id,firstname,lastname') // <- here is change
->get();
The id (and foreign keys) column is important. You should be also able to make nested with in following way (I write code from head - so please test it):
Club::findOrFail($club_id)
->with('BasketballPlayer:id,user_id')
->with('BasketballPlayer.user:id,firstname,lastname')
->get();

Laravel select from DB where in Collection - Laravel Nova

I have a DB, "views," with many, many entries. I also have a "Courses" table, which these views are one-many related to. In Laravel Nova, I can get a metric of all views over time for a course with some code like this:
public function calculate(Request $request)
{
return $this->countByDays($request, view::where('viewable_id', $request->resourceId));
}
In this case, viewable_id is the id of the course, and $request->resourceId gives the ID of the course to sort by. Pretty simple.
However, now things get a little difficult. I have another model called Teachers. Each Teacher can have many courses, also in a one-many relationship. How do I get a metric of views over time for all the courses that teacher teaches?
I assumed the simplest way to do this would be to create a Laravel Collection with all courses the Teacher teaches (not exactly efficient), and then select all views in the database where viewable_id matches one of the courses in that list. Of course, by posting this, I couldn't figure out how to do that.
Of course, once this is figured out, I'd love to do the same thing for Categories (though that should function in a very identical manner to Teachers, so I don't need to ask that question).
How do I get a metric of views over time for all the courses that teacher teaches?
This should be the "countByDays" of views where the viewable_id is in the list of course ids that the teacher teaches.
An SQL query statement to achieve that is given below:
select * from "views"
where "viewable_id" in (select "id" from "courses" where "teacher_id" = ?)
The Eloquent query should be similar to:
$this->countByDays($request,
view::whereIn(
'viewable_id',
Course::with('teacher')
->select('id')
->where('teacher_id', $request->resourceId)
)
);

Laravel 5.1 / Eloquent: How do I use a 3rd table to select from the second table

I have three tables: users, purchase_orders and approvals.
One purchase_order has to be approved by multiple users.
When a new purchase_order gets created, I also create 3 pending approvals belonging to that PO.
The approvals table has a field allowed_user_type that determines who can approve it.
I can't figure out, what is the Eloquent way of selecting the pending purchase orders that can be approved by a specific user, as these are determined from the approvals table.
So far I can pull the pending approvals from the approvals table for a user with the following in the User model.
public function approvals_pending()
{
return $this->hasMany('App\Approval', 'allowed_user_type', 'user_type')
->where('approved', '=', 0);
}
The question is, how do I combine this with a theoretical filter?
I mean ideally, I would love to write:
return $this->hasMany('App\PO')->whereIn('id', '=', $this->approvals_pending()->get()->po_id);
Or something like that...
Any ideas would be greatly appreciated.
OK, for anyone interested I found a solution:
It's very close to what I thought I would have to write.
The lists method basically creates a single array out of the selected field, so it can be plugged-in directly to a whereIn method like so:
return \App\PO::whereIn('id', $this->approvals_pending()->lists('po_id'));
I don't know if this is the most Eloquent way of doing this but it does work.

Laravel Eloquent: How to select not attached records in many to many relationship?

I have a many to many relationship between users and books. How to select all books, that are not attached to a specific user?
The easy, but ugly, way is to load all books, load all books attached to the user and diff the collections. But there has to be an elegant way, I just can not figure it out.
It could be done using the query builder and a some joining, but how to achieve it using eloquent?
You can use whereDoesntHave() for that:
$userId = 1;
$books = Book::whereDoesntHave('users', function($q) use ($userId){
$q->where('user_id', $userId);
})->get();
This method allows you to filter the query by the existence (whereHas) (or in this case non-existence: whereDoesntHave) of a related model.
In case you want all books that aren't assigned to any user it's a bit simpler:
$books = Book::doesntHave('users')->get();

Laravel join tables

I have 2 tables and I need to join those tables.
I need to select id and name from galleries, where share gal.id = galleries.id and user_id = auth::id().
Tables:
Galleries: id, name
Share: gal_id, user_id
Please show me example in laravel. And I need to display it.
To achieve this, you have Relationship in Eloquent ORM. The official website states :
Of course, your database tables are probably related to one another. For example, a blog post may have many comments, or an order could be related to the user who placed it. Eloquent makes managing and working with these relationships easy. Laravel supports many types of relationships:
You can read all about eloquent relationship over here
If you do not want to use relationship, following is an example on how to join two tables :
DB::table('users')
->select('users.id','users.name','profiles.photo')
->join('profiles','profiles.id','=','users.id')
->where(['something' => 'something', 'otherThing' => 'otherThing'])
->get();
The above query will join two tables namely users and profiles on the basis of user.id = profile.id with two different conditions, you may or may not use where clause, that totally depends on what you are trying to achieve.

Categories