Laravel Many to many without using id column - php

I have 3 tables. Players, player_skills and Skills.
Players
- bb1_player_id
Skills
- bb1_skill_id
Player_skills
- bb1_player_id
- bb1_skill_id
I have created the Player Model with this method.
public function skills()
{
return $this->belongsToMany('App\Skills', 'player_skills', 'bb1_player_id', 'bb1_skill_id');
}
That outputs this Query:
"select `skills`.*, `player_skills`.`bb1_player_id` as `pivot_bb1_player_id`, `player_skills`.`bb1_skill_id` as `pivot_bb1_skill_id` from `skills` inner join `player_skills` on `skills`.`id` = `player_skills`.`bb1_skill_id` where `player_skills`.`bb1_player_id` = ?"
The query works, but I am hoping to get a query that does not every use the id field of the tables and only uses the fields I am specifying. It uses both the id of the skill and player.id at the '?' of the query. Is there a way around this? Or do I need to rework my tables to conform?
I have also tried adding more options like below to no avail.
public function skills()
{
return $this->belongsToMany('App\Skills', 'player_skills','bb1_player_id','bb1_skill_id','bb1_player_id','bb1_skill_id');
}

Related

Laravel query builder add complex query result

I have three models with the following hierarchy :
User
id
....some other properties
Journey
id
user_id
budget
....some other properties
Confirmation
id
journey_id
user_id
....some other properties
I have a HasMany from User to Journey, a HasMany from Journey to Confirmation.
I want to get the sum for a column of the journeys table by going through the confirmations table but I cannot create an intermediate HasManyThrough relation between User and Journey by using Confirmation.
I have tried to do
public function journeysMade(): HasManyThrough
{
return $this->hasManyThrough(Journey::class, Confirmation::class);
}
// And after,
User::with(...)->withSum('journeysMade','budget')
But it was not possible because the relations are not adapted.
With hindsight, the sql query I want to translate would look like
select coalesce(sum(journeys.budget), 0) as income
from journeys
inner join confirmations c on journeys.id = c.journey_id
where c.user_id = ? and c.status = 'finalized';
How can I implement this query considering how I will use my query builder :
$driversQueryBuilder = User::with(['profile', 'addresses']); // Here
$pageSize = $request->input('pageSize', self::DEFAULT_PAGE_SIZE);
$pageNumber = $request->input('pageNumber', self::DEFAULT_PAGE_NUMBER);
$driversPaginator = (new UserFilterService($driversQueryBuilder))
->withStatus(Profile::STATUS_DRIVER)
->withCountry($request->input('country'))
->withSex($request->input('sex'))
->withActive($request->has('active') ? $request->boolean('active') : null)
->get()
->paginate(perPage: $pageSize, page: $pageNumber);
return response()->json(['data' => $driversPaginator]);
The reason why I want to get a builder is because UserFilterService expects a Illuminate\Database\Eloquent\Builder.
Do you have any idea about how I can solve this problem ?
Not 100% sure what exactly you want to sum, but I think you need the following query
$user->whereHas('journeys', function($query) {
$query->whereHas('confirmations', function($subQuery) {
$subQuery->sum('budget);
}
});
If you the above query isn't summing the budget you need, you just add another layer of abstraction with whereHas methods to get exactly what you need. Hope this helps!
EDIT:
$user->whereHas('confirmations', function($q) {
$q->withSum('journeys', 'budget')->journeys_sum_budget;
}

Handling relationship in model in laravel

I am learning relationships in Laravel php framework and I am trying to build this query
SELECT * FROM users u INNER JOIN link_to_stores lts ON u.id=lts.user_id INNER JOIN stores s ON lts.store_id=s.store_id WHERE lts.privilege = 'Owner'
I built this in Model
Link_to_store.php
public function store()
{
return $this->belongsTo('App\Store');
}
public function user()
{
return $this->belongsTo('App\User');
}
User.php
public function store_links()
{
return $this->hasMany('App\Link_to_store');
}
Store.php
public function user_links()
{
return $this->hasMany('App\Link_to_store');
}
I tried this query but this only joins user and link_to_store table
$personal_stores = Auth::user()->store_links->where('privilege','=','Owner');
Now I am confused how to join store table too. Can anyone help with this?
Schema is like this
Stores Table
store_id store_name
Users Table
id name
Link_to_stores Table
id store_id user_id privilege
I suppose store_links is actually a pivot table. In this case, you can use belongsToMany(), this will automatically take care of the pivot table.
To do this, in your User model you change the store function to this:
function stores() {
return $this->belongsToMany('App\Store', 'store_links', 'user_id', 'store_id')->withPivot('privilege');
}
Because the primary key of stores is not id, you will have to define this in you Store model with the following line:
protected $primaryKey = 'store_id';
Now to get the stores for a user, you simply call
$stores = Auth::user->stores()->wherePivot('privilege', 'Owner')->get();
I am learning relationships in Laravel php framework and I am trying to build this query
SELECT * FROM users u INNER JOIN link_to_stores lts ON u.id=lts.user_id INNER JOIN stores s ON lts.store_id=s.store_id WHERE lts.privilege = 'Owner'
You are trying to do a join here. You can do a join like this:
$stores = User::join('link_to_stores as lts', 'users.id', '=', 'lts.user_id')->join('stores as s', 'lts.store_id', '=', 's.id')->where('lts.privilege', 'Owner')->get();
But like Jerodev pointed out, it seems like Many to Many relationship might make more sense in your case. The difference is that relationship will actually execute 2 queries (1 for original model, 1 for relationship). It will then attach the related models to the original model (which is extremely handy).

Looping within a loop of a recordset

Just for reference I am using Laravel 5.
I have a two tables
users
id
first name
skills
id
name
and a pivot table
skill_user
skill_id
user_id
if I do a select in MySQL as follows:
select users.id as id, users.first_name, skills.name from users
left join skill_user on users.id = skill_user.user_id
left join skills on skill_user.skill_id=skills.id
I get:
id, first_name, skill
1, Jenna, Reliable
1, Jenna, Organized
2, Alex, Hardworking
3, Barry, Capable
3, Barry, Amiable
3, Barry, Patient
4, Janine, (null)
I pass this through to a view via a Controller:
$peoples = [];
$peoples = \DB::table('users')
->select(\DB::raw('users.id as id, first_name, skill.name as name"'))
->leftJoin('skill_user','users.id','=','skill_user.user_id')
->leftJoin('skills','skill_user.skill_id','=','skills.id')
->get();
return view('find-people', compact(['peoples']));
Now, I want to loop through this in the view (pseudocode):
forelse ( peoples as people )
people - > first_name
people - > skill
empty
no people found
endforelse
Which all works fine in a sense - but the first name gets repeated when there is more than one skill.
I can probably hack a loop of the skills by doing something like comparing user_id to itself but it seems such a clumsy way to do it.
user_id = $peoples->id
while (some looping criteria)
{
write out skills
if($peoples->id != user_id){break;}
}
How do I loop through the recordset in an elegant/eloquent fashion? Or is there a better entirely to do this?
If you define the relationships in your models you don't need to try and construct raw SQL to achieve that. This is what the Laravel ORM "Eloquent" is for!
class People extends Model {
public function skills () {
return $this->hasMany('Skill');
}
}
Then you define the skill model :
class Skill extends Model {
public function People () {
return $this->belongsToMany('People');
}
}
Now you're able to iterate over the People model and for each person, get their ->skills. This way you don't end up with the duplicate issue you're experiencing and you greatly simplify the SQL you're trying to achieve by leveraging the ORM.

Laravel Eager Load and Group Multiple Joins on Pivot

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.

Laravel 4 - Sorting Data

I am building a website in Laravel 4. I have a table in my DB called Games which contains a list of games. By default, I am sorting these Games with:
$games = Game::orderBy('title', 'ASC')->paginate(20);
Users can select which Games they are currently playing. These records are stored in a table called Players which stores game_id and user_id.
A couple of relevant models I have created:
class Game extends Eloquent {
public function players()
{
return $this->hasMany('Player');
}
}
class User extends Eloquent {
public function players()
{
return $this->hasMany('Player');
}
}
class Player extends Eloquent {
public function game()
{
return $this->belongsTo('Game');
}
public function user()
{
return $this->belongsTo('User');
}
}
What I would like to do is, be able to sort my Games by most popular (most total players) so that the Games list can be viewed in this way. I have tried the following, but it was nothing more than a stab in the dark as I'm pretty new to PHP frameworks and Laravel.
$games = Game::orderBy(count($this->players), 'DESC')->paginate(20);
Obviously the count($this->players) is wrong, but I'm kinda stuck on this one.
Thanks. Hopefully that all makes sense!
:)
You need to JOIN the tables manually then SELECT COUNT(player.id), GROUP BY (game.id) and ORDER BY COUNT(player.id).
Your final query should look like this:
SELECT game.*, COUNT(player.id) AS players
FROM game
LEFT JOIN player
ON player.game_id = game.id
GROUP BY player.id
ORDER BY players
Generating this with Laravel query builder is pretty simple:
DB::table('game')
->leftJoin('player', 'player.game_id', '=', 'game.id')
->groupBy('player.id')
->orderBy(DB::raw('COUNT(player.id)'))
->select('game.*')
->get();
Note that we cannot alias the COUNT to players and we cannot use models (no way to JOIN).

Categories