I've been stuck with this problem for a few days now.
How do I group by PartCode, take the Max/Min or First value of the Description (items with the same partcode may have a different description), and then sum of the other fields. What I currently have is shown below:
var lengths =
from q in
(from p in partList
select new { p.PartCode, p.Description, p.Quantity, p.TotalMeter, p.PurchaseLength })
group q by new { q.PartCode, q.Description, q.Quantity, q.TotalMeter, q.PurchaseLength } into g
orderby g.Key.PartCode
select new
{
PartCode = g.Max(p => p.PartCode),
Description = g.Max(p => p.Description),
Quantity = g.Sum(p => p.Quantity),
TotalMeter = g.Sum(p => p.TotalMeter),
PurchaseLength = g.Sum(p => p.PurchaseLength)
};
I see two main problems here:
The first is that you say you want to group by PartCode, but instead you're grouping by a combination of everything.
Another possible problem is that you're using Max() on what I can only assume to be collections of strings. This won't fail, but it will select the value that is last in alphabetical order (is that what you want?).
Try this:
var lengths =
from q in
(from p in partList
select new { p.PartCode, p.Description, p.Quantity,
p.TotalMeter, p.PurchaseLength })
group q by q.PartCode into g
orderby g.Key
select new
{
PartCode = g.First().PartCode,
Description = g.First().Description,
Quantity = g.Sum(p => p.Quantity),
TotalMeter = g.Sum(p => p.TotalMeter),
PurchaseLength = g.Sum(p => p.PurchaseLength)
};
If that doesn't solve your issue please tell us what your issue is.
Related
I have two tables:
PractitionerSkill { Id, Title }
PractitionerInSkills { Id, PractitionerSkillId ), where PractitionerSkillId is FK into PractitionerSkill
(there are more columns but that is not really important)
And I'm trying to count number of skills pr practitioner.
Using LINQ method syntax, I am trying to do this:
SELECT
S.Id, S.Title, COUNT(*) as [Count] from
PractitionerSkills S INNER JOIN
PractitionerInskills PIS ON S.ID = PIS.PractitionerSkillId
GROUP BY
S.Id, S.Title
ORDER BY
S.Title
Easy in SQL. Notice that I'm getting the ID, title and count in the result.
My current efforts (which is not even method syntax)
var query = from skill in _context.PractitionerSkills
join pis in _context.PractitionerInSkills on skill.Id equals pis.PractitionerSkillId into grp
select new
{
Title = skill.Title,
Count = grp.Count()
};
which is almost there, but I can't get more columns out. I need the Skill.Id (or PractitionerInSkills.PractitionerSkillId)
It's easy in Linq too!
var query = _context.PractitionerSkills.Join(_context.PractitionerInSkills,
ps => new { k1 = ps.Id },
pis => new { k1 = pis.PractitionerSkillId },
(ps, pis) => new { ps.Id, ps.Title })
.GroupBy(r => new { r.Id, r.Title })
.Select(g => new { g.Key.Id, g.Key.Title, Count = g.Count() })
.OrderBy(r => r.Title);
How can I join two tables to get the following results in the pictures below? I want to get the counts of the project names.
I know I can group by Project ID:
var TotalSupportsByProject =
from s in db.MySupportContext
.GroupBy(s => s.ProjectID)
.Select(g => new { ProjectId = g.Key, Total = g.Count() })
select s;
Join and group. It appears you are trying to get the counts of support for each of the projects.
var query =
from s in db.Support
join p in db.Project on s.ProjectId equals p.ProjectId
group 1 by p.ProjectName into g
select new
{
ProjectName = g.Key,
TotalSupport = g.Count(),
};
If you need to be able to include counts of projects with no support, some adjustments would have to be made. Assuming the Project table contains a all of the projects you need:
var query =
from p in db.Project
select new
{
p.ProjectName,
TotalSupport = db.Support.Count(s => s.ProjectId == p.ProjectId),
};
I have the following table (Records):
RecordID int,
Nickname nvarchar(max),
DateAdded datetime
I need group by max count of records for Nickname. I made it:
var users = (from i in db.Records
where i.Form.CompetitionID == cID
group i by i.Nickname into g
orderby g.Count() descending
select new TopUserModel()
{
Nickname = g.Key,
Position = g.Count()
}).Take(100).ToList();
it works
Right now I need to sort it by date too (who first got max records).
I should have a request like it:
select Nickname, Count(*) as Result, MAX(DateAdded) as MDate from Records group by Nickname order by Result Desc, MDate Asc
How to do it by LINQ?
I think this is what you want. I've used extension version of Linq which is probably more easier. The idea is to calculate MaxCount and MaxDate after GroupBy so you can use it in next OrderBy clauses.
db.Records
.Where(i => i.Form.CompetitionID == cID)
.GroupBy(i => i.Nickname)
.Select(g => new { MaxCount = g.Count(), MaxDate = g.Max(i => i.DateAdded), Nickname = g.Key})
.OrderByDescending(gx => gx.MaxCount)
.ThenByDescending(gx => gx.MaxDate)
.Select(gx => new TopUserModel()
{
Nickname = gx.Nickname,
Position = gx.MaxCount
}).Take(100).ToList();
I think what you're asking for is:
...
select new TopUserModel()
{
Nickname = g.Key,
Position = g.Count()
Date = g.Max(r => r.DateAdded)
}).Take(100).OrderByDescending(t => t.Position).ThenBy(t => t.Date).ToList();
When you use group the Key is your grouping but the enumerable is all the records you've grouped so you can still use aggregate functions on them.
If you want to sort by multiple columns, you can put them in order using the chaining above.
var users = (from i in db.Records
where i.Form.CompetitionID == cID
group i by new {i.Nickname} into g
orderby g.Count() descending,
select new TopUserModel()
{
Nickname = g.Key,
Position = g.Count()
Date = g.Max(r => r.DateAdded)
}).Take(100
).OrderBy(c => c.Date).ToList();
Just add max date for nickname to ordering. You also can introduce new range variable for position:
var users = (from r in db.Records
where r.Form.CompetitionID == cID
group r by r.Nickname into g
let position = g.Count()
orderby position descending, g.Max(r => r.DateAdded)
select new TopUserModel {
Nickname = g.Key,
Position = position
}).Take(100).ToList();
Question is already answered here: OrderBy a date field with linq and return ToList()
Add at the end of your users statement
.OrderBy(e => e.Date).ToList();
I have a doubt about the object IGrouping that results from a linq where I use a "group by" sentence.
I have two tables in the database, Products and Responses they have a relationship 1 to *. In the Responses table we have a column called FinalRate which is the rate of the product. The products can have n responses or rates.
I want to get the Products order by the sum of the FinalRate divided by the number of rates done. That is to say, order by the average rate descending from higher to lower marks.
As it can be read in the code (at the end of the question), I try to get the responses first. To sum all the finalrates and divide them by the count I use a group.
There are 2 problems with the code, even if the current code works:
1.-I tried to get the Products in a single query but it is impossible because I can not use the products table in the group and then use the Response table in the "orderby". One more thing LINQ only gives you the possibility to group one table, it is imposible to have "group prod, response".
I couldn't get this sql sentence in LINQ:
select prod.ProductID,prod.Commercial_Product_Name,prod.Manufacturer_Name,
prod.ProductImageUrl
from rev_product prod
inner join rev_response res on res.AtProductid=prod.ProductID
group by prod.ProductID,prod.Commercial_Product_Name,prod.Manufacturer_Name
,prod.ProductImageUrl
order by (sum(res.FinalRate)/count(res.AtProductid))
I tried this:
var gruposproductos = (from prod in ctx.Products
join res in ctx.Responses on prod.ProductID equals res.AtProductId
group prod by prod.ProductID into g
orderby (g.Sum(ra =>ra.FinalRate)/g.Count())
descending select g).Take(2);
But as I say, the "orderby (g.Sum..." gives an error, because "into g" groups the Product table, not the Response Table.
So this is why in my final code I don't get the products in the same LINQ sentence.
2.-Once accepted this fact, the problem is that I get an IGrouping, but I don't obtain a list of Responses that I can iterate without doing the two foreach in the code. I wanted only one loop, as one would do if you had a "List" object.
It is not really a cool method but it works. Moreover, I have to control that in the second loop there is only added 1 time.
Any better code?
var groupproducts = (from res in ctx.Responses
group res by res.AtProductId into g
orderby (g.Sum(ra =>ra.FinalRate)/g.Count())
descending select g).Take(2).ToList();
List<Product> theproducts = new List<Product>();
foreach (var groupresponse in groupproducts)
{
foreach (var response in groupresponse)
{
var producttemp= (from prod in ctx.Products
where prod.ProductID == response.AtProductId
select prod).First();
theproducts.Add(producttemp);
}
}
}
FINAL SOLUTION (thx a lot #Daniel)
var productsanonymtype = ctx.Products.Select(x => new
{
Product = x,
AverageRating = x.Responses.Count() == 0 ? 0 : x.Responses.Select(r => (double)r.FinalRate).Sum() / x.Responses.Count()
}).OrderByDescending(x => x.AverageRating);
List<Product> products = new List<Product>();
foreach (var prod in productsanonymtype)
{
products.Add(prod.Product);
}
Try this:
products.Select(x => new
{
Product = x,
AverageRating = x.Responses.Sum(x => x.FinalRate) /
x.Responses.Count()
});
The Sum overload I am using is not implemented in all providers. If that's a problem for you, you can use this alternate version:
products.Select(x => new
{
Product = x,
AverageRating = x.Responses.Select(x => x.FinalRate)
.Sum() /
x.Responses.Count()
});
If there is no navigation property from product to its responses you should first try to fix that. If you can't you can use this version:
products.Join(responses, x => x.Id, x => x.ProductId,
(p, r) => new { Product = p, Response = r })
.GroupBy(x => x.Product)
.Select(g => new { Product = g.Key,
AverageRating = g.Select(x => x.Response.FinalRate)
.Sum() /
g.Count()
});
Assuming FinalRate is an int, both methods will calculate the average rating with an int, i.e. there will be no 4.5 rating. And there will be no rounding, i.e. an actual average rating of 4.9 will result in 4. You can fix that by casting one of the operands of the division to double.
Another problem is the case with no ratings so far. The code above will result in an exception in this case. If that's a problem for you, you can change the calculation to this:
AverageRating = g.Count() == 0
? 0
: g.Select(x => (double)x.Response.FinalRate).Sum() / g.Count()
ctx.Products.GroupBy(x => new {
ProductId = x.ProductId,
FinalRate = x.Responses.Sum(y => y.FinalRate),
CountProductId = x.Responses.Count
})
.OrderBy(x => x.Key.FinalRate / x.Key.CountProductId);
And here with the projection.....
ctx.Products.Select(x => new {
ProductID = x.ProductID,
Commercial_Product_Name = x.Commercial_Product_Name,
Manufacturer_Name = x.Manufacturer_Name,
ProductImageUrl = x.ProductImageUrl,
FinalRate = x.Responses.Sum(y => y.FinalRate),
CountProductId = x.Responses.Count
})
.GroupBy(x => new {
ProductId = x.ProductId,
FinalRate = x.FinalRate,
CountProductId = x.CountProductId
})
.OrderBy(x => x.Key.FinalRate / x.Key.CountProductId);
I am looking for a solution to have all the content of the table PART (by adding a right/left join I suppose) in the following LINQ query :
var query = (from p in db.PARTS
join oc in db.OUTPUT_CONTROLS on p.id equals oc.partid
join f in db.FCT on p.fct equals f.id
select new
{ p.id, p.plant, p.unit, p.type, p.num, f.name, oc.datetime, oc.ncr }
into x
group x by new
{ x.id, x.plant, x.unit, x.type, x.num, x.name }
into g
select new
{ g.Key.id, g.Key.plant, g.Key.unit, g.Key.type, g.Key.num, g.Key.name, startdate = g.Min(oc => oc.datetime), endate = g.Max(oc => oc.datetime), sumncr = g.Sum(oc => oc.ncr) })
.OrderByDescending(oc => oc.startdate);
Thanks
I found the solution on my own thanks to this post : LINQ Left Join And Right Join
The solution :
var query = (from p in db.PARTS
join oc in db.OUTPUT_CONTROLS on p.id equals oc.partid into joined
join f in db.FCT on p.fct equals f.id
from j in joined.DefaultIfEmpty()
select new
{ p.id, p.plant, p.unit, p.type, p.num, f.name, j.datetime, j.ncr } into x
group x by new { x.id, x.plant, x.unit, x.type, x.num, x.name } into g
select new { g.Key.id, g.Key.plant, g.Key.unit, g.Key.type, g.Key.num, g.Key.name, startdate = g.Min(oc => oc.datetime), endate = g.Max(oc => oc.datetime), sumncr = g.Sum(oc => oc.ncr) })
.OrderByDescending(oc => oc.startdate);
If you have a SQL where you see a join followed by a GroupJoin, consider using the LINQ GroupJoin.
Quite often you'll see this in situations where you want "Schools with their Students", "Customers with their Orders", "Zoos with their Animals"
It seems that you have 3 tables: Parts, OutputControls and Fcts.
Every Part has zero or more OutputControls, and every OutputControl belongs to exactly one Part, using foreign key PartId: a straightforward one-to-many relation
A Part has a foreign key FctId, that points to the Fct of the part.
You want (some properties of) the Parts, with their OutputControls and its Fct
var result = parts.GroupJoin(outputControls, // GroupJoin Parts and OutputControls
part => part.Id, // from every part take the Id
outputControl => outputControl.PartId, // from every outputControl take the PartId
// result selector: when these Ids match,
// use the part and all its matching outputControls to make one new object:
(part, outputControlsOfThisPart) => new
{
// select the part properties you plan to use:
Id = part.id,
Plant = part.plant,
Unit = part.unit
// the output controls of this part:
OutputControls = outputControlsOfThisPart.Select(outputControl => new
{
// again, select only the output control properties you plan to use
Id = outputControl.Id,
Name = outputControl.Name,
...
})
.ToList(),
// For the Fct, take the Fct with Id equal to Part.FctId
Fct = Fcts.Where(fct => fct.Id == part.Fct)
.Select(fct => new
{
// select only the Fct properties you plan to use
Id = fct.Id,
Name = fct.Name,
...
})
// expect only one such Fct
.FirstOrDefault(),
});