How do I get the max of each group? - c#

Consider the following LINQ statement:
var posts = db.Posts
.Where(p => p.Votes.Count > 0 && p.User.Confirmed)
.Select(p => new
{
PostId = p.PostId,
Votes = p.Votes.Count(),
Hours = EntityFunctions.DiffHours(DateTime.UtcNow, p.Timestamp)
})
.Select(p1 => new
{
PostId = p1.PostId,
Votes = p1.Votes,
Group = p1.Hours <= 24 ? 24 :
p1.Hours <= 168 ? 168 :
p1.Hours <= 720 ? 720 : 0
})
.Where(p2 => p2.Group != 0);
It successfully groups a listing of posts into their respective groups: 24 hours, 168 hours, and 720 hours.
However, now I need to get the PostId that has the Max Votes for each group. How do I do that?

var postIds = posts.OrderByDescending(x => x.PostId).GroupBy(x => x.Group)
.Select(x => x.First().PostId);
Or, for a bit more clarity (IMHO), and (I think) less speed:
var postIds = posts.GroupBy(x => x.Group).Select(g => g.Max(p => p.PostId));
The former has the benefit that if you want the post, and not just the PostId, you have that available more easily.

I was looking at this but kind of slow. It's a little different syntax so I'll post it anyway
var groups = (from p in posts
group p by p.Group into g
select new
{
Id = g.Max(p => p.Id),
Group = g.Key
}).ToList();
var bestPosts = (from p in posts
join j in groups on new {p.Group, p.Votes} equals new {j.Group, j.Votes}
select p).ToList();

Groups according to "GroupByField" and selects the max.
var query = from o in _context.Objects
group o by o.GroupByField
into group
select new
{
maxParameter = (from o in group orderby o.OrderByField select o).Last()
};
and then in order to select the original (max) objects
var largest = query.Select(q => q.maxParameter).ToList();

Related

LINQ Query with GroupBy, MAX and Count

What could be the LINQ query for this SQL?
SELECT PartId, BSId,
COUNT(PartId), MAX(EffectiveDateUtc)
FROM PartCostConfig (NOLOCK)
GROUP BY PartId, BSId
HAVING COUNT(PartId) > 1
I am actually grouping by two columns and trying to retrieve max EffectiveDateUtc for each part.
This is what I could write. Stuck up on pulling the top record based on the date.
Also not sure, if this is a optimal one.
//Get all the parts which have more than ONE active record with the pat
//effective date and for the same BSId
var filters = (from p in configs
?.GroupBy(w => new
{
w.PartId,
w.BSId
})
?.Select(g => new
{
PartId = g.Key.PartId,
BSId = g.Key.BSId,
Count = g.Count()
})
?.Where(y => y.Count > 1)
select p)
?.Distinct()?.ToList();
var filteredData = (from p in configs
join f in filters on p.PartId equals f.PartId
select new Config
{
Id = p.Id,
PartId = p.PartId,
BSId = p.BSId,
//EffectiveDateUtc = MAX(??)
}).OrderByDescending(x => x.EffectiveDateUtc).GroupBy(g => new { g.PartId, g.BSId }).ToList();
NOTE: I need the top record (based on date) for each part. Was trying to see if I can avoid for loop.
The equivalent query would be:
var query =
from p in db.PartCostConfig
group p by new { p.PartId, p.BSId } into g
let count = g.Count()
where count > 1
select new
{
g.Key.PartId,
g.Key.BSId,
Count = count,
EffectiveDate = g.Max(x => x.EffectiveDateUtc),
};
If I understand well, you are trying to achieve something like this:
var query=configs.GroupBy(w => new{ w.PartId, w.BSId})
.Where(g=>g.Count()>1)
.Select(g=>new
{
g.Key.PartId,
g.Key.BSId,
Count = g.Count(),
EffectiveDate = g.Max(x => x.EffectiveDateUtc)
});

GroupBy performs slowly

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();

Group by and MIN() in LINQ

