I have a Customers and an Orders database.
I need to make some statistics for the first order of all new customers and count the number of first orders from new clients by month.`
var date = new DateTime(now.Year - 1, now.Month, 1);
db.Orders
.Where(o => o.Customer.IsNew && o.OrderDate > date)
.GroupBy(o => new { o.OrderDate.Year, o.OrderDate.Month })
.Select(g => new NewCustomerStatsModel {
Month = g.Key.Month,
Year = g.Key.Year,
Count = g.Count()
})
.OrderBy(cs => cs.Year)
.ThenBy(cs => cs.Month)
.ToList();
This query provide me the number of orders for all new client but I need to get only the sum of the first order for each new Customer if the first order date is greater than the provided date.
Is it possible to do it with a query (and how) or am I forced to use AsEnumerable and do it in memory?
I need to make some statistics for the first order of all new customers
var clientFirstOrders = db.Customers.Where(c => c.IsNew)
.Select(c => new{
Customer = c,
FirstOrder = c.Orders.OrderBy(c => c.OrderDate).FirstOrDefault()
})
// might have to do (int?)FirstOrder.Id != null or something like that.
.Where(e => e.FirstOrder != null);
and count the number of first orders from new clients by month.
var clientCountByFirstOrderMonth = clientFirstOrders
.GroupBy(e => new { e.FirstOrder.OrderDate.Year, e.FirstOrder.OrderDate.Month })
.Select(g => new{g.Key.Year, g.Key.Month, Count = g.Count()});
I could find the solution.
With some appropriate index, the performances are pretty good.
It's probably not a perfect solution, but I couldn't update the entities because it's not my Library.
var date = new DateTime(now.Year - 1, now.Month, 1);
var result = db.Orders
.Where(o => o.Customer.IsNew && o.State != OrderState.Cancelled) // get all orders where the Customer is a new one.
.GroupBy(o => o.Customer.Id) // group by customer
.Select(g => g.OrderBy(o => o.OrderDate).FirstOrDefault()) // get the first order for every customer
.Where(o => o.OrderDate > date) // restrict to the given date
.GroupBy(o => new { o.OrderDate.Year, o.OrderDate.Month) }) // then group by month
.Select(g => new NewCustomerStatsModel {
Month = g.Key.Month,
Year = g.Key.Year,
Count = g.Count()
})
.OrderBy(g => g.Year)
.ThenBy(g => g.Month)
.ToList();
I have the following query and is super slow for 3000 records and produces 370 entries. How can I improve performance on it?
dealerResults = _results.GroupBy(x => new { x.DealerName, x.DealerId })
.Select(x => new MarketingReportResults()
{
DealerId = x.Key.DealerId,
DealerName = x.Key.DealerName,
LinkedTotal = linkedLeadCores.Count(y => y.DealerId == x.Key.DealerId),
LeadsTotal = x.Count(),
SalesTotal = x.Count(y => y.IsSold),
Percent = (decimal)(x.Count() * 100) / count,
ActiveTotal = x.Count(y => y.IsActive),
}).ToList();
I think the linkedLeadCores.Count() is the bottleneck here as you loop though the entire linkedLeadCores list each time a entry of _results is processed. This assumption seems to be confirmed by your comments also.
So to remove the bottleneck you could create a map (aka dictionary) that holds the count for each dealer before doing anything with _results like this ...
var linkedLeadCoresCountMap = linkedLeadCores
.GroupBy(y => y.DealerId )
.ToDictionary(y => y.Key, y => y.Count());
... and then you could write
LinkedTotal = linkedLeadCoresCountMap.ContainsKey(x.Key.DealerId) ?
linkedLeadCoresCountMap[x.Key.DealerId] : 0,
Doing a Group Join to linkedLeadCores will use an internal hash table for lookup and should solve your problem.
var dealerResults =
(from r in _results.GroupBy(x => new { x.DealerName, x.DealerId })
join llc in linkedLeadCores on r.Key.DealerId equals llc.DealerId into g
select new MarketingReportResults()
{
DealerId = r.Key.DealerId,
DealerName = r.Key.DealerName,
LinkedTotal = g.Count(),
LeadsTotal = r.Count(),
SalesTotal = r.Count(y => y.IsSold),
Percent = (decimal)(r.Count() * 100) / count,
ActiveTotal = r.Count(y => y.IsActive),
}).ToList();
I having two list or table as per below:
Query:
var q = db.tbl_User_to_CustomerMast
.Where(i => i.fk_Membership_ID == m.MembershipID)
.Join(
db.tbl_CustomerMast,
u => u.fk_Customer_ID,
c => c.CustomerID,
(u, c) => new { UserCustomer = u, Customer = c })
.Where(i => i.UserCustomer.fk_Store_ID == shopid).ToList();
Output:
List A:
User_Customer_ID Name
===================================
1 XYZ
2 ABC
Query:
var rewards = q.Join(
db.tbl_RewardAwardMast,
i => i.UserCustomer.User_Customer_ID,
j => j.fk_Customer_UserID,
(i, j) => new { Customer = i, Reward = j })
.Where(i => i.Reward.RewardDate >= i.Customer.UserCustomer.Membership_Start)
.GroupBy(i => i.Reward.fk_Customer_UserID)
.Select(i => new { CustomerID = i.Key, RewardCount = i.Count()})
.ToList();
Output:
List B:
User_Customer_ID RewardCount
===================================
1 5
Here is final Output Table
User_Customer_ID Name RewardCount
===============================================
1 XYZ 5
2 ABC 0
If I want to check that which user_customer_ID has less than 5 Reward Count, How I will Check:
Query:
var final = q.GroupJoin(
rewards,
i => i.UserCustomer.User_Customer_ID,
j => j.CustomerID,
(i, j) => new { Customer = i, Reward = j.DefaultIfEmpty() })
.Select(i => new { Count = i.Reward, id = i.Customer.UserCustomer.User_Customer_ID })
.ToList();
var final1 = final.Where(i => i.Count < m.MembershipMinVisit.Value).ToList();
Error:
Operator '<' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'int'
You don't need a group join here as for each customer you need a single result (reward). Also because you need only customers with rewards < 5, an inner join using that condition wil give you what you want:
var final = q.Join( // Join instead of GroupJoin
rewards.Where(r => r.RewardCount < 5), // filter out rewards >= 5
i => i.UserCustomer.User_Customer_ID,
j => j.CustomerID,
(i, j) => new { Customer = i, Reward = j })
.Select(i => new {
Reward = i.Reward, // 'Count' is a bad name
// it is still the reward object
id = i.Customer.UserCustomer.User_Customer_ID
})
.ToList();
In your original query, Count (bad name) is a collection (IEnumerable) of awards, that's why you get that error. To fix it, you have to check that the single returned reward is not null (to filter out users without rewards at all, because you use a left join) and that it has a RewardCount less that 5:
var final1 = final.Where(i => i.Count.Single() != null &&
i.Count.Single().RewardCount < 5)
.ToList();
I am looking for some help on adapting the following LINQ query to return all dates within the next 6 months, even those where no records fall within the given month.
var maxDate = DateTime.Now.AddMonths(6);
var orders = (from ord in db.Items
where (ord.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
group ord by new
{
ord.Expiry.Value.Year,
ord.Expiry.Value.Month
}
into g
select new ExpiriesOwnedModel
{
Month = g.Select(n => n.Expiry.Value.Month).First(),
Quantity = g.Count()
}).ToList();
I'd really appreciate any assistance or pointers on how best to implement this.
I'm not sure how well it'll interact with your database, but I'd do this as with a join:
var firstDaysOfMonths = Enumerable.Range(0, 7).Select(i =>
new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i));
var orders = firstDaysOfMonths.GroupJoin(
db.Items,
fd => fd,
ord => new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1),
(fd, ords) => new { Month = fd.Month, Quantity = ords.Count() });
Note you may end up with an extra month where before you didn't (on the first day of the month?)
Stolen from Rawling's answer, if you prefer query syntax for group joins (I do):
var orders =
from month in Enumerable.Range(0, 7)
.Select(i => new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i))
join ord in db.Items
on month equals new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1)
into ords
select new { month.Month, Quantity = ords.Count() };
Alternative if it does not play nice with the database:
var rawGroups = db.Items.Where(item.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
.GroupBy(item => new
{
item.Expiry.Value.Year,
item.Expiry.Value.Month
}, g => new ExpiriesOwnedModel()
{
Month = g.Key.Month,
Quantity = g.Count()
}).ToDictionary(model => model.Month);
var result = Enumerable.Range(DateTime.Now.Month,6)
.Select(i => i > 12 ? i - 12 , i)
.Select(i => rawGroups.Keys.Contains(i) ?
rawGroups[i] :
new ExpiriesOwnedModel()
{ Month = i , Quantity = 0 });
This is going to be a two part question.
I am trying to build a data structure for use with the Google Charts API (specifically, their data table).
Here is my code as it stands now:
return Json.Encode(
RMAs
.Where(r => r.CreatedDate.Year > DateTime.Now.Year - 4) //Only grab the last 4 years worth of RMAs
.GroupBy(r => new { Problem = r.Problem, Year = r.CreatedDate.Year, Quarter = ((r.CreatedDate.Month) / 3) })
.Select(r => new { Problem = r.Key.Problem, Year = r.Key.Year, Quarter = r.Key.Quarter, Count = r.Count() })
);
This gets me very close. This gets me an array similar to the following:
{"Problem":"It broke!","Year":2012,"Quarter":2,"Count":3},
{"Problem":"It broke!","Year":2012,"Quarter":1,"Count":1}
But, what I want is for the data to be grouped further by the "Problem" property so that the quarter is an array for each problem (this makes the data structure much easier to iterate over). An example of the desired structure:
{"Problem":"It broke!",
{"Year":2012,"Quarter":2,"Count":3},
{"Year":2012,"Quarter":1,"Count":1}
},
{"Problem":"Some other problem",
{"Year":2012,"Quarter":1,"Count":31}
}
The second part of the question: How can I ensure that I have data for each quarter (again, this makes it much easier to iterate over for building the data table with the API), even if a "Problem" did not occur in that quarter? Using the same example as last time:
{"Problem":"It broke!",
{"Year":2012,"Quarter":2,"Count":3},
{"Year":2012,"Quarter":1,"Count":1}
},
{"Problem":"Some other problem",
{"Year":2012,"Quarter":2,"Count":0}
{"Year":2012,"Quarter":1,"Count":31}
}
Thanks to Mr. TA for the inspiration and for showing me that you can use LINQ against a grouping.
I have tested this out in a local environment and the LINQ does indeed return a list of Problems tied to an array of Year/Quarter groupings with a total Count. I don't know if Json.Encode encodes it in the correct format though.
The following LINQ should return an anonymous type that fits the format you needed:
Edit: Query now returns count=0 for quarters where at least one problem occurred, but specified problem did not occur
var quarters = RMAs
.Where(rma => rma.CreatedDate.Year > DateTime.Now.Year - 4)
.GroupBy(rma => new {
Year = rma.CreatedDate.Year,
Quarter = ((rma.CreatedDate.Month) / 3)
});
return Json.Encode(
RMAs
//Only grab the last 4 years worth of RMAs
.Where(r => r.CreatedDate.Year > DateTime.Now.Year - 4)
// Group all records by problem
.GroupBy(r => new { Problem = r.Problem })
.Select(grouping => new
{
Problem = grouping.Key.Problem,
Occurrences = quarters.Select(quarter => new
{
Year = quarter.Key.Year,
Quarter = quarter.Key.Quarter,
Count = grouping
.GroupBy(record => new
{
Year = record.CreatedDate.Year,
Quarter = ((record.CreatedDate.Month) / 3)
})
.Where(record =>
record.Key.Year == quarter.Key.Year
&& record.Key.Quarter == quarter.Key.Quarter
).Count()
}).ToArray()
}));
Update: Thanks to JamieSee for updating with example JSON output:
This is an example of the JSON output:
[{"Problem":"P","Occurrences":[{"Year":2012,"Quarter":4,"Count":2},{"Year":2012,"Quarter":2,"Count":1},{"Year":2012,"Quarter":1,"Count":1}]},{"Problem":"Q","Occurrences":[{"Year":2012,"Quarter":3,"Count":1},{"Year":2012,"Quarter":2,"Count":1},{"Year":2012,"Quarter":1,"Count":1}]}]
Add the following to your query:
.GroupBy(x => x.Problem)
.ToDictionary(g => g.Key, g => g.Select(x=>new { Year=x.Year, Quarter=x.Quarter, Count = x.Count }));
You have to insert the following before .ToDictionary() above:
.Select(g =>
new {
Key = g.Key,
Items =
g
.GroupBy(r => r.Year)
.SelectMany(gy =>
gy.Concat(
Enumerable.Range(1,5)
.Where(q => !gy.Any(r=>r.Quarter == q))
.Select(q => new { Problem = g.Key, Year = gy.Key, Quarter = q, Count = 0 })
)
)
}
)
I think... try it out :)
I would advise against following this approach, however, and create "empty" records on the client, to avoid excessive bandwidth use.
Here's the full restatement to meet all your criteria:
public static IEnumerable<DateTime> GetQuarterDates()
{
for (DateTime quarterDate = DateTime.Now.AddYears(-4); quarterDate <= DateTime.Now; quarterDate = quarterDate.AddMonths(3))
{
yield return quarterDate;
}
}
public static void RunSnippet()
{
var RMAs = new[] {
new { Problem = "P", CreatedDate = new DateTime(2012, 6, 2) },
new { Problem = "P", CreatedDate = new DateTime(2011, 12, 7) },
new { Problem = "P", CreatedDate = new DateTime(2011, 12, 8) },
new { Problem = "P", CreatedDate = new DateTime(2011, 8, 1) },
new { Problem = "P", CreatedDate = new DateTime(2011, 4, 1) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 11, 11) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 6, 6) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 3, 3) }
};
var quarters = GetQuarterDates().Select(quarterDate => new { Year = quarterDate.Year, Quarter = Math.Ceiling(quarterDate.Month / 3.0) });
var rmaProblemQuarters = from rma in RMAs
where rma.CreatedDate > DateTime.Now.AddYears(-4)
group rma by rma.Problem into rmaProblems
select new {
Problem = rmaProblems.Key,
Quarters = (from quarter in quarters
join rmaProblem in rmaProblems on quarter equals new { Year = rmaProblem.CreatedDate.Year, Quarter = Math.Ceiling(rmaProblem.CreatedDate.Month / 3.0) } into joinedQuarters
from joinedQuarter in joinedQuarters.DefaultIfEmpty()
select new {
Year = quarter.Year,
Quarter = quarter.Quarter,
Count = joinedQuarters.Count()
})
};
string json = System.Web.Helpers.Json.Encode(rmaProblemQuarters);
Console.WriteLine(json);
}
Which yields:
[{"Problem":"P","Quarters":[{"Year":2008,"Quarter":2,"Count":0},{"Year":2008,"Quarter":3,"Count":0},{"Year":2008,"Quarter":4,"Count":0},{"Year":2009,"Quarter":1,"Count":0},{"Year":2009,"Quarter":2,"Count":0},{"Year":2009,"Quarter":3,"Count":0},{"Year":2009,"Quarter":4,"Count":0},{"Year":2010,"Quarter":1,"Count":0},{"Year":2010,"Quarter":2,"Count":0},{"Year":2010,"Quarter":3,"Count":0},{"Year":2010,"Quarter":4,"Count":0},{"Year":2011,"Quarter":1,"Count":0},{"Year":2011,"Quarter":2,"Count":1},{"Year":2011,"Quarter":3,"Count":1},{"Year":2011,"Quarter":4,"Count":2},{"Year":2011,"Quarter":4,"Count":2},{"Year":2012,"Quarter":1,"Count":0},{"Year":2012,"Quarter":2,"Count":1}]},{"Problem":"Q","Quarters":[{"Year":2008,"Quarter":2,"Count":0},{"Year":2008,"Quarter":3,"Count":0},{"Year":2008,"Quarter":4,"Count":0},{"Year":2009,"Quarter":1,"Count":0},{"Year":2009,"Quarter":2,"Count":0},{"Year":2009,"Quarter":3,"Count":0},{"Year":2009,"Quarter":4,"Count":0},{"Year":2010,"Quarter":1,"Count":0},{"Year":2010,"Quarter":2,"Count":0},{"Year":2010,"Quarter":3,"Count":0},{"Year":2010,"Quarter":4,"Count":0},{"Year":2011,"Quarter":1,"Count":1},{"Year":2011,"Quarter":2,"Count":1},{"Year":2011,"Quarter":3,"Count":0},{"Year":2011,"Quarter":4,"Count":1},{"Year":2012,"Quarter":1,"Count":0},{"Year":2012,"Quarter":2,"Count":0}]}]