laravel withsum with additional where clause - laravel

I know you can do a withSum on a relationship like this:
$posts = Post::withSum('comments', 'votes')->get();
But I like to chain an additional where clause on this, something like this (doesn't work but as an example):
$posts = Post::withSum('comments', 'votes', function (Builder $query) {
$query->where('comments.votes', '>', 5);
})->get()
Is that possible?

While Aggregating Related Models is not explicit about this in the documentation, it does give us a clue by mentioning:
If you need to set additional query constraints on the count query, you may pass an array keyed by the relationships you wish to count. The array values should be closures which receive the query builder instance.
If you need to set additional query constraints on the count query, you can pass an encoded array by the relationships you want to count. The values in the array must be anonymous functions that receive the query builder instance.
So, we can take advantage of that anonymous function to modify the query builder instance so that instead of taking the default values (which would use the aggregate 'count'), and pass it the subquery we want to do, which is the where clause and the 'sum' of the column.
Said that, your query cuold looks like:
Post::withCount(['comments as comments_sum_votes' => function($query) {
$query->where('comments.votes', '>', 5)->select(DB::raw('sum(votes)'));
}])->get()

Related

Is Laravel sortBy slower or heavier to run than orderBy?

My concern is that while orderBy is applied to the query, I'm not sure how the sortBy is applied?
The reason for using sortBy in my case is because I get the collection via the model (i.e. $user->houses->sortBy('created_at')).
I'm just concerned about the performance: is sortBy simply looping each object and sorting them?, or is Laravel smart enough to simply transform the sortBy into an orderBy executed within the original query?
You need orderBy in order to perform a SQL order.
$user->houses()->orderBy('created_at')->get()
You can also eager load the houses in the right order to avoid N+1 queries.
$users = User::with(['houses' => function ($query) {
return $query->orderBy('created_at');
}])->get();
$orderedHouses = $users->first()->houses;
The sortBy method is applied to the Collection so indeed, it will looping each objects.
The orderBy() method is much more efficient than the sortBy() method when querying databases of a non-trivial size / at least 1000+ rows. This is because the orderBy() method is essentially planning out an SQL query that has not yet run whereas the sortBy() method will sort the result of a query.
For reference, it is important to understand the difference between a Collection object and a Builder object in Laravel.
A builder object is, essentially, an SQL query that has not been run. In contrast, a collection is essentially an array with some extra functionality/methods added. Sorting an array is much less efficient than pulling the data from the DB in the correct format on the actual query.
example code :
<?php
// Plan out a query to retrieve the posts alphabetized Z-A
// This is still a query and has not actually run
$posts = Posts::select('id', 'created_at', 'title')->orderBy('title', 'desc');
// Now the query has actually run. $posts is now a collection.
$posts = $posts->get();
// If you want to then sort this collection object to be ordered by the created_at
timestamp, you *could* do this.
// This will run quickly with a small number or rows in the result,
// but will be essentially unusable/so slow that your server will throw 500 errors
// if the collection contains hundreds or thousands or objects.
$posts = $posts->sortBy('created_at');

Laravel Eloquent: count($result) vs $result->count()

I'm a bit new to this, and was originally trying to check if my model was returning results with isEmpty(), but thought I'd try count() instead, then I came across the following:
I've got the following code, which returns data from my model:
$results = Game::where('code', '=', $code)->with('genre', 'creator')
And whether I use first() or get() combined with count(result) or $results->count() I get different values, and I'm not sure why.
when using ->first()
dd($results->count()) = 11930 // Number of rows in the db
when using ->get()
dd($results->count()) = 1 // What I'd expect the query to return
when using ->first()
dd(count($results)) = "count(): Parameter must be an array or an object that implements Countable"
when using ->get()
dd(count($results)) = 1
I don't understand 1) why when using first, the count is the same as every row in the db. 2) Why count() can't be used with first().
Is anyone able to shed some light as to why I can't use count on first as I'd like to?
Update:
I'm also not able to use ->isEmpty() with ->first() but can with ->get()...?
When I try using it with first, I get Illuminate\Database\Query\Builder::isEmpty does not exist.
Disclaimer: I'm not sure why your database count and your results count aren't the same, however I can shed some light on the different types of count.
Game::where('code', '=', $code)->count();
This is being called on a query builder instance. It is run on the database query, without selecting all the rows. Check out the title Aggregates here: https://laravel.com/docs/5.7/queries
Game::where('code', '=', $code)->get()->count();
As soon as your use get() laravel selects the rows, boots them all as models, and creates a collection. This count is on the collection (a bit like an array) so just gets the number that are returned (i.e. if they are paginated or anything like that it will just get that amount). Check out Count here.
Game::where('code', '=', $code)->first()->count();
This is being run on the first returned model... unless you've written it, a default laravel model won't have a count() method.
count($results)
Finally, count() when not a class method is just the default php function that returns the length of an array or other object (documentation).
First of all,
get() returns collection of objects while first() returns modal object of query.
$results = Game::where('code', '=', $code)->with('genre', 'creator')
dd($results->count()) = 11930 // Number of rows in the db
when using ->get()
$results = Game::where('code', '=', $code)->with('genre', 'creator')->get()
dd($results->count()) = 1
because it has collection, which contains numbers of objects of database data. As ->count getting only one collection so it returns 1.

Is it possible to use the result from a `whereHas`-query in another `whereHas`?

I want to filter results of one subquery with the result of another (which are sharing the same relations). Something like this:
modelA::whereHas('modelB', function ($query) {
$query->select('modelC_id');
})
->whereHas('modelD', function ($query) {
$query->where('field', 'modelC_id')
});
For this example, assume the following relations:
modelA->hasMany('modelB')
modelA->hasMany('modelD')
modelB->hasOne('modelC')
modelD->hasOne('modelC')
Models and relations are all working as they should, but I want modelA to lookup the modelC value through modelB, and then use this to filter the lookup on modelC through modelD.
I can do all this by looking up the value and use this on a second query, but it would be nice if I can use this in one statement (since the value can be looked up in the query!) so I can create a local scope on modelA.
Another option would be to add the related field to the resultset which I can then use in the where-query, but I cannot figure out how to get modelC_id in the resultset...
Is this possible?

laravel database query Does `where` always need `first()`?

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

Laravel - When to use ->get()

I'm confused as to when ->get() in Laravel...
E.G. DB::table('users')->find(1) doesn't need ->get() to retrieve the results, neither does User::find(1)
The laravel docs say "...execute the query using the get or first method..."
I've read the Fluent Query Builder and Eloquent docs but don't understand when the usage of get() is required...
Thanks for the help
Since the find() function will always use the primary key for the table, the need for get() is not necessary. Because you can't narrow your selection down and that's why it will always just try to get that record and return it.
But when you're using the Fluent Query Builder you can nest conditions as such:
$userQuery = DB::table('users');
$userQuery->where('email', '=', 'foo#bar.com');
$userQuery->or_where('email', '=', 'bar#foo.com');
This allows you to add conditions throughout your code until you actually want to fetch them, and then you would call the get() function.
// Done with building the query
$users = $userQuery->get();
For find(n), you retrieve a row based on the primary key which is 'n'.
For first(), you retrieve the first row among all rows that fit the where clauses.
For get(), you retrieve all the rows that fit the where clauses. (Please note that loops are required to access all the rows or you will get some errors).
find returns one row from the database and represent it as a fluent / eloquent object. e.g. SELECT * FROM users WHERE id = 3 is equivalent to DB::table('users')->find(3);
get returns an array of objects. e.g. SELECT * FROM users WHERE created_at > '2014-10-12' is equivalent to DB::table('users')->where('created_at', '>', '2014-10-12')->get() will return an array of objects containing users where the created at field is newer than 4014-10-12.
The get() method will give you all the values from the database that meet your parameters where as first() gets you just the first result. You use find() and findOrFail() when you are searching for a key. This is how I use them:
When I want all data from a table I use the all() method
Model::all();
When I want to find by the primary key:
Model::find(1)->first();
Or
Model::findOrFail(1)->first();
This will work if there is a row with a primary key of one. It should only retrieve one row so I use first() instead of get(). Remember if you deleted the row that used key 1, or don't have data in your table, your find(1) will fail.
When I am looking for specific data as in a where clause:
Model::where('field', '=', 'value')->get();
When I want only the first value of the data in the where clause.
Model::where('field', '=', 'value')->first();
Basically what you need to understand is that get() return a collection(note that one object can be in the collection but it still a collection) why first() returns the first object from the result of the query(that is it returns an object)
#Take_away
Get() return a collection first() return an object
You can use get() method with latest() method to get the latest record that were recently added to your table
For example
$user=Student::latest()->get();
return all the data in descending order

Resources