I have a table called gk and I am currently running two queries. Please have a look at the queries:
Gk::groupBy(DB::raw("MONTH(created_at)"))
->groupBy(DB::raw("YEAR(created_at)"))
->selectRaw('id, user_id, sum(ton) as ton,pl, count(id) as total, sum(w) , created_at')
->with(array('user'=> function($q){
$q->select('id', 'userName', 'profilePic');
}))
->where('user_id', $userData[0]->id)
->get();
This query returns a little summary of every months. As you can I see I am grouping results by months and years. And I have another query which will return all the rows of any given months.
I am running second query like this
$m=Carbon::parse($request->date);
Gk::where('user_id',$request->user_id)->whereRaw(DB::raw("YEAR(created_at)=$m->year"))->whereRaw(DB::raw("MONTH(created_at)=$m->month"))
->orderBy('created_at','desc')
->get();
The second query returns all the rows of any month. I'm executing this query in a foreach loop for all of the months that are returned in the first query.
I am trying to combine this two query into one so that I can get a group of the results by months and years and also all the details of that month.
Any help, suggestions or idea would be extremely helpful.
[Note: For the date in second query, this date is created_at result from the first query.]
Thank you.
The way I read your question is as following: The second query is executed in a loop with results from the first one. Is that right? In my answer I have explained a way to execute the second query just one time instead of in a loop. You'd still have to execute the first query once.
So, I think that you are better of using the Php collection methods:
$results = Gk::where('user_id',$request->user_id)
->orderBy('created_at','desc')
->get()
->groupBy(function (Gk $item) {
return $item->created_at->format('Y-m');
});
The groupBy method has to return an attribute on which you want to group the elements. For this example I think that using a yyyy-mm format will do fine.
Reference: https://laravel.com/docs/5.5/collections#method-groupby
Edit: Maybe you can also get rid of the orderBy method call because you are grouping by afterwards:
$results = Gk::where('user_id',$request->user_id)
->get()
->groupBy(function (Gk $item) {
return $item->created_at->format('Y-m');
});
Edit 2: To combine the information of the two queries, you could do something like the following:
$results = Gk::where('user_id',$request->user_id)
->get()
->groupBy(function (Gk $item) {
return $item->created_at->format('Y-m');
})->map(function(Collection $rows) {
return [
'sum(ton)' => $rows->sum('ton'),
'total' => $rows->count(),
'sum(w)' => $rows->sum('w'),
'rows' => $rows
];
);
Note that I have omitted a few of the selected columns in your first query because they are not unique in the given group by. But feel free to add any logic in the map function. Because we use map() after groupBy, every call of map() will receive a collection of items for one month. You can that use that fabulous collection magic to calculate all values that you need and reduce the number of queries to just one.
Related
I want to use count and sum together on DB Query not sure how to go about it. I've tried several combinations but keep getting an error. I know I can just use a raw query but would like to learn how to use it correctly
Working:
DB::select('SELECT count(*) AS order_count, sum(total_including_vat)
AS orders_total FROM orders WHERE user_id =' .$userProfile->id);
Not Working
DB::table('orders')->where('user_id', '=', $userProfile->id)->count()->Sum();
count same as aggregates returns single value so
// you try to call method sum on number and it fails
DB::table('orders')->where('user_id', $userProfile->id)->count()->sum();
you can make two requests to get sum and count but its not a good idea, or get data in collection and let it do the math
// not a good idea
//$count = DB::table('orders')->where('user_id', $userProfile->id)->count();
//$sum = DB::table('orders')->where('user_id', $userProfile->id)->sum('total_including_vat');
//collection way
$orders = DB::table('orders')
->where('user_id',$userPorfile->id)
->get(['id', 'total_including_vat']);
$result = [
'order_count' => $orders->count(),
'orders_total' => $orders->sum('total_including_vat')
];
or the same result as for your working example with mix raw expressions
$result = DB::table('orders')
->where('user_id', $userProfile->id)
->selectRaw('count(1) as order_count, sum(total_including_vat) as orders_total')
->first();
I'm building search functionality for my app. To simplify things:
there are two tables: shops and subscriptions
Each shop can have multiple subscription records, subscription has field expires_at. Now, I assume that shop has active subscription if subscription exsists and at least one of shop's subscripion expires_at date is bigger than now().
It is one of the conditions to the whole query. Here is code:
$shops = Shop::when($subscription, function($query, $subscription) {
$query->doesntHave('subscriptions')->orWhereHas('subscriptions', function($q) use ($subscription, $query) {
$query->where('expires_at', '<', now());
});
});
It doesn't work as expected because if i.e. shop has three related subscriptions and at least one of them is expired – it assumes that shop has no active subscription (even though it has).
I would need to implement some nested function inside or whereHas, I guess, to sort by expires_at desc and then limit to one and only then pass where expires_at clause, however I've no idea how.
And I rather need to stick with Eloquent Query Builder rather than DB facade or raw sql.
Basically, it is the same problem what wasn't answered here:
https://laracasts.com/discuss/channels/eloquent/latest-record-from-relationship-in-wherehas?page=1
Try this:
$shops = Shop::doesntHave('subscriptions')->orWhereHas('subscriptions', function ($query) {
$query->whereDate('expires_at', '<', now());
})->get();
try this :
$shops = Shop::WhereHas('subscriptions')->withCount('subscriptions as
active_subscriptions_count' => function ($query) {
$query->where('expires_at', '<', now());
}])->having('active_subscriptions_count', '>=', 3)->get();
Ok, so after some tries in raw sql I figured it out:
$query->select('shops.*')
->leftJoin('subscriptions', 'shops.id', 'subscriptions.shop_id')
->whereNull('subscriptions.id')
->orWhere('subscriptions.expires_at', '<', now())
->where('subscriptions.id', function($q) {
$q->select('id')
->from('subscriptions')
->whereColumn('shop_id', 'shops.id')
->latest('expires_at')
->limit(1);
});
It is not only faster than where exists clause but also gives me what I needed – only the "highest" subscription for given shop is under consideration.
How can I get the total number of records for each of the grouped column(some_id) results generated in this eloquent query? (Answers generated using DB query builder or vanilla PHP also welcome).
$results = \App\MyModel::groupBy('some_id')
->whereNotNull('some_id')
// some query here to get sum of each grouped column records
->get();
The desired result would be such that when I'm looping through the results, I can also have a field called for example totalRecords for each grouped results. i.e.
foreach($results as $result) {
echo $result->totalRecords;
}
$results = DB::table('your_table')
->select('some_column_name', DB::raw('count(some_id) as totalRecords'))
->whereRaw('some_id IS NOT NULL')
->groupBy('some_id')
->get();
You can do like this :-
$results = \App\MyModel::select('*', DB::raw('count(some_id) as totalRecords'))
->groupBy('some_id')
->whereNotNull('some_id')
->get();
foreach ($results as $result) {
echo $result->totalRecords;
}
Collection Provide itself count() method.
But sometime once you fetch whole collection using get(). You can use count method on collection something like this:
$results = \App\MyModel::groupBy('some_id')
->whereNotNull('some_id')
// some query here to get sum of each grouped column records
->get();
dd($results->count());
Also, If your data is not collection then Php array's provide you count method you can use that too:
dd(count($results));
I used dd method just for debuging purpose.That will show you result before actual output.
count() method of array will help you to count collection as well as sub collection or array of sub array.
Good luck !!!
I'm wondering if it is possible to take the sum of multiple fields in one query using the fluent query builder.
I currently have two tables: events and attendees. Attendees belong to events and have two fields: total_raised and total_hours. What I want to do is select all events and the total amount raised/total number of hours spent on that event. Now, if I were just using SQL I would do something to the effect of:
SELECT Event.id, sum(Attendees.total_raised), sum(Attendees.total_hours)
FROM Events JOIN Attendees ON Events.id = Attendees.event_id
GROUP BY Event.id
However, I can't seem to find a way to take multiple sums at once using the fluent query builder. Is there any way to do what I'm trying to do using fluent, or should I just make it a raw SQL query?
You can use sum() i.e.:
$q = DB::table('events')
->join('attendees', 'events.id', '=', 'attendees.event_id')
->sum('total_raised')
->sum('total_hours');
If that doesn't work you can try:
...
->get(
array(
'events.id',
DB::raw('SUM(attendees.total_raised)'),
DB::raw('SUM(attendees.total_hours)')
)
);
Building on simones answer. You could do this by essentially running two queries.
$query = DB::table('events')->join('attendees', 'events.id', '=', 'attendees.event_id');
$raised = $query->sum( 'total_raised' );
$hours = $query->sum( 'total_hours' );
It depends on the situation. If it were on the admin/CMS side of things I'd be lean towards this solution. If it is on the front end it should be done in a single query which will be faster. Depending on the content it may or may not be a significant difference.
$result = DB::table('events')->join('attendees', 'events.id', '=', 'attendees.event_id')
->get( array(
DB::raw( 'SUM(attendees.total_raised) AS raised' ),
DB::raw( 'SUM(attendees.total_hours) AS hours' ),
));
I am writing this answer to help those who are in search to sum multiple fields in a single table.
If you want to sum multiple fields inside a single table so there would be no need to "join" you can simply do it likewise, assuming the table like this.
In your controller do this:
$billInfo= Bills::where('reports_id',2)->get( array(
DB::raw('SUM(Price) as total_price'),
DB::raw('SUM(balance) as total_balance'),
DB::raw('SUM(paid) as total_paid'),
));
this will result the below data:
[{"total_price":17500,"total_balance":17500,"total_paid":null}]
I am doing the same thing in my project, Here is the solution which I found. I am using Laravel 5.2 Eloquent here is the Eloquent statement.
This statement which I use in my project, Please made change according to your need.
$result = self::select("*", DB::raw('SUM(auction_amount) as total_auction_amount') , DB::raw('SUM(commission_amount) as total_commission_amount'),
DB::raw('SUM(deposit_amount) as total_deposit_amount'))
->groupBy('cp_user_id')
->get()
->toArray();
Same way you can use for your query like
$result = self::select("*", DB::raw('SUM(auction_amount) as total_auction_amount') , DB::raw('SUM(Attendees.total_raised) as total_raised'),
DB::raw('SUM(Attendees.total_hours) as total_hours'))
->with('Attendees')
->groupBy('id')
->get()
->toArray();
Of course I can use order_by with columns in my first table but not with columns on second table because results are partial.
If I use 'join' everything works perfect but I need to achieve this in eloquent. Am I doing something wrong?
This is an example:
//with join
$data = DB::table('odt')
->join('hdt', 'odt.id', '=', 'hdt.odt_id')
->order_by('hdt.servicio')
->get(array('odt.odt as odt','hdt.servicio as servicio'));
foreach($data as $v){
echo $v->odt.' - '.$v->servicio.'<br>';
}
echo '<br><br>';
//with eloquent
$data = Odt::get();
foreach($data as $odt){
foreach($odt->hdt()->order_by('servicio')->get() as $hdt){
echo $odt->odt.' - '.$hdt->servicio.'<br>';
}
}
In your model you will need to explicitly tell the relation to sort by that field.
So in your odt model add this:
public function hdt() {
return $this->has_many('hdt')->order_by('servicio', 'ASC');
}
This will allow the second table to be sorted when using this relation, and you wont need the order_by line in your Fluent join statement.
I would advise against including the order by in the relational method as codivist suggested. The method you had laid is functionally identical to codivist suggestion.
The difference between the two solutions is that in the first, you are ordering odt ( all results ) by hdt.servicio. In the second you are retrieving odt in it's natural order, then ordering each odt's contained hdt by servico.
The second solution is also much less efficient because you are making one query to pull all odt, then an additional query for each odt to pull it's hdts. Check the profiler. Considering your initial query and that you are only retrieving one column, would something like this work?
HDT::where( 'odt_id', '>', 0 )->order_by( 'servico' )->get('servico');
Now I see it was something simple! I have to do the query on the second table and get contents of the first table using the function odt() witch establish the relation "belongs_to"
//solution
$data = Hdt::order_by('servicio')->get();
foreach($data as $hdt){
echo $hdt->odt->odt.' - '.$hdt->servicio.'<br>';
}
The simple answer is:
$data = Odt::join('hdt', 'odt.id', '=', 'hdt.odt_id')
->order_by('hdt.servicio')
->get(array('odt.odt as odt','hdt.servicio as servicio'));
Anything you can do with Fluent you can also do with Eloquent. If your goal is to retrieve hdts with their odts tho, I would recommend the inverse query for improved readability:
$data = Hdt::join('odt', 'odt.id', '=', 'hdt.odt_id')
->order_by('hdt.servicio')
->get(array('hdt.servicio as servicio', 'odt.odt as odt'));
Both of these do exactly the same.
To explain why this works:
Whenever you call static methods like Posts::where(...), Eloquent will return a Fluent query for you, exactly the same as DB::table('posts')->where(...). This gives you flexibility to build whichever queries you like. Here's an example:
// Retrieves last 10 posts by Johnny within Laravel category
$posts = Posts::join('authors', 'authors.id', '=', 'posts.author_id')
->join('categories', 'categories.id', '=', 'posts.category_id')
->where('authors.username', '=', 'johnny')
->where('categories.name', '=', 'laravel')
->order_by('posts.created_at', 'DESC')
->take(10)
->get('posts.*');