I have following table:
ID | Type | Desc | Amount
---------------------------------------
Stationery| Pen | Red Pen | 1.00
Stationery| Pen | Blue Pen | 1.10
Stationery| Ruler | A Ruler | 1.50
Stationery| Ruler | B Ruler | 1.40
I want get the Sum Amount for different type. The sum for Pen is 2.10, and for ruler is 2.90. How to I achieve this? I have try using "Group By". Here is my query:
From c In DB.Shop
Group By c.Type
Into TotalAmount=Sum(c.Amount) Select Type,TotalAmount
Then I will select the value and bind to gridview. Binding data to gridview is not the issue for me. The expected result for gridview as follow:
ID | PenAmount | RulerAmount
-------------------------------------
Stationery| 2.10 | 2.90
Please help. Thanks
Here is an example :
var query =
(from c in DB.Shop
group c by c.Type
into grp
select new
{
type = grp.Key,
value = grp.Sum(g => g.Amount)
});
grp.Key will contains c.Type because the grouping constructed by Type property. You may need to make some adjustment to get the exact working code, because I didn't test the linq above and I don't know exactly about your object model.
try this
var query =
(from c in DB.Shop
group c by c.Type
into grp
select new
{
Type= g.Key,
PenAmount = g.Where(c => c.Type=="Pen").Sum(c => c.Amount),
RulerAmount= g.Where(c => c.Type=="Ruler").Sum(c => c.Amount),
});
Related
In my LINQ query below I want to select the 'product' rows and add the 'sale' rows data into it but the opposite is happening, it's selecting the 'sale' rows and adding the 'product' rows
var query = (from product in SampleProductTable
from sale in SampleSalesTable
where (sale.ProductId == product.Id)
select new
{
Id = product.Id,
TotalSales = product.TotalSales + ((product.Id == sale.ProductId) ? sale.Amount : 0)
})
Sample Product Table
+-------+------------+---------+-----------------+-------+------------+
| Id | CategoryId | BrandId | Name | Price | TotalSales |
+-------+------------+---------+-----------------+-------+------------+
| mlk3 | MLK | BRND1 | Creamy Milk | 5 | 10 |
| snck2 | SNCK | BRND2 | Chocolate Snack | 2 | 24 |
+-------+------------+---------+-----------------+-------+------------+
Sample Sales Table
+-----+-----------+--------+
| Id | ProductId | Amount |
+-----+-----------+--------+
| 120 | mlk3 | 55 |
| 121 | mlk3 | 15 |
| 122 | snck2 | 12 |
| 123 | mlk3 | 5 |
| 124 | mlk3 | 10 |
| 125 | snck2 | 2 |
| 126 | mlk3 | 115 |
| 127 | snck2 | 6 |
| 128 | snck2 | 34 |
+-----+-----------+--------+
Desired Output
+-------+------------+
| Id | TotalSales |
+-------+------------+
| mlk3 | 210 |
| snck2 | 78 |
+-------+------------+
var answer = (from product in SampleProductTable
join sale in SampleSalesTable on product.Id == sale.ProductId into subSales
from subSale in subSales.DefaultIfEmpty()
group subSale by new { product.Id, product.TotalSales } into gr
select new
{
gr.Key.Id,
TotalSales = gr.Sum(x => x == null ? 0 : x.Amount) + gr.Key.TotalSales
}).ToList();
Approximate T-SQL:
select p.Id, p.TotalSales + sum(coalesce(s.Amount, 0)) TotalSales
from SampleProductTable p
left outer join SampleSalesTable s on p.Id = s.ProductId
group by p.Id, p.TotalSales
In your example you join two collections the way that the result will have as many rows as there are distinct child records (sales in this case) and create a new object for each record (similar to INNER JOIN). That's why the result is "sales-based".
If I understand your intent correctly, I would approach it like:
SampleProductTable.Select(p => new
{
Id = p.Id,
TotalSales = p.Sales.Sum(s => s.Amount)
}
please note that for this approach you will need to map "Sales" collection on a product.
First of all it would be better to use the join statement instead, and then, it seems you need to group your Sales tables based on the ProductId:
var query = (from product in SampleProductTable
join sale in SampleSalesTable.GroupBy(c => c.ProductId)
on product.Id equals sale.Key
select new
{
Id = product.Id,
TotalSales = product.TotalSales + sale.Sum(c=>c.Amount)
}).ToList();
Also please note: since you used a where statement in your code, you don't need to use this condition (product.Id == sale.ProductId) ? in your select anymore. Same as mine, because I used the join statement with on keyword, there is no need to use the condition in the select area.
You can see your desired result in the following link:
https://dotnetfiddle.net/RFTtrv
In general LINQ terms the query shape you are looking for is called grouped join:
The group join is useful for producing hierarchical data structures. It pairs each element from the first collection with a set of correlated elements from the second collection.
In your case, it will produce a collection of correlated Sales for each Product. Then all you need is to apply aggregate (Sum) inside the final projection (select):
var query =
from product in SampleProductTable
join sale in SampleSalesTable on product.Id equals sale.ProductId into productSales
select new
{
Id = product.Id,
TotalSales = product.TotalSales + productSales.Sum(sale => sale.Amount)
};
But since in some of the comments you mentioned converting to SQL, most likely you are using some ORM like LinqToSQL, EF or EF Core. In such case the things are even simpler. These ORMs support a so called navigation properties which represent the relationships, and when used inside queries are translated to SQL with all the necessary joins, so you don't need to be bothered with such details and can concentrate on the logic needed to produce the desired result.
If that's the case, the Product class would normally have something like
public ICollection<Sale> Sales { get; set; }
and the query in question would be simple Select like this:
var query = db.Products
.Select(product => new
{
Id = product.Id,
TotalSales = product.TotalSales + product.Sales.Sum(sale => sale.Amount)
});
LEFT JOIN with grouping looks like
var query =
from product in SampleProductTable
join sale in SampleSalesTable.GroupBy(c => c.ProductId)
on product.Id equals sale.Key into join1
from lj in join1.DefaultIfEmpty() // left join
select new
{
Id = product.Id,
TotalSales = product.TotalSales + (lj == null ? 0 : lj.Sum(c => c.Amount))
};
Left join may return null, so check the potential group, lj before trying to sum it. For later c# versions null check could be abbreviated to
TotalSales = product.TotalSales + (lj?.Sum(c => c.Amount) ?? 0)
Fiddle
In query syntax, Slava's solution should return with the result you're looking for i.e.
var querySyntax = (from product in SampleProductTable
join sale in SampleSalesTable on product.Id equals sale.ProductId into sales
from subSales in sales.DefaultIfEmpty()
group subSales by new { product.Id, product.TotalSales }
into grp
select new
{
grp.Key.Id,
TotalSales = grp.Sum(s => s.Amount) + grp.Key.TotalSales
}).ToList();
If you have a burning desire to use method syntax for whatever reason, this equivalent LINQ query will also work:
var methodSyntax = (SampleProductTable
.GroupJoin(SampleSalesTable, product => product.Id, sale => sale.ProductId,
(product, sales) => new {product, sales})
.SelectMany(s => s.sales.DefaultIfEmpty(), (s, subSales) => new {s, subSales})
.GroupBy(ss => new {ss.s.product.Id, ss.s.product.TotalSales}, ss => ss.subSales)
.Select(grp => new {grp.Key.Id, TotalSales = grp.Sum(s => s.Amount) + grp.Key.TotalSales})).ToList();
I asked this question previously, but missed a vital part of my problem.
Return certain record based on criteria
Take this list of results
Client | Date | YESorNO
-------------------------------
A1 | 01/01/2001 | NO
A1 | 01/01/2002 | NO
A1 | 01/01/2003 | YES
A1 | 01/01/2004 | NO
A1 | 01/01/2005 | NO
A1 | 01/01/2006 | NO
A1 | 01/01/2007 | YES
A1 | 01/01/2008 | YES
A1 | 01/01/2009 | YES
A2 | 01/01/2001 | NO
A2 | 01/01/2002 | NO
A2 | 01/01/2003 | YES
A2 | 01/01/2004 | NO
A2 | 01/01/2005 | YES
A2 | 01/01/2006 | YES
A3 | 01/01/2001 | NO
...etc...
The list is ordered chronologically and I cannot sort this is any other way other than descending / ascending.
I cannot sort for Yes | NO and find the First() or Last() as this won't give me the required value.
I want to be able to return the first 'YES' after all 'NO's have been accounted for, per Client.
In the above example for Client[A1] row 7 is the record I want returned (on 01/01/2007).
Client[A2] - row 5 (01/01/2005) ..etc
My code is as follows
var query =
(
from m in db.MyTable
where m.Criteria == XYZ
select new
{
Client = m.Client,
Date = m.Date,
YESorNO = m.YESorNO
}
).OrderBy(x => x.Date);
Using .FirstOrDefault(x => x.YesOrNO == "YES") returns the 3rd record.
User #RenéVogt advised that
var result = query.AsEnumerable()
.TakeWhile(x => x.YESorNO == "YES")
.LastOrDefault();
would get the job done and it does, but I forgot to add that the query will be returning many Clients and I need the first 'YES' for each Client, therefore the above code won't suffice.
Iterating over my results would be hugely time consuming and whilst that is a solution I would prefer this logic to be within the database query itself (if possible)
Many thanks
What you have to do is grouping by client,and then find the last YES of each one starting from the end. Something like this (ClientList is a List<>, you may have to change it depending on where is your data):
var query = ClientList.OrderBy(x => x.client).ThenBy(x => x.date).GroupBy(x => x.client);
foreach (var client in query)
{
var lastYES=client.Reverse().TakeWhile(x => x.YESorNO == "YES")
.LastOrDefault();
Console.WriteLine(String.Format("{0} {1}",client.Key,lastYES.date));
}
//Output: A1 01/01/2007 0:00:00
// A2 01/01/2005 0:00:00
Edit
Mansur Anorboev rightly suggested ordering by descending date, thus eliminating the need of Reverse, so the code would be:
var query = ClientList.OrderBy(x => x.client).ThenByDescending(x => x.date).GroupBy(x => x.client);
foreach (var client in query)
{
var lastYES=client.TakeWhile(x => x.YESorNO == "YES")
.LastOrDefault();
Console.WriteLine(String.Format("{0} {1}",client.Key,lastYES.date));
}
Edit 2
I still was not completly happy with my solution, as it is using a foreach. This does everything in one Linq command:
var query = ClientList.OrderBy(x => x.client)
.ThenByDescending(x => x.date)
.GroupBy(x => x.client, (key, g) => g.TakeWhile(x => x.YESorNO == "YES").LastOrDefault())
.ToList();
This returns a list with one element per client and with the correct date.
I can provide a little sql query
;WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Client DESC) AS rn
FROM [dbo].[tblSkaterhaz]
)
,gte AS (
SELECT Client,max(rn) mx FROM cte
WHERE YesOrNo = 'NO'
GROUP BY Client
)
SELECT cte.* FROM gte
INNER JOIN cte on cte.Client = gte.Client and cte.rn = gte.mx + 1
Although it is not the required solution, but it yields the required result. You can create a stored proc and use it in your code.
NOTE: This is tested against the same table (and data) mentioned in question above
I hope this will be helpful for you.
My database has a sales table with entries like so:
_____________________________________
| id | title_id | qty |
-------------------------------------
| 0 | 6 | 10 |
-------------------------------------
| 1 | 5 | 5 |
-------------------------------------
| 2 | 6 | 2 |
-------------------------------------
Title_id is Foreign key pointing to Titles table which is as follows:
_____________________________________
| id | title_id | title |
-------------------------------------
| 0 | 5 | Soda |
-------------------------------------
| 1 | 6 | Coffee |
-------------------------------------
I want to find top 5 sold products wich means i need to calculate the qty value for each product for all it's entried in sales table then order the result by qty in descending order and limit the select to 5.
However I'm new to C# ASP.NET and somewhat new to SQL. I dont know how to do this with LINQ.
This is my code so far:
var getIds = (from sale in db.sales
join tit in db.titles on sale.title_id equals tit.title_id
group sale by sale.qty into result
orderby result.Sum(i => i.qty) descending
select new Publication
{
PubID = sales.title_id, Title = tit.title
}
).Take(5);
Assuming you have a navigation property Sale.Title, something like this should do:
var tops =
db.Sales
.GroupBy( o => o.Title )
.Select( o => new { Title = o.Key, Sum = o.Sum( x => x.Quantity ) } )
.OrderByDescending( o => o.Sum )
.Take( 5 )
.ToList();
tops is then a list of an anonymous type with two properties: the Title object and the sum of the quantities.
You can then get the values like this:
foreach( var top in tops )
{
int titleId = top.Title.title_id;
string title = top.Title.title;
int sumOfQuantities = top.Sum;
...
If you just want the top Title objects, can can select them like this:
List<Title> topTitles = tops.Select( o => o.Title ).ToList();
var result= (from p in sales
let k = new
{
Name = p.Name
}
group p by k into t
orderby Name descending
select new
{
Name = t.Name,
Qty = t.Sum(p => p.Qty)
}).Take(5);
If the entries in the Sales table are more than one per item (ie: in your example you have 'Soda' 10 + 'Soda' 2, then you need to GroupBy(), using the name as the key (or it's related id if it's in another table), but not the qty.
var topSales = db.sales.GroupBy(x => x.title)
.Select(g => new
{
Title = g.Key,
Qty = g.Sum(x => x.qty)
})
.OrderByDescending(x => x.Qty)
.Select(x => new Publication
{
PubID = x.Title.title_id,
Title = x.Title.title1
})
.Take(5)
.ToList();
Note that I've omitted the join statement assuming that you have a foreign key between sales.title_id -> title.id, and you are using LINQ to SQL. Also note that I've avoided using the query syntax in favor of the chained method syntax, I think it's much clear in this use case (although not always true, ie: cross-joins).
Also, SQL and LINQ have some similarities but don't let the names of clauses/methods fool you, LINQ is not SQL, IMHO, Microsoft just tried to make people comfortable by making it look similar ;)
EDIT: fixed GroupBy()
var result= (from p in sales
let k = new
{
Name = p.Name
}
group p by k into t
select new
{
Name = t.Name,
Qty = t.Sum(p => p.Qty)
}).OrderByDescending(i => i.Qty).Take(5);
You need to look at GroupBy; this will give you what you need
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
I've currently got this sample table of data:
ID | Policy ID | History ID | Policy name
1 | 1 | 0 | Test
2 | 1 | 1 | Test
3 | 2 | 0 | Test1
4 | 2 | 1 | Test1
Out of this, I want to group by the Policy ID and History ID (MAX), so the records I want to be kept are ID's 2 and 4:
ID | Policy ID | History ID | Policy name
2 | 1 | 1 | Test
4 | 2 | 1 | Test1
I've tried to do this in LINQ and stumbling on the same issue every time. I can group my entities, but always into a group where I have to re-define the properties, rather than have them kept from my Policy objects. Such as:
var policies = _context.Policies.GroupBy(a => a.intPolicyId)
.Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
This simply just brings out a list of objects which have "Policy ID" and "History ID" within them. I want all the properties returned from the Policies object, without having to redefine them all, as there are around 50+ properties in this object.
I tried:
var policies = _context.Policies.GroupBy(a => a.intPolicyId)
.Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
PolicyObject = group;
});
But this errors out.
Any ideas?
Group by composite key
_context.Policies.GroupBy(a => new {a.intPolicyId, *other fields*}).Select(
group=> new {
PolicyId = group.Key.intPolicyId,
HistoryId = group.Max(intHistoryId),
*other fields*
}
);
Another way - grab histories, than join back with the rest of the data, something like this (won't work out of the box, will require some refining)
var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
var finalData = from h in historyIDs
join p in _context.Policies on h.intPolicyId equals p.intPolicyId
select new {h.HistoryId, *all other policy fields*}
And yet another way, even simpler and not require a lot of typing :):
var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
var finalData = from h in historyIDs
join p in _context.Policies on h.PolicyId equals p.intPolicyId && h.HistoryId equals p.HistoryId
select p
Basically it's somewhat equivalent to the following SQL query:
select p.*
from Policy p
inner join (
select pi.policyId, max(pi.historyId)
from Policy pi
group by pi.policyId
) pp on pp.policyId = p.policyId and pp.historyId = p.historyId
In LINQ to Objects, I'd do this as
var policies = _context.Policies
.GroupBy(a => a.intPolicyId)
.Select(g => g.OrderByDescending(p => p.intHistoryID).First());
but your _context impleis there might be a database involved and I'm not 100% sure this will translate.
Basically it groups by the policy ID as you'd expect, then within each group orders by history ID and from each group selects the row with the highest history ID. It returns exactly the same type as is found in Policies.
I have the following model:
Schools with Many Majors
Majors with Many Offered Degrees (or just Degrees for Short).
+------+--------+--------+
|School| Major | Degree |
+------+--------+--------+
| UCLA |CompSci | B |
| UCLA |CompSci | M |
| UCLA |CompSci | D |
| UCLA |Math | B |
+------+--------+--------+
I'd like to retrieve all the degrees offered by a school, grouped by Majors (so majors is not repeated for each degree returned). How might I do that? I have the following code so far, but now I'm stuck.
var query = from school in schools
where school.Id == Id
select new
{
name = s.Name
majors = (from major in school.Majors
select new
{
majorname = major.Name
}).Distinct()
};
I'm not quite sure I know how to return the degrees for each distinct major.
I was able to solve this by checking out similar situations on SO and by using the group/by/into keywords.
var query = from school in schools
where school.Id == id
select new
{
name = school.Name,
majors = ( from major in school.Majors
group major.Degree by major.Name into sub
select new
{
m = sub.Key,
d = (from degree in sub
select degree.Name)
} )
};
Thanks so much everyone.
What about the following?
var query = schools
.Where(school => school.Id == Id)
.Select(school => new
{
Name = school.Name,
Majors = school.Majors.Select(major => major.Name).Distinct()
})
.GroupBy(obj => obj.Majors);
The only change to your code, other than desugaring the query syntax, is to change the Majors field to an IEnumerable<string> and to add a GroupBy call.
Simply make a Group By
var groupedBy= list.Where(c=> c.Id==Id).GroupBy(c=> c.Major);
foreach(var item in groupedBy )
{
var v=item.Select(c=> new {Major=item.Key,Degree=c.Degree });
}