Linq groupby and sum - c#

Basically I have a class with two proprties - ParentProduct and NumberOfOrders. I want to select the distinct parent product, ordered by the total number of orders so I used the following Linq:
var list = List.GroupBy(t => t.ParentProduct)
.Select(group => new { Product = group.Key, NumberOfOrders = group.Sum(t => t.NumberOfOrders) })
.OrderBy(o => o.NumberOfOrders)
.Select(o => o.Product);
Where List is a list of all child products
t is the child product that contains the number of orders and the parent product
o is the new object created
I was wondering if there was a better way to get a distinct list of parent products that is ordered by the sum of orders as the above seems to be doing a lot of selects.

You could remove the middle select and just order the groups by the sums and then select the product.
var list = List.GroupBy(t => t.ParentProduct)
.OrderBy(group => group.Sum(t => t.NumberOfOrders))
.Select(group => group.Key);

Related

Order by and group by and sum using SQL

What I am trying to do is get the top 10 most sold Vegetables by grouping them by an Id passed by parameter in a function and ordering them by the sum of their Quantity. I don't know how to use SUM or (total) quite yet but I thought I'd post it here seeking help. If you need me offering you anything else I will be ready.
This is my code:
TheVegLinQDataContext db = new TheVegLinQDataContext();
var query =db.OrderDetails.GroupBy(p => p.VegID)
.Select(g => g.OrderByDescending(p => p.Quantity)
.FirstOrDefault()).Take(10);
And this is an image of my database diagram
Group orders by Vegetable ID, then from each group select data you want and total quantity:
var query = db.OrderDetails
.GroupBy(od => od.VegID)
.Select(g => new {
VegID = g.Key,
Vegetable = g.First().Vegetable, // if you have navigation property
Total = g.Sum(od => od.Quantity)
})
.OrderByDescending(x => x.Total)
.Select(x => x.Vegetable) // remove if you want totals
.Take(10);
Since this is not clear that you are passing what type of id as function parameter, I'm assuming you are passing orderId as parameter.
First apply where conditions then group the result set after that order by Total sold Quantity then apply Take
LINQ query
var result = (from a in orderdetails
where a.OrderId == orderId //apply where condition as per your needs
group a by new { a.VegId } into group1
select new
{
group1.Key.VegId,
TotalQuantity = group1.Sum(x => x.Quantity),
group1.FirstOrDefault().Vegitable
}).OrderByDescending(a => a.TotalQuantity).Take(10);
Lamda (Method) Syntax
var result1 = orderdetails
//.Where(a => a.OrderId == 1) or just remove where if you don't need to filter
.GroupBy(x => x.VegId)
.Select(x => new
{
VegId = x.Key,
x.FirstOrDefault().Vegitable,
TotalQuantity = x.Sum(a => a.Quantity)
}).OrderByDescending(x => x.TotalQuantity).Take(10);

Eliminate duplicates in the List and sum up the quantity field

