How to take count based on different where conditions in laravel? - laravel

I am trying to count tickets based on different where conditions . For which I am using four different queries but same model. Can I do that in one query?
$openTickets = Ticket::where('status',1)->count();
$pending = Ticket::where('status',2)->count();
$unAssigned = Ticket::where('agent_id',null)->count();
$unResolved = Ticket::whereNotIn('status',[3,4])->count();

Ticket::selectRaw('COUNT(CASE WHEN status = 1 THEN 1 END) AS open_tickets')
->selectRaw('COUNT(CASE WHEN status = 2 THEN 1 END) AS pending_tickets')
->selectRaw('COUNT(CASE WHEN agent_id IS NULL THEN 1 END) AS unassigned_tickets')
->selectRaw('COUNT(CASE WHEN status NOT IN (3,4) THEN 1 END) AS unresolved_tickets')
->first();
You can of course resolve the multiple queries with this query. We can use conditional cases and count.

You can sum up conditionals but will need lots of raw parts in your query:
$result = DB::table('tickets')->whereIn('status', [1,2])->orWhereNull('agent_id')->orWhereNotIn('status', [3,4]) // This is to filter out the things we don't care about
->select([
DB::raw('SUM(IF(status = 1, 1,0)) as countOpen'),
DB::raw('SUM(IF(status = 2, 1,0)) as countPending'),
DB::raw('SUM(IF(agent_id IS NULL, 1,0)) as countUnassigned'),
DB::raw('SUM(IF(status NOT IN (3,4), 1,0)) as countUnresolved'),
])->first()
$openTickets = $result->countOpen;
$pending = $result->countPending;
$unAssigned = $result->countUnassigned;
$unResolved = $result->countUnresolved;

Related

Laravel: Sum column based on relationship condition