Trying to convert the below SQL query to LINQ, but I'm stuck at grouping by ClientCompany.
SELECT TOP 300 ClientCompany,
CASE WHEN MIN(FeatureID) = 12 THEN 1 ELSE 0 END as Sort
FROM Ad
LEFT JOIN AdFeature
ON Ad.ID = AdFeature.AdID
WHERE (AdFeature.FeatureID = 13 OR AdFeature.FeatureID = 12)
AND SiteID = 2
GROUP BY ClientCompany
ORDER BY Sort DESC
My attempt to convert this to LINQ:
(from a in Ads
join af in AdFeatures
on new {
join1 = a.ID,
join3 = 2
} equals new {
join1 = af.AdID,
join3 = af.SiteID
}
let sort = (
af.FeatureID == 12 ? 1 : 0
)
orderby sort descending
where af.FeatureID == 13 || af.FeatureID == 12
select new { a.ClientCompany, sort } ).Take(300)
How would I use MIN(FeatureID) and GROUP BY ClientCompany in LINQ, so that I only get a single row per ClientCompany back?
EDIT
This worked! Based on Daniel Hilgarth's answer. Is there anything that can go horribly wrong with this solution?
Ads.Join(AdFeatures, x => x.ID, x => x.AdID,
(a, af) => new { Ad = a, AdFeature = af })
.Where(x => x.AdFeature.FeatureID == 12 || x.AdFeature.FeatureID == 13)
.Where(x => x.AdFeature.SiteID == 2)
.GroupBy(x => x.Ad.ClientCompany)
.Select(g => new { ClientCompany = g.Key, Sort = g.Min(x => x.AdFeature.FeatureID) == 12 ? 1 : 0 })
.OrderByDescending(x => x.Sort)
.Take(300)
Try this:
Ads.Join(AdFeatures, x => x.FeatureID, x => x.FeatureID,
(a, af) => new { Ad = a, AdFeature = af })
.Where(x => x.AdFeature.FeatureID == 12 || x.AdFeature.FeatureID == 13)
.Where(x => x.AdFeature.SiteID == 2)
.GroupBy(x => x.Ad.ClientCompany)
.Select(g => new { ClientCompany = g.Key,
Sort = g.Min(x => x.AdFeature.FeatureID) == 12 ? 1 : 0 });
Please note, I changed the left outer join into an inner join, because your original query accesses AdFeature unconditionally, making it effectively an inner join .
hi I would write it like that
context.Ads.Where(ad => ad.AdFeatures.Any(feature => (feature.FeatureID == 13 || feature.FeatureID == 12) && feature.SiteID == 2))
.GroupBy(ad => ad.ClientCompany)
.Select(ads => new
{
cc = ads.Key, sort = ads.SelectMany(ad => ad.AdFeatures)
.Select(feature => feature.FeatureID)
.Min() == 12
})
.OrderBy(arg => arg.sort).Take(300);
Try this:
(from a in ads
join af in AdFeatures on a.ID equals af.AdID into g
from x in g.DefaultIfEmpty()
where x.FeatureID == 13 || x.FeatureID == 12
where x.SiteID == 2
orderby a.Sort descending
group a by a.ClientCompany into g2
from x2 in g2
let sort = g2.Select(T => T.FeatureID).Min() == 12 ? 1 : 0
select new { a.ClientCompany, Sort = sort }).Take(300);
Why do you need grouping anyway?

Linq DefaultValues on Join

I'm trying to get the total download count of my app for the last 30 days on different devices, i succeeded of returning the right query, by grouping by the number of days and joinning with an enumerable with the last thirty days. However I'm not able to format the output as i want. Let me share the query first with the presentation in LinqPad
var last_days = (from idx in Enumerable.Range(1, (DateTime.Now - DateTime.Now.AddDays(-30)).Days)
select new { day = DateTime.Now.AddDays(-30).AddDays(idx).Date});
var orders = (from od in Orders
group od by EntityFunctions.AddSeconds((DateTime?)new DateTime(1970,1,1,0,0,0,0), (int?)od.Created) into g
select new {
day = g.Key ,
web = g.Where( q => q.Source == "web").Count(),
ios = g.Where( q => q.Source == "ios").Count(),
android = g.Where( q => q.Source == "android").Count(),
total = g.Count()
}).OrderByDescending(q => q.day).Take(31);
var days=
(from d in last_days
join od in orders on d.day equals od.day into x
from od in x.DefaultIfEmpty()
select x );
days.Dump();
This is the result I get
Now, I want to format the final output to an IEnumerable of 5 columns(day, web, ios, android, total) regardless whether it was empty or not. So instead of the empty O sign, I get the date, and the web = ios = android = total = 0. How can I do this?
So on day without any downloads, I still get an entry with the date and platforms to 0.
This is hardly the most elegant solution, but something like this should work:
var days = last_days.Select(d =>
orders.DefaultIfEmpty(new {
day = d,
web = 0,
ios = 0,
android = 0,
total = 0
}).FirstOrDefault(od =>
od.day == d.Date));
The basic idea is to tell the generator what to fall back on, in each case, if an appropriate order entry cannot be found.
In retrospect, it's probably easier to start from a blank slate. What about something more like:
var last_30_days =
from idx in Enumerable.Range(1, 30)
orderby idx descending
select DateTime.Now.AddDays(idx - 30).Date;
var orders =
from date in last_30_days
let datesOrders = Orders.Where(order => order.Created == date)
select new Info()
{
Date = date,
Web = datesOrders.Where(q => q.Source == "web").Count(),
iOS = datesOrders.Where(q => q.Source == "ios").Count(),
Android = datesOrders.Where(q => q.Source == "android").Count(),
Total = datesOrders.Count()
};
What about selecting new DaySum { ord = (ord == null ? 0 : ord.Count) };
Instead of just
Select x
Got it right, generated sql is okay in complexity, was a bit rusty on linq i guess
var last_days = (from idx in Enumerable.Range(1, (DateTime.Now - DateTime.Now.AddDays(-31)).Days)
select new { day = DateTime.Now.AddDays(-31).AddDays(idx).Date});
var orders = (from od in Orders
where od.ServiceProviderID == 2
group od by new DateTime(1970,1,1,0,0,0,0).AddSeconds(od.Created).Date into g
select new {
day = (DateTime)g.Key ,
web = g.Where( q => q.Source == "web").Count(),
ios = g.Where( q => q.Source == "ios").Count(),
android = g.Where( q => q.Source == "android").Count(),
total = g.Count()
}).OrderByDescending(q => q.day).Take(32);
var days = (from d in last_days
join od in orders on d.day.Date equals od.day.Date into x
from od in x.DefaultIfEmpty()
select new {
day = d.day ,
web = (od == null) ? 0: od.web,
ios = (od == null) ? 0: od.ios,
android = (od == null) ? 0: od.android,
total = (od == null) ? 0 : od.total
} );
days.Dump();

LINQ query to group records by month within a period

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 });

Categories

Resources