I have a products table like this:-
ProductID ProductName Price
1 Milk 10
2 Banana 20
3 Apple 15
1 Grapes 12
2 Banana 25
1 Milk 8
I want to find the products who have maximum price for each productId.
Sample Output:-
ProductID ProductName Price
1 Grapes 12
2 Banana 25
3 Apple 15
I have tried this query:-
List<Product> groups = products
.GroupBy(p => p.ProductId)
.Select(p => new Product
{
ProductId = p.Key,
Name = p.OrderByDescending(o => o.Price).First().Name,
Price = p.OrderByDescending(o => o.Price).First().Price,
})
.ToList();
This query is working fine, but my question is should i use OrderByDescending twice? I mean since i just want single item based on 1 property and suppose there are multiple other properties, so do i need to use same logic again and again?
Edit:
Pardon me forgot to mention, Please Assume ProductName can be different, Please check updated tables.
No, you don't have to, you can just select First():
new Product
{
ProductId = p.Key,
Name = p.First().Name,
Bar = p.First().Bar,
Price = p.OrderByDescending(o => o.Price).First().Price,
}
This of course assumes all products with a given ProductId have the same Name and Bar.
If this isn't the case and you want to map all properties from the selected entity, create a code block in your Select():
.Select(p =>
{
var havingHighestPrice = p.OrderByDescending(o => o.Price).First()
return new Product
{
ProductId = p.Key,
Name = havingHighestPrice.Name,
Bar = havingHighestPrice.Bar,
Price = havingHighestPrice.Price,
}
})
You can use Linq query syntax to store local variables:
var groups = from p in products.GroupBy(x => x.ProductId)
let first = p.OrderByDescending(o => o.Price).First()
select new Product
{
ProductId = p.Key,
Name = first.Name,
Price = first.Price,
};
What's important is that it's safe to use in Entity Framework queries.
Related
I have list of products which has id, name and price. I want to show it in console using prices such as
Price 1 to 100
--list of products
price 101 to 200
--list of products
and it so on till last highest price.
I need to determine at runtime how many segments I need to create based upon highest price.
If you have a list called products that have a property called Price you could use a basic Linq expression with OrderBy and Where
Edit: now that I understand your question better, you could create a new list for each segment of 100's.
so something like this:
products = products.OrderBy(x => x.Price).ToList();
var subProducts = products.Where(x => x.Price > 0 && x.Price < 100).ToList();
// then print each item in the list here.
// then do the next segment
subProducts = products.Where(x => x.Price >= 100 && x.Price < 200).ToList();
and you could put this basic format in a loop and then iterate through by the 100's to get each segment.
There are two problems to solve here. First, you need to figure out, for any given price, how to categorize it into a price range. The information you've given in your post isn't quite specific enough unless all your prices are in whole numbers, but I'll make an educated guess here.
// Model
record ProductPriceRange(decimal Low, decimal High);
// Desired behavior
GetPriceRange(0.01m).Should().Be(new ProductPriceRange(0.01m, 100m));
GetPriceRange(100m).Should().Be(new ProductPriceRange(0.01m, 100m));
GetPriceRange(100.01m).Should().Be(new ProductPriceRange(100.01m, 200m));
// Implementation
ProductPriceRange GetPriceRange(decimal price)
{
var groupBasis = (int)(((price - 0.01m) / 100)) * 100 + 0.01m;
return new ProductPriceRange(groupBasis, groupBasis + 99.99m);
}
Then your grouping code can look something like this:
var productGroups =
from p in products
group p by GetPriceRange(p.Price) into g
select new {
PriceRange = g.Key,
Products = g.ToList()
};
foreach (var g in productGroups)
{
Console.WriteLine($"{g.PriceRange.Low} - {g.PriceRange.High}: {string.Join(", ", g.Products)}");
}
Sample output:
0.01 - 100.00: Product { Price = 0.01 }, Product { Price = 99.99 }, Product { Price = 100 }
100.01 - 200.00: Product { Price = 100.01 }, Product { Price = 199.99 }, Product { Price = 200 }
200.01 - 300.00: Product { Price = 200.01 }
Try the following:
var query = products
.GroupBy(p => (int)(p.Price / 100))
.OrderBy(g => g.Key)
.Select(g => new {
Lower = g.Min(x => x.Price),
Higher = g.Max(x => x.Price),
Items = g.OrderBy(x => x.Price).ToList())
})
.ToList();
It will return list of lists.
I have looked at many similar question on SO but seems its not that straight forward.
The problem is, most of them are dealing with IEnumerable where in my case I have two IQueryable dbsets.
The situation is somewhat similar to the question here.
Student
id Name
1 a1
2 b1
3 c1
Images
id Image StudentId Status ModifiedOn
1 1.jpg 1 Active 2021-03-12 02:02:32.580
2 2.jpg 1 Deleted 2021-03-12 02:01:32.580
3 3.jpg 2 Deleted 2021-03-12 02:02:32.580
4 4.jpg 2 Deleted 2021-03-12 02:01:32.580
Result should be
id Name Image
1 a1 1.jpg
2 b1 3.jpg
3 c1 NULL
I can do this with TSQL and nested WITH qqueries, where one selects Status = Active, and the other selects Status != Active, then merge these two and select the TOP 1.
But since the requirement is to write the equivalent LINQ, I started with the below query, since I don't know a good way to do a merge of CASE WHEN on Status = Active.
var aquery = context.Images;
var lquery = context.Students;
var result = from l in lquery
join a in aquery on l.Id equals a.StudentId into aGroup
from a in aGroup.OrderByDescending(m => m.ModifiedOn).Take(1)
select new {
l.id,
a.StudentId,
a.Status
};
This failed the dbsets are not IEnumerable. Any idea how to get the correct result?
This query should work:
var query =
from s in context.Students
from i in context.Images
.Where(i => i.StudentId = s.Id)
.OrderBy(i => i.Status == "Active" ? 0 : 1)
.ThenByDescending(i => i.ModifiedOn)
.Take(1)
.DefaultIfEmpty()
select new
{
s.Id,
s.Name,
i.Image
};
IQueryable<Image> images = context.Images.AsQueryable();
IQueryable<Student> students = context.Students;
var result = (from st in students
select new
{
Id = st.Id,
Name = st.Name,
ImageName = images
.OrderBy(x => x.ModifiedAt)
.Where(x => x.Status)
.Where(i=> i.StudentId == st.Id)
.Select(x=> x.ImageName)
.FirstOrDefault()
})
.ToList();
But the easiest option is to define navigation field for images inside Student class:
public class Student{
List<Image> Images {get; private set;}
}
and then:
context.Students
.Select(st=> new
{
Id = st.Id,
Name = st.Name,
ImageName = st.Images
.OrderBy(x => x.ModifiedAt)
.Where(x => x.Status)
.Where(i=> i.StudentId == st.Id)
.Select(x=> x.ImageName)
.FirstOrDefault()
})
.ToList();
Hello every one I have a data as follows
Item Qty Type
1 2 Purchase
1 3 Sales
1 8 Return
2 5 Purchase
2 4 Sales
2 5 Return
Now I have a requirement of getting quantity of each item by Subtracting Sales and Return with Purchase
And my final output would be
item Qty
1 -9
2 -4
Note : To get quantity: Qty => (Purchase Qty - SalesQty - Return Qty) eg: (2-3-8)
So how can I write this query on LINQ or in SQL
SQL Query:
SELECT Item, SUM(CASE Type WHEN 'Purchase' THEN Qty ELSE -Qty END) AS Qty
FROM Table
GROUP BY Item
LINQ:
Items
.GroupBy(p => p.Item)
.Select(p => new
{
Item = p.Key,
Qty = p.Sum(x => x.Type == "Purchase" ? x.Qty : -x.Qty)
});
If you are using LINQ to SQL, you can do:
var ans = from i in src
group i by i.Item into ig
let PurchaseQty = ig.Where(i => i.Type == "Purchase").Sum(i => i.Qty)
let SalesQty = ig.Where(i => i.Type == "Sales").Sum(i => i.Qty)
let ReturnQty = ig.Where(i => i.Type == "Return").Sum(i => i.Qty)
select new {
Item = ig.Key,
Qty = PurchaseQty - SalesQty - ReturnQty
};
If you are using LINQ to EF 2.2, it may work but will do the grouping client side. If you are using EF 3.x, good luck!
I have created a sqlite db. Now I need a table where I can store the quantity bought by my clients and the average price they paid.
The code is in C# and the table has the following structure:
ClientID; ItemID; Quantity; Price; Date;
where I store my client ID and the price they paid when they bought a given quantity of an item on a given date.
It happens that the same client can buy the same item on multiple days and pay a different price.
What I need is to aggregate all the quantity each client bought for a given item and what is the average price he paid.
I assume this can be done in Linq to make it efficient but I am not sure how to set up the query for the need above. Any help is much appreciated.
Did you try anything? How about:
var result = data.GroupBy(x => new {x.ClientID, x.ItemID})
.Select(
g =>
new
{
g.Key.ClientID,
g.Key.ItemID,
AvgPrice = g.Average(c => c.Price),
SumQuantity = g.Sum(c => c.Quantity)
});
purchases.GroupBy(g => new { g.ClientId, g.ItemId })
.Select(g => new
{
ClientId = g.Key.ClientId,
ItemId = g.Key.ItemId,
Price = g.Sum(p => p.Price * p.Quantity) / g.Sum(p => p.Quantity)
});
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);