Multiple group by and Sum LINQ - c#

I have a products sales table that looks like this:
saleDate prod qty
10/22/09 soap 10
09/22/09 pills 05
09/25/09 soap 06
09/25/09 pills 15
I need to make the SUM of each MONTH so the final table would look like this:
saleDate prod qty
10/09 soap 10
09/09 soap 06
09/09 pills 20
Can I do this with LINQ?

var products = new[] {
new {SaleDate = new DateTime(2009,10,22), Product = "Soap", Quantity = 10},
new {SaleDate = new DateTime(2009,09,22), Product = "Pills", Quantity = 5},
new {SaleDate = new DateTime(2009,09,25), Product = "Soap", Quantity = 6},
new {SaleDate = new DateTime(2009,09,25), Product = "Pills", Quantity = 15}
};
var summary = from p in products
let k = new
{
//try this if you need a date field
// p.SaleDate.Date.AddDays(-1 *p.SaleDate.Day - 1)
Month = p.SaleDate.ToString("MM/yy"),
Product = p.Product
}
group p by k into t
select new
{
Month = t.Key.Month,
Product = t.Key.Product,
Qty = t.Sum(p => p.Quantity)
};
foreach (var item in summary)
Console.WriteLine(item);
//{ Month = 10/09, Product = Soap, Qty = 10 }
//{ Month = 09/09, Product = Pills, Qty = 20 }
//{ Month = 09/09, Product = Soap, Qty = 6 }

var q = from s in sales
group s by new {saleDate = s.saleDate.ToString("MM/yy"), s.prod} into g
select new { g.Key.saleDate, g.Key.prod, qty = g.Sum(s => s.qty) };

class Program
{
static void Main(string[] args)
{
var sales = new List<Sale>();
sales.Add(new Sale() { Product = "soap", saleDate = new DateTime(2009, 10, 22), Quantity = 10});
sales.Add(new Sale() { Product = "soap", saleDate = new DateTime(2009, 9,22), Quantity = 6});
sales.Add(new Sale() { Product = "pills", saleDate = new DateTime(2009,9,25), Quantity = 15});
sales.Add(new Sale() { Product = "pills", saleDate = new DateTime(2009,9,25), Quantity = 5});
var q = from s in sales
group s by new { s.Product, s.saleDate.Month, s.saleDate.Year } into g
select new {Month = String.Format("{0:MM/yy}", new DateTime(g.Key.Year, g.Key.Month, 1)), product = g.Key.Product, quantity = g.Sum(o=>o.Quantity)};
}
}
class Sale
{
public DateTime saleDate { get; set; }
public int Quantity { get; set; }
public string Product { get; set; }
}

Sure.
It'll look like this:
var GroupedSales =
from p in products
group p by p.saleDate.Month into g
select new { saleMonth = g.saleDate.Month, QtySold = g.Sum(p => p.qty) };
See: http://msdn.microsoft.com/en-us/vcsharp/aa336747.aspx#sumGrouped
Also, note that I was operating under the assumption that your dataset only had 2009 data. If you want to group by month/year, you can use saleDate.ToString("yyyyMM") instead of saleDate.Month.

Related

C# calculate total weights

