I have 3 tables:
Users - for storing users
User_point - for associacion between users and points(has only user_id and point_id)
Points for description of points(id, amount, description)
How do I define a relation between these? I tried
public function points(){
return $this->belongsToMany('\App\Point', 'user_point');
}
but when I do
return $user->points()->sum('amount');
it returns just one
Edit:
At first I tried making it like this as it makes more sense:
public function points(){
return $this->hasMany('\App\Point');
}
But it wouldn't work
SUM is an aggregate function and so it should only return one row.
$user->points will be a collection of points attached to that user.
$user->points() is a query that you can do additional work against (i.e. $user->points()->whereSomething(true)->get()).
As user ceejayoz pointed out, using user->points() is going to return a builder which you can do additional work on. I believe using sum() on that will look at the first row returned which is what you indicated is actually happening.
Likely, what you really want to do is $user->points->sum('amount');
That will get the sum of that column for the entire collection.
Related
I have two multi-word models, let's call them FunkyModel and AnotherModel.
Will creating a pivot table named another_model_funky_model work?
The docs and examples I've come across all use single word model names like this: model A - User, model B - Address, and pivot table will then be address_user.
If you dive into the source code of the BelongsToMany relation function, you'll find that if you haven't provided a $table, the code will execute the function joiningTable. This uses the current model and the passed related class, snake cases the names and then puts them in alphabetical order of each other.
Simply said, no matter if you have a single word or a couple, the result will always be the 2 classes snaked, in alphabetical order. Note that the alphabetical order is applied by the default php sort.
Examples:
Department + Occupation > department_occupation
AwesomeModel + LessInterestingModel > awesome_model_less_interesting_model
Role + UserPermission > role_user_permission
You can even try and see what the auto-generated name is by simply calling the following:
(new Model)->joiningTable(OtherModel::class, (new OtherModel));
Yes it would work, you can also name it whatever you want, you just need to declare the table name in the relation (same goes for the foreign keys)
class FunkyModel
{
public function anotherModels()
{
return $this->belongsToMany(AnotherModel::class, 'pivot_table_name', 'funky_model_id', 'another_model_id');
}
I have a table called rentals, within each row are columns state,city,zipcode which all house ids to another table with that info. There are about 3400 rentals. I am pulling each column to display the states,city and zipcode distinctly. I need to show how many rentals are in each one. I am doing this now via ajax, the person starts typing in what they want to see and it auto completes it with the count, but its slow because of the way im doing it.
$rentals_count = Rentals::where('published',1)->get();
foreach($states as $state) {
echo $state.”-“.$rentals_count->where(‘state’,$state->id)->count();
}
Above is roughly what im doing with pieces removed because they are not related to this question. Is there a better way to do this? It lags a bit so the auto complete seems broken to a new user.
Have you considered Eager loading your eloquent query? Eager loading is used to reduce query operations. When querying, you may specify which relationships should be eager loaded using the with method:
$rental_counts = Rentals::where('published',1)->with('your_relation')->get();
You can read more about that in Laravel Documentation
$rentals = Rentals::wherePublished(true)->withCount('state')->get();
When you loop through $rentals, the result will be in $rental->state_count
Setup a relation 'state' on rentals then call it like this
$rentals_count = Rentals::where('published',1)->with('state')->get()->groupBy('state');
$rentals_count->map(function($v, $k){
echo $v[0]->state->name .' - '. $v->count();
});
Meanwhile in Rentals Model
public function state(){
return $this->hasOne(State::class, 'state'); //state being your foreign key on rentals table. The primary key has to be id on your states table
}
I have an appointments table and an appointment_members table and my users need to be able to get a collection of appointments by searching with a "member_id", which could be a Person, Incident or CustomerID. The appointment_members table has member_id and appointment_id columns, so the member_type (also a column) is irrelevant. This all set up by a previous dev and what is missing are the relationships on the Eloquent models. I'm just creating a simple GET route that needs to return any/all appointments by that member_id. Each row has one appointment, so if I were to pass in a member_id that returned 10 results, some could have appts and others not, but at the end of the day I just need a collection of appts that are related to that member_id. Here's a screenshot of the appointment_members table:
If I create a simple hasOne relationship to appointments on appointment_members:
public function appointments()
{
return $this->HasOne(Appointment::class, 'id', 'appointment_id');
}
I can get a collection of appointment_members with it's respective appointment, but not sure how I boil it down to just getting the appointments. One workaround I have is to use that HasOne and then pluck/filter the appointments:
$appointmentMembers = AppointmentMembers::where('member_id', $request->input('memberId'))->get();
$appointments = $appointmentMembers->pluck('appointments')->filter();
Curious if anyone might see a better way to go about this. Thanks!
I'm possibly not understanding, but I would probably take the simplest approach here if the member type is not important.
The table is already set up to handle either a belongsToMany or a morphMany, so create the relationship on the Member class (or if you don't have a parent member class, stick it on each of the types Person, Incident, etc. You can also do this via poly, of course, but this is a simple example to get what you need):
public function appointments()
{
return $this->belongsToMany(Appointment::class)->withPivot('member_type');
}
And then just query on the member object you need appointments for (having poly would make this one step):
$allAppointmentsForID = $member->appointments();
$appointments = $allAppointmentsForID->wherePivot('member_type', $whateverClassThisIS);
The above takes member_type into account. If this doesn't matter, you can just use the top line.
Your original db is setup to handle polymorphic relations, so if you wanted more than the appointment you can set it up this way as well. For now, you'll need to add the TYPE to the query to cover the different classes.
If the member type is important, polymorphic might be something like this on the Member class:
public function appointments()
{
return $this->morphMany(Appointment::class, 'memberableOrmember_typeOrWhatever');
}
Then you can query on the member object with just one line
$appointments = $member->appointments();
I'm working with a datatable that I'm outputting from various relationships.
Most of the data comes from a meters table that has a Meter model, but some of it is pulled from other tables via relationships. For instance, I'm having an issue with sorting by the calibrations table.
The datatable has sortable columns that work just fine. The columns that sort based on other relationships have the joins in place so that they sort without any query errors.
All the sorting and joins work except for one, last_calibration_date.
There is no column called last_calibration_date. In fact, each meter could have multiple calibrations.
In the Meter model I grab the last_calibration_date from the calibrations table via the calibration_date column this way:
public function getLastCalibrationDateAttribute()
{
if (isset($this->relations['calibrations']) && $this->relations['calibrations']->count())
return $this->relations['calibrations']->sortBy('calibration_date', SORT_REGULAR, true)->first()->calibration_date->format('Y-m-d');
}
This works superbly when I'm not sorting by the last_calibration_date column, but returns a sql error if you try to sort by it without a join.
Here's my attempt at the join:
if ($sort == 'last_calibration_date')
{
$query->join('calibrations', 'calibrations.meter_id', '=', 'meters.id');
$sort = 'calibrations.calibration_date';
}
While this doesn't return an error it also doesn't return the actual last_calibration_date.
Just a little more info, the calibrations table is set up like so
calibrations
- id
- calibration_date
- next_calibration_date
- meter_id
So, as was said previously, any meter may have multiple calibrations.
Any ideas on how I could replicate my Meter method in my join? Or maybe another way of sorting by last_calibration_date?
Alrighty, well, I seem to have solved my problem without quite understanding why.
if ($sort == 'last_calibration_date')
{
$query->select('meters.*');
$query->join('calibrations as calibration', 'calibration.meter_id', '=', 'meters.id');
$sort = 'calibration.calibration_date';
}
Adding that $query->select('meters.*'); has solved it. Again, not sure why. My understanding is that is selecting a particular table's columns, not a model's relationships.
Anyways, it's working now.
I am new to laravel and confused about some query methods.
find($id) is useful and returns a nice array, but sometimes I need to select by other fields rather than id.
The Laravel document said I could use where('field', '=', 'value') and return a bunch of data, which is fine.
What I can't understand is why I need to add ->first() every time, even if I am pretty sure there is only one single row matches the query.
It goes like this:
$query->where(..)->orderBy(..)->limit(..) etc.
// you can chain the methods as you like, and finally you need one of:
->get($columns); // returns Eloquent Collection of Models or array of stdObjects
->first($columns); // returns single row (Eloquent Model or stdClass)
->find($id); // returns single row (Eloquent Model or stdClass)
->find($ids); // returns Eloquent Collection
// those are examples, there are many more like firstOrFail, findMany etc, check the api
$columns is an array of fields to retrieve, default array('*')
$id is a single primary key value
$ids is an array of PKs, this works in find method only for Eloquent Builder
// or aggregate functions:
->count()
->avg()
->aggregate()
// just examples here too
So the method depends on what you want to retrieve (array/collection or single object)
Also the return objects depend on the builder you are using (Eloquent Builder or Query Builder):
User::get(); // Eloquent Colleciton
DB::table('users')->get(); // array of stdObjects
even if I am pretty sure there is only one single row matches the query.
Well Laravel cant read your mind - so you need to tell it what you want to do.
You can do either
User::where('field', '=', 'value')->get()
Which will return all objects that match that search. Sometimes it might be one, but sometimes it might be 2 or 3...
If you are sure there is only one (or you only want the first) you can do
User::where('field', '=', 'value')->first()
get() returns an array of objects (multiple rows)
while
first() returns a single object (a row)
You can of course use get() when you know it will return only one row, but you need to keep that in mind when addressing the result:
using get()
$rez = \DB::table('table')->where('sec_id','=','5')->get();
//will return one row in an array with one item, but will be addressed as:
$myfieldvalue = $rez[0]->fieldname;
using first()
$rez = \DB::table('table')->where('sec_id','=','5')->first();
// will also return one row but without the array, so
$myfieldvalue = $rez->fieldname;
So it depends on how you want to access the result of the query: as an object or as an array, and also depends on what "you know" the query will return.
first() is the equivalent of LIMIT 1 at the end of your SELECT statement. Even if your query would return multiple rows, if you use first() it will only return the first row