C# Linq to aggregate results and compute averages - c#

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

Related

How to display product using group by prices in c# console app

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.

Linq query with multiple GroupBy

Hey I have a linq query that I cannot make any sense of. It draws from a table to get sales data for a specific company(collection of stores).
The sales table is a collection of individual sale items, multiple sale items can be on one bill. I need to make a query that sorts a day worth of sales by store and then counts the total Bills for that store for that day and gives the average bill for that store for that day.
Here is the start of what I have so far
//get list of all stores bill count/average bill
FactSales.Where(d => d.DateKey >= 20130920).Where(d => d.DateKey <= 20130920)
.Where(c => c.CompanyID == 4).GroupBy(g=>new{g.StoreID, g.BillID}).Select(g=>new{Store = g.Key.StoreID, Amount = g.Sum(a => a.Amount).Value })
This gets me a list of all individual bills but how do I get the average and bill count for each store in a list that would contain : storeID, Avg Bill(Sum of all store bills/billcount) and the Bill Count of each store?
Ive been at this awhile and read many posts and I must be missing something very obvious.
Any help will be greatly appreciated!
FactSales
// filter sale positions by day
.Where(d => d.DateKey >= 20130920 && d.DateKey <= 20130920)
// filter sale positions by company
.Where(c => c.CompanyID == 4)
// group values by store and bill so each row will contains all sales in one bill
.GroupBy(g=>new { g.StoreID, g.BillID })
// calculate total amount for each bill
.Select(g=>new { Store = g.Key.StoreID, Bill = g.Key.BillID, Amount = g.Sum(a => a.Amount).Value })
// group bills by store so each row will contains all bills with total amount for this store
.GroupBy(g => g.Store)
// calculate bills count and avg amount for this store
.Select(g => new { Store = g.Key, Bills = g.Count(), AvgBill = g.Average(x => x.Amount) });
I thing that g.Sum(a => a.Amount).Value should not contains Value property and you can do just g.Sum(a => a.Amount).
i thk this will also work :
var result = FactSales
.Where(d => d.DateKey >= 20130920 && d.DateKey <= 20130920)
.Where(c => c.CompanyID == 4)
.GroupBy(t => new { t.StoreId })
.Select(X => new
{
Store = X.Key.StoreId,
Sum = X.Sum(t => t.Amount),
Bills = X.Count(),
average = X.Sum(t => t.Amount) / X.Count()
});
isn't.

C# LINQ Group by

I'm new to C# and trying to answer some LINQ questions. I'm stuck on 1st marked as difficult...
Q: What were the top 10 origin airports with the largest average​ departure delays, including the values of these delays? (Hint: use group by)?
I have a list named "Flights" populated with more than 20000 objects of class "FlightInfo".
Properties of the FlightInfo class are:
string Carrier, string Origin, string Destination, int DepartureDelay, int ArrivalDelay, int Cancelled, int Distance.
I understand that I should group FlightInfo by FlightInfo.Origin and than average each of these groups by FlightInfo.DepartureDelay and than show 10 with the highest average delay, but beside grouping I'm completely stuck on how to proceed further.
Thank you in advance for any help!
Here is the example of one of previous questions that I was able to answer:
Q: The weighted arrival delay of a flight is its arrival delay divided the distance. What  was the flight with the largest weighted arrival delay out of Boston, MA?
A:
var weighted = (from FlightInfo in Flights
where FlightInfo.Origin == "Boston MA"
orderby (FlightInfo.ArrivalDelay / FlightInfo.Distance) descending
select FlightInfo).Take(1);
var topTen = flights.
GroupBy(g => g.Origin).
Select(g => new { Origin = g.Key, AvgDelay = g.ToList().Average(d => d.DepartureDelay) }).
OrderByDescending(o => o.AvgDelay).
Take(10);
var result = flights
.GroupBy(f => f.Origin)
.OrderByDescending(g => g.Average(f => f.DepartureDelay))
.Take(10)
.Select(g => new
{
AirportName = g.Key,
Flights = g.ToList()
});
The last .Select parameter depends on what you want.
You could do this.
var top10 = Flights.GroupBy(g=>g.Origin) // groupby origin
.OrderByDescending(x=> x.Sum(f=> f.ArrivalDelay / f.Distance)) // Get the weighted delay for each fight and use for ordering.
.Select(x=>x.Key) //Airport or Origin (Modify with what you want)
.Take(10)
.ToList() ;

Should I use OrderByDescending twice in LINQ?

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.

LINQ: how to get a group of a table ordering with a related table?

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

Categories

Resources