I have two classes as below. I get products list 'ProductBooleans' along with the category flag if they fit in it. I also get the weight for each category 'ProductWeight. I need to find the the total weight for each category.
public class ProductBooleans
{
public int Prodid;
public int Cat1;
public int Cat2;
public int Cat3;
}
public class ProductWeight
{
public string Cat;
public int Wt;
}
I need to calculate the total weight for each product in the basket and return the dict<int,int>
ProductBoolean
var pbs = new List<ProductBooleans>()
{
new ProductBooleans() { Prodid = 333, Cat1 = 1, Cat2 = 0, Cat3 = 1, },
new ProductBooleans() { Prodid = 444, Cat1 = 1, Cat2 = 1, Cat3 = 0, },
};
ProductWeight
var pws = new List<ProductWeight>()
{
new ProductWeight() { Cat = "Cat1", Wt = 10, },
new ProductWeight() { Cat = "Cat2", Wt = 20, },
new ProductWeight() { Cat = "Cat3", Wt = 30, },
};
Result should be
Prodid | totalWt
333 40
444 30
NOTE: I am currently using reflection to solve this problem but I think there has to be much simpler way to achieve this.
Is there lib to do this or a simpler way?
This seems to do the job for me:
var query =
from pb in pbs
let totalWt =
(
from x in new (string Cat, int Wt)[]
{
("Cat1", pb.Cat1),
("Cat2", pb.Cat2),
("Cat3", pb.Cat3),
}
join pw in pws on x.Cat equals pw.Cat
select x.Wt * pw.Wt
).Sum()
select new
{
pb.Prodid,
totalWt
};
That gives me:
Here's a version that uses reflection:
(string Cat, int Wt)[] GetValues(ProductBooleans pb) =>
typeof(ProductBooleans)
.GetFields()
.Where(p => p.Name.StartsWith("Cat"))
.Where(p => p.FieldType == typeof(int))
.Select(p => (p.Name, (int)p.GetValue(pbs[0])))
.ToArray();
var query =
from pb in pbs
let totalWt =
(
from x in GetValues(pb)
join pw in pws on x.Cat equals pw.Cat
select x.Wt * pw.Wt
).Sum()
select new
{
pb.Prodid,
totalWt
};
Obviously change .GetFields() to .GetProperties() if that's needed.
var _categoryDic = new Dictionary<Category, bool>();
_categoryDic.Add(new Category() { CategoryName = "cate1", CatId = 1 }, true);
_categoryDic.Add(new Category() { CategoryName = "cate2", CatId = 2 }, false);
_categoryDic.Add(new Category() { CategoryName = "cate3", CatId = 3 }, true);
productBooleans.Add(new ProductBooleans()
{
prodid = 333
,
categoryDic = _categoryDic
});
var _categoryDic2 = new Dictionary<Category, bool>();
_categoryDic2.Add(new Category() { CategoryName = "cate1", CatId = 1 }, true);
_categoryDic2.Add(new Category() { CategoryName = "cate2", CatId = 2 }, true);
_categoryDic2.Add(new Category() { CategoryName = "cate3", CatId = 3 }, false);
productBooleans.Add(new ProductBooleans()
{
prodid = 444
,
categoryDic = _categoryDic2
});
List<ProductWeight> productWeights = new List<ProductWeight>();
productWeights.Add(new ProductWeight() { category = new Category() { CategoryName = "cate1", CatId = 1 }, weight = 10 });
productWeights.Add(new ProductWeight() { category = new Category() { CategoryName = "cate2", CatId = 2 }, weight = 20 });
productWeights.Add(new ProductWeight() { category = new Category() { CategoryName = "cate3", CatId = 3 }, weight = 30 });
var _prodBool =productBooleans.Select(i => new {
prod_id = i.prodid
,
categoryList = i.categoryDic
.Where(j => j.Value)
.Select(j => j.Key)
.ToList<Category>()
}).ToList();
Dictionary<int, int> resultList=new Dictionary<int, int>();
foreach (var item in _prodBool)
{
int itemweight = 0;
foreach (var pw in productWeights)
{
itemweight += (item.categoryList.Where(i => i.CatId == pw.category.CatId).Any()) ? pw.weight : 0;
}
resultList.Add(item.prod_id, itemweight);
}
return resultList;

Find the product with the lowest price return with name LINQ

I´m new to LINQ and I´m trying to find the lowest price in a list and return the name of it.
I´ve been searching and haven´t find anything that I can use.
The List is in a class Category but I have to write out the result in main.
It´s a C# in a Microsoft Visual Studio.
The list I have to find the lowest price from is like this:
public static IEnumerable<Product> GetProducts( )
{
List<Product> products = new List<Product>( );
products.Add( new Product { Name = "Milk", Price = 90, CategoryID = 4, ID = 1 } );
products.Add( new Product { Name = "Cheese", Price = 130, CategoryID = 4, ID = 2 } );
products.Add( new Product { Name = "Butter", Price = 110, CategoryID = 4, ID = 3 } );
products.Add( new Product { Name = "Apple juice", Price = 230, CategoryID = 1, ID = 4 } );
products.Add( new Product { Name = "Grape juice", Price = 240, CategoryID = 1, ID = 5 } );
products.Add( new Product { Name = "Beetroot juice", Price = 300, CategoryID = 1, ID = 6 } );
products.Add( new Product { Name = "Carrot juice", Price = 190, CategoryID = 1, ID = 7 } );
products.Add( new Product { Name = "Ginger ale", Price = 990, CategoryID = 1, ID = 8 } );
products.Add( new Product { Name = "Oregano", Price = 500, CategoryID = 2, ID = 9 } );
products.Add( new Product { Name = "Salt", Price = 550, CategoryID = 2, ID = 10 } );
products.Add( new Product { Name = "Pepper", Price = 490, CategoryID = 2, ID = 11 } );
products.Add( new Product { Name = "Carrots", Price = 300, CategoryID = 3, ID = 12 } );
products.Add( new Product { Name = "Spinach", Price = 250, CategoryID = 3, ID = 13 } );
products.Add( new Product { Name = "Onion", Price = 200, CategoryID = 3, ID = 14 } );
products.Add( new Product { Name = "Garlic", Price = 150, CategoryID = 3, ID = 15 } );
products.Add( new Product { Name = "Tomatoes", Price = 100, CategoryID = 3, ID = 16 } );
return products;
}
from p in Products where p.Price == Products.Min(x=>x.Price) select p.Name
The problem with taking the First from an Ordered list is that it doesn't deal with the possibilities of multiple items having the same lowest price.
products.OrderBy(p => p.Price).Select(p => p.Name).First();
or
products.OrderBy(p => p.Price).First().Name;
This returns Milk
string namesOfProductWithLowestPrice = products
.GroupBy(p => p.Price)
.OrderBy(g => g.Key)
.Select(g => string.Join(",", g.Select(p => p.Name)))
.FirstOrDefault();
In case of multiple products with the lowest price it will concatenate the names with comma.
EDIT I've changed the answer after Tim Schmelters comment on Ralph Shillingtons answer, which is very similiar to mine, but in a different syntax.
int lowestPrice = from prod in GetProducts()
select prod.Price).Min();
var lowestPriceProduct = from p in GetProducts()
where lowestPrice == p.Price)
select p;