Let's say we have two tables:
Payments
id
reason_id
amount
1
1
100
2
2
10
3
1
30
Payment Reasons
id
title
is_discount
1
Payment for something
0
2
Discount for something
1
I'd like to query payments table and sum amount column based on its relationship payment_reasons.is_discount value:
total_received
total_discounted
130
10
How to achieve this?
If you want to get the data in one db-request, I don't think there is a clean laravel-way to solve this. I've been trying out some raw mysql to get it into a working query and it would look like this:
select
sum(if(pr.is_discount = 0, p.amount, 0)) as total_received,
sum(if(pr.is_discount = 1, p.amount, 0)) as total_discounted
from payments p
join payment_reasons pr on pr.id = p.reason_id
The if statement will render if discount needs to be used per row and the sum wil simply sum all the end-results together.
Doing this in laravel with a model does not really make sence, since there are no fields in the result which your model could use anyway. In this case, using eloquent would be more (unmaintainable) code than a simple Query Builder:
$data = DB::select('
select
sum(if(pr.is_discount = 0, p.amount, 0)) as total_received,
sum(if(pr.is_discount = 1, p.amount, 0)) as total_discounted
from payments p
join payment_reasons pr on pr.id = p.reason_id
')
If you don't mind doing multiple queries to fetch the data, models make more sense:
$totalReceived = Payment
::whereHas('paymentReason', fn($q) => $q->where('is_discount', 0))
->sum('amount');
$totalDiscounted = Payment
::whereHas('paymentReason', fn($q) => $q->where('is_discount', 1))
->sum('amount');
Please note that this example will perform 4 queries, 2 on payments, and 2 on payment_reasons

Laravel Eloquent counts with multiple ranges of data

I'm trying to figure out how to create Laravel query that will produce me with the following mysql query.
select
count(CASE WHEN points BETWEEN 0 AND 1.99 THEN 1 END) as count0_199,
count(CASE WHEN points BETWEEN 2 AND 3.99 THEN 1 END) as count2_399,
count(CASE WHEN points BETWEEN 4 AND 5.99 THEN 1 END) as count4_599
from
stats
Is it possible to make it in Eloquent? Haven't seen it in the documentation.
Thanks
From my knowledge using eloquent to perform this will involve some formatting of returned data:
$results = [];
$stats = Stat::where('points', '>=', '0')
->where('points', '<=', 5.99)
->get()
->each(function ($stat, $key) use (&$results) {
if ($stat->points >= 0 && $stat->points <= 1.99) {
$results['count0_199'][] = $stat;
}
if ($stat->points >= 2 && $stat->points <= 3.99) {
$results['count2_399'][] = $stat;
}
if ($stat->points >= 4 && $stat->points <= 5.99) {
$results['count4_599'][] = $stat;
}
});
return $results;
In the code above, all stats where points are between 0 and 5.99 are returned (as they will be returned anyway). The returned collection is then looped through to populate a $results array which will group the returned data.
There is also the DB Facade:
$query = [
'count(CASE WHEN points BETWEEN 0 AND 1.99 THEN 1 END) as count0_199',
'count(CASE WHEN points BETWEEN 2 AND 3.99 THEN 1 END) as count2_399',
'count(CASE WHEN points BETWEEN 4 AND 5.99 THEN 1 END) as count4_599'
];
$results = DB::select(implode(',', $query) . ' from stats');

Write multiple laravel query at once

im new to laravel, I just want to know if there's any way to efficiently rewrite this code?
$answer1 = SurveyAnswer::where('answer_1','=','1')->get()->count();
$answer2 = SurveyAnswer::where('answer_1','=','2')->get()->count();
$answer3 = SurveyAnswer::where('answer_1','=','3')->get()->count();
$answer4 = SurveyAnswer::where('answer_1','=','4')->get()->count();
$answer5 = SurveyAnswer::where('answer_1','=','5')->get()->count();
Get the data first:
$answers = SurveyAnswer::whereIn('answer_1', [1, 2, 3, 4, 5])->get();
Then count answers using loaded collection:
$answer1 = $answers->where('answer_1', 1)->count();
$answer2 = $answers->where('answer_1', 2)->count();
...
This code will generate just one DB query instead of five.
Try this:
$matches = [1,2,3,4,5];
$answer = SurveyAnswer::whereId('answer_1', $matches)->groupBy('answer_1')->get();
You can easily do it with an agregate function with a case expression, since mysql doesnot support native pivoting functions
Following is a sample, and try to rewrite according to your requirement and runt it against database directly, if it works, then you can use it with laravel raw sql.
select id,
sum(case when value = 1 then 1 else 0 end) ANSWER1_COUNT,
sum(case when value = 2 then 1 else 0 end) ANSWER2_COUNT
from survey
group by answer

How to get two sums from a linq query

Summary
I have a list of Transactions. Using Linq, I want to get a sum of the Cost and sum of the Quantity from this list in one query.
Grouping
My first thought is to use grouping - but I don't really have a key that I want to group on, I want just one group with the results from the whole list. So, I happen to have a property called "Parent" that will be the same for all of the transactions, so I'm using that to group on:
var totalCostQuery =
(from t in Transactions
where t.Status != GeneralStoreTransactionStatus.Inactive &&
(t.Type == GeneralStoreTransactionType.Purchase ||
t.Type == GeneralStoreTransactionType.Adjustment)
group t by t.Parent into g
select new
{
TotalCost = g.Sum(t => t.Cost.GetValueOrDefault()),
TotalQuantity = g.Sum(t => t.Quantity.GetValueOrDefault())
});
Grouping by t.Parent seems like it could be wrong. I really don't want to group at all, I just want the sum of t.Quantity and sum of t.Cost.
Is that the correct way to get a sum of two different properties or can it be done in a different way.
Assuming this is Linq to SQL or Entity Framework, you can do that:
var totalCostQuery =
(from t in Transactions
where t.Status != GeneralStoreTransactionStatus.Inactive &&
(t.Type == GeneralStoreTransactionType.Purchase ||
t.Type == GeneralStoreTransactionType.Adjustment)
group t by 1 into g
select new
{
TotalCost = g.Sum(t => t.Cost),
TotalQuantity = g.Sum(t => t.Quantity)
});
Note that you don't need to use GetValueOrDefault, null values will be ignored in the sum.
EDIT: not sure this works with Linq to NHibernate though...
Note that if you're using Linq to objects, the solution above won't be efficient, because it will enumerate each group twice (once for each sum). In that case you can use Aggregate instead:
var transactions =
from t in Transactions
where t.Status != GeneralStoreTransactionStatus.Inactive &&
(t.Type == GeneralStoreTransactionType.Purchase ||
t.Type == GeneralStoreTransactionType.Adjustment)
select t;
var total =
transactions.Aggregate(
new { TotalCost = 0.0, TotalQuantity = 0 },
(acc, t) =>
{
TotalCost = acc.TotalCost + t.Cost.GetValueOrDefault(),
TotalQuantity = acc.TotalQuantity + t.Quantity.GetValueOrDefault(),
});

Aggregate functions with a left outer join in LINQ to Entities

I've been looking through related LINQ questions here trying to figure this one out, but I'm having some trouble converting a SQL query of mine to the equivalent LINQ to Entities version.
select companies.CommpanyName,
job.Position,
count(offers.jobID) As Offered,
job.Openings,
job.Filled
from jobs
left outer join offers on jobs.ID = offers.JobID
join membership.dbo.individuals on jobs.UserID = individuals.ID
join membership.dbo.companies on individuals.CompanyID = companies.ID
where jobs.Hidden = 0
group by offers.JobID,
companies.CommpanyName,
job.Position,
job.Openings,
job.Filled
I've done left outer joins in LINQ before similar to this example but I'm not sure how to combine the count and group statements with this to get the desired result:
CompanyName Position Offered Openings Filled
1 Exmaple Co. Job X 0 2 0
2 Example Co. Job Y 4 6 3
3 Test Co. Job Z 1 1 1
The query is further complicated by the fact that it needs to utilize two separate data contexts. I apologize for the lack of example code, but I'm really not sure how to start this, my LINQ-fu is still weak.
Update:
This is the solution I arrived at with Craig's help, had to use LINQ to Objects because of the unfortunate multiple context setup, JobWithOfferCounts is not an entity object:
IEnumerable<Job> t = context1.JobSet.Include("Offers").Include("Contacts").Where(j => j.Hidden == false);
IEnumerable <JobWithOfferCounts> r = (from j in t
join i in context2.IndividualSet on j.UserID equals i.ID
join c in context2.CompanySet on i.CompanyID equals c.ID
select new JobWithOfferCounts()
{
JobObject = j,
CompanyID = Convert.ToInt32(c.ID),
CompanyName = c.HostName,
OfferCount = j.offers.Count(o => o.Rejected == false),
FilledCount = j.offers.Count(o => o.Accepted == true),
PendingCount = j.offers.Count(o => o.Accepted == false && o.Rejected == false)
});
return r;
I can't see why you have individuals in your query, or why you group by offers.JobID when it (unlike jobs.JobId) could be null, but here's a first stab:
var q = from c in Context.Companies
from i in c.Individuals
from j in i.Jobs
where j.Hidden == 0
select new
{
CompanyName = c.CompanyName,
Position = j.Position,
Offered = j.Offers.Count(),
Openings = j.Openings,
Filled = j.Filled
};
It's rarely correct to use join in LINQ to Entities or LINQ to SQL.

Resources