I have a List consisting of ID and quantity. IDs are repeated with different quantities. I want to remove the duplicate IDs and add the quanties.
Eg: ID1 :4
ID2 :5
ID1 :3
I want this as ID1:7
ID2:5
I tried with LINQ. I am able to delete the duplicate but not add the quantity.
List<PrintDetails> myList = printDetailsList
.GroupBy(s => s.MasterID)
.Select(grp => grp.FirstOrDefault())
.OrderBy(s => s.Quantity)
.ToList();
You're almost there, you just need to return a new object with the sums:
List<PrintDetails> myList = printDetailsList
.GroupBy(s => s.MasterID)
.Select(grp => new PrintDetails() { MasterID = grp.Key, Quantity = grp.Sum(s => s.Quantity) }) // create a new object with the Id and quantity by sum
.OrderByDescending(s => s.Quantity) // based on the example in the question you actually want to order from highest quantity to lowest
.ToList();
When you group a list, you get a grouping that contains a Key and is an enumerable of the items. So you can use the key as the masterId (since that's what it is) and then use the enumerable to sum quantities.
I also fixed your OrderBy since it seemed to be the wrong way around for your example.
Try it online

Linq - Group By Id, Order By and Then select top 5 of each grouping

Is there some way in linq group By Id, Order By descending and then select top 5 of each grouping? Right now I have some code shown below, but I used .Take(5) and it obviously selects the top 5 regardless of grouping.
Items = list.GroupBy(x => x.Id)
.Select(x => x.OrderByDescending(y => y.Value))
.Select(y => new Home.SubModels.Item {
Name= y.FirstOrDefault().Name,
Value = y.FirstOrDefault().Value,
Id = y.FirstOrDefault().Id
})
You are almost there. Use Take in the Select statement:
var items = list.GroupBy(x => x.Id)
//For each IGrouping - order nested items and take 5 of them
.Select(x => x.OrderByDescending(y => y.Value).Take(5))
This will return an IEnumerable<IEnumerable<T>>. If you want it flattened replace Select with SelectMany

c# linq join with projection

So in my shop app i'm allowing users to favorite items.
what i wish to do is to show the list of favorite items in the user profile page where the list is sorted on the like date in a descending order.
We have 2 tables that we need to join, one is items and the other one is favorites.
how would one join this two tables, so the result will answer this criteria:
The result will be a list of items that was favorite by this particular user.
The results will come with the list of comments for each item (each item have a list of comments).
The results will be sorted correctly.
So far i came up with this:
Items =
await _context.Favorites
.Join(
_context.Items,
f => f.ItemId,
i => i.Id,
(f, i) => new { f, i })
.Distinct()
.OrderByDescending(x => x.f.FavDate)
.Select(x => x.i)
.Skip(skip).Take(take)
.Include(c => c.ListOfComments)
.ToListAsync();
This works but does not answer the first criteria, which is that only items favorite by particular user will be returned, this returns list of items favorite by the users and not by a particular user.
I tried to add a where clause before the join (_context.Favorites.Where(f.UserVoterId.equals(profileId)) but it throws an exception.
One way to approach this is to:
include the user id as the join key and
load the data in separate steps
To select the favorite items of a specific user (profileId) you need this query:
var favorites = _context.Favorites.OrderByDescending(f => f.FavDate)
.Join(_context.Items,
fav => new { fav.ItemId, UserId = fav.UserVoterId },
item => new { ItemId = item.Id, UserId = profileId },
(fav, item) => item)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToList();
And to load the comments just try one of the following (whichever works):
var itemIds = favorites.Select(f => f.Id);
var comments = _context.Comments.Where(c => itemIds.Contains(c.ItemId))
.GroupBy(c => c.ItemId)
.ToDictionary(g => g.Key, g => g.ToArray());
Or
var items = _context.Comments
.GroupJoin(favorites,
comment => comment.ItemId,
favorite => favorite.Id,
(fav, comments) => new
{
Item = fav,
Comments = comment.ToArray()
});
In the first case, the comments are added to a Dictionary<TItemId, Comment> where TItemId is the type of item.Id and to get the comments for an item you'd use
var itemComments = comments[item.Id];
which is a O(1) operation.
In the second case the items collection will have all the data you need so you'll have to project it into the structure that suits your needs.
NB I mentioned earlier whichever works because I'm not entirely sure that GroupJoin is properly translated to SQL and I'm not sure if I missed some requirements for the GroupJoin method.

Query to select worst-selling products

I have these 3 tables/classes in the Entity Framework model:
Orders (OrderedItems, OrderTime)
OrderedItems (Product, Qty)
Products (Name, CreatedTime)
I can select the best-selling products by doing this
var items = Orders.SelectMany(o => o.OrderedItems);
var products = items.GroupBy(oi => oi.Product)
.OrderByDescending(g => g.Sum(oi => oi.Qty))
.Select(g => g.Key).Take(10);
How do I select the worst-performing products (ie. the opposite)?
Note: Worst-performing products may not exists in the Orders table because they may never be ordered.
You can start with the Products table and find matches from there. One way is to use the join into clause to group join the Products table with the OrderedItems:
var items = Orders.SelectMany(o => o.OrderedItems);
var products = (from product in Products
join item in items on product equals item.Product into matchingItems
orderby matchingItems.Sum(oi => oi.Qty)
select product).Take(10);
Another way, which is probably less efficient but you might find more readable, is to filter the items using Enumerable.Where():
var items = Orders.SelectMany(o => o.OrderedItems);
var products = (from product in Products
orderby items.Where(oi => oi.Product == product).Sum(oi => oi.Qty)
select product).Take(10);
This translates nicely into method syntax:
var items = Orders.SelectMany(o => o.OrderedItems);
var products = Products.OrderBy(p => items.Where(oi => oi.Product == product)
.Sum(oi => oi.Qty))
.Take(10);
var items = Orders.SelectMany(o => o.OrderedItems);
var products = items.GroupBy(oi => oi.Product)
.OrderBy(g => g.Sum(oi => oi.Qty))
.Select(g => g.Key).Take(10);
Try
OrderBy(g => g.Sum(oi => oi.Qty))
it will put minimum sums first.
Just change OrderByDescending to OrderBy and it should order them in ascending order.
It seems you need a left outer join from Products to OrderedItems. productsQty should contain a list of Products with Qty from OrderedItems or 0 if there was none.
var productQty = from p in Products
join oi in OrderedItems on p equals oi.Product into joined
from poi in joined.DefaultIfEmpty()
select new { Product = p, Qty = poi == null ? 0 : poi.Qty };
var products = productQty.GroupBy(p => p.Product).OrderBy(g => g.Sum(oi => oi.Qty)).Take(10);
UPDATE: Query corrected.

Categories