How to improve this LINQ query?

I would appreciate any suggestions on improving this linq query. I am querying a list of invoices, with the objective of finding the customer with the highest invoice total for each month. I then want to display the year, month, customer and invoice total.
The invoice:
public class Invoice
{
public string Customer { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
}
The data context to create a list of invoices:
public class DataContext
{
private List<Invoice> _invoices;
private List<string> _customers;
public List<Invoice> Invoices
{
get
{
if (_invoices == null)
{
_customers = new List<string>(){ "Jim", "John", "Jeff", "Joe", "Jack"};
_invoices = new List<Invoice>();
Random random = new Random();
for (int i = 0; i < 1000; i++)
{
_invoices.Add(new Invoice() {
Customer = _customers[random.Next(0, 5)],
Date = new DateTime(random.Next(2010, 2015), random.Next(1, 13), random.Next(1, 20)),
Amount = random.Next(1,1000)
});
}
}
return _invoices;
}
}
}
The query:
DataContext context = new DataContext();
var invoiceTotalsByMonth = from invoice in context.Invoices
group invoice by invoice.Date.Year into yearGroup
orderby yearGroup.Key
select new
{
Year = yearGroup.Key,
Months = from year in yearGroup
group year by year.Date.Month into monthGroup
orderby monthGroup.Key
select new
{
Month = monthGroup.Key,
CustomerTotals = from month in monthGroup
group month by month.Customer into customerGroup
select new
{
Customer = customerGroup.Key,
Total = customerGroup.Sum(i=>i.Amount)
}
}
};
foreach (var year in invoiceTotalsByMonth)
{
Response.Write(year.Year + "<br/>");
foreach (var month in year.Months)
{
var maxCustomer = month.CustomerTotals.First(i => i.Total == month.CustomerTotals.Max(j => j.Total));
Response.Write(month.Month + ": " + maxCustomer.Customer + " - " + maxCustomer.Total.ToString("c") + "<br/>");
}
}
Thank you for your suggestions.
How about that:
DataContext context = new DataContext();
var invoiceTotalsByMonthQuery = from i in context.Invoices
group i by new { i.Date.Year, i.Date.Month } into g
select new
{
Year = g.Key.Year,
Month = g.Key.Month,
Customer = g.GroupBy(x => x.Customer)
.Select(x => new { Name = x.Key, Total = x.Sum(y => y.Amount)})
.OrderByDescending(x => x.Total)
.First()
};
var invoiceTotalsByMonth = invoiceTotalsByMonthQuery.OrderBy(x => x.Year)
.ThenBy(x => x.Month);
foreach(var item in invoiceTotalsByMonth)
{
Console.WriteLine("{0}/{1} - {2} ({3})", item.Month, item.Year, item.Customer.Name, item.Customer.Total);
}
One advice: it's probably better to use OrderBy + First instead of First + Max to find item with the max property value.

LINQ Sum with GroupBy

(Not sure if I even need GroupBy)
My (simplified) tables:
Products (ProductID, Name, Code)
Invoices (InvoiceID, Number, IsPaid)
Invoices_Products (InvoiceID, ProductID, Quantity, Price) - the many-to-many linking table
I need to show a list of Invoices_Products of paid Invoices grouped by the Product Code which sums (Quantity*Price).
The code that I first use to get a collection that I can bind to the UI:
IEnumerable<Invoices_Products> invoices_products = db.Invoices_Products
.Where(ip => ip.Invoice.IsPaid).DistinctBy(m => m.Product.Code);
I then iterate through this to bind it to the UI:
List<BindableInvoiceProduct> bindableInvoiceProducts =
new List<BindableInvoiceProduct>();
foreach (var item in invoices_products)
{
decimal salesValue = db.Invoices_Products.Where(ip => ip.Invoice.IsPaid
&& ip.Product.Code == item.Product.Code).Sum(m => (m.Price * m.Quantity));
bindableInvoiceProducts.Add(new BindableInvoiceProduct()
{
A = item.A,
B = item.B,
SalesValue = salesValue.ToString()
});
}
(The DistinctBy method there is from morelinq)
Why does this not total correctly?
edit:
Some data:
Product - ProductID = 1, Name = 123, Code = A
Product - ProductID = 2, Name = 456, Code = A
Invoice - InvoiceID = 1, Number = INV123, IsPaid = True
Invoices_Products - InvoiceID = 1, ProductID = 1, Quantity = 10, Price = 100
Invoices_Products - InvoiceID = 1, ProductID = 2, Quantity = 10, Price = 200
Expected result:
Code = A, SalesValue = 3000
from invoice in invoices
where invoice.IsPaid
from xr in invoice.InvoiceProducts
group xr.Quantity * xr.Price by xr.Product.Code into g
select new {Code = g.Key, SalesValue = g.Sum()};
If you want per invoice, then:
from invoice in invoices
where invoice.IsPaid
from xr in invoice.InvoiceProducts
group xr.Quantity * xr.Price
by new {Code = xr.Product.Code, Invoice = invoice }
into g
select new {
Code = g.Key.Code,
Invoice = g.Key.Invoice,
SalesValue = g.Sum()};
Based on your description I would have written:
var bindableInvoiceProducts = db.Invoices_Products
.Where(ip => ip.Invoice.IsPaid)
.GroupBy(ip => ip.Product.Code,
(code, ips) => new BindableInvoiceProduct()
{
Code = code,
SalesValue = ips.Sum(ip => (ip.Price*ip.Quantity))
})
.ToList();
Is that, what you need? What is item.A and item.B in your code?

Select top N records after filtering in each group

I am an old bee in .NET but very new to Linq! After some basic reading I have decided to check my skill and I failed completely! I don't know where I am making mistake.
I want to select highest 2 order for each person for while Amount % 100 == 0.
Here is my code.
var crecords = new[] {
new {
Name = "XYZ",
Orders = new[]
{
new { OrderId = 1, Amount = 340 },
new { OrderId = 2, Amount = 100 },
new { OrderId = 3, Amount = 200 }
}
},
new {
Name = "ABC",
Orders = new[]
{
new { OrderId = 11, Amount = 900 },
new { OrderId = 12, Amount = 800 },
new { OrderId = 13, Amount = 700 }
}
}
};
var result = crecords
.OrderBy(record => record.Name)
.ForEach
(
person => person.Orders
.Where(order => order.Amount % 100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2)
);
foreach (var record in result)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
Can anyone focus and tell me what would be correct query?
Thanks in advance
Try this query:
var result = crecords.Select(person =>
new
{
Name = person.Name,
Orders = person.Orders.Where(order => order.Amount%100 == 0)
.OrderByDescending(x => x.Amount)
.Take(2)
});
Using your foreach loop to print the resulting IEnumerable, the output of it is:
XYZ
-->200
-->100
ABC
-->900
-->800
This has already been answered but if you didn't want to create new objects and simply modify your existing crecords, the code would look like this alternatively. But you wouldn't be able to use anonymous structures like shown in your example. Meaning you would have to create People and Order classes
private class People
{
public string Name;
public IEnumerable<Order> Orders;
}
private class Order
{
public int OrderId;
public int Amount;
}
public void PrintPeople()
{
IEnumerable<People> crecords = new[] {
new People{
Name = "XYZ",
Orders = new Order[]
{
new Order{ OrderId = 1, Amount = 340 },
new Order{ OrderId = 2, Amount = 100 },
new Order{ OrderId = 3, Amount = 200 }
}
},
new People{
Name = "ABC",
Orders = new Order[]
{
new Order{ OrderId = 11, Amount = 900 },
new Order{ OrderId = 12, Amount = 800 },
new Order{ OrderId = 13, Amount = 700 }
}
}
};
crecords = crecords.OrderBy(record => record.Name);
crecords.ToList().ForEach(
person =>
{
person.Orders = person.Orders
.Where(order => order.Amount%100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2);
}
);
foreach (People record in crecords)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
}

Categories