Reputation: 16743
I have a DataTable I would like massage into a new format (here is what it comes out like when attached to a gridview):
<table cellspacing="0" rules="all" border="1" id="GridView1" style="border-collapse: collapse;">
<tr>
<th scope="col">
Line
</th>
<th scope="col">
StartTime
</th>
<th scope="col">
EndTime
</th>
<th scope="col">
Attribute
</th>
<th scope="col">
Value
</th>
</tr>
<tr>
<td>
Line1
</td>
<td>
24/01/2013 7:30:10 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Actual
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Line1
</td>
<td>
24/01/2013 7:30:10 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
ProductCategory
</td>
<td>
FFAC
</td>
</tr>
<tr>
<td>
Line1
</td>
<td>
24/01/2013 7:30:10 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Target
</td>
<td>
36.5
</td>
</tr>
<tr>
<td>
Line2
</td>
<td>
24/01/2013 7:26:50 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Actual
</td>
<td>
69
</td>
</tr>
<tr>
<td>
Line2
</td>
<td>
24/01/2013 7:26:50 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
ProductCategory
</td>
<td>
FFAC
</td>
</tr>
<tr>
<td>
Line2
</td>
<td>
24/01/2013 7:26:50 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Target
</td>
<td>
55.5555582046509
</td>
</tr>
<tr>
<td>
Line3
</td>
<td>
24/01/2013 8:00:20 AM
</td>
<td>
24/01/2013 8:47:50 AM
</td>
<td>
Actual
</td>
<td>
1475
</td>
</tr>
<tr>
<td>
Line3
</td>
<td>
24/01/2013 8:00:20 AM
</td>
<td>
24/01/2013 8:47:50 AM
</td>
<td>
ProductCategory
</td>
<td>
FFAC
</td>
</tr>
<tr>
<td>
Line3
</td>
<td>
24/01/2013 8:00:20 AM
</td>
<td>
24/01/2013 8:47:50 AM
</td>
<td>
Target
</td>
<td>
202.430557310581
</td>
</tr>
<tr>
<td>
Line4
</td>
<td>
24/01/2013 7:31:30 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Actual
</td>
<td>
1384
</td>
</tr>
<tr>
<td>
Line4
</td>
<td>
24/01/2013 7:31:30 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
ProductCategory
</td>
<td>
FFAC
</td>
</tr>
<tr>
<td>
Line4
</td>
<td>
24/01/2013 7:31:30 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Target
</td>
<td>
3179.26381587982
</td>
</tr>
<tr>
<td>
Line5
</td>
<td>
24/01/2013 7:37:00 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Actual
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Line5
</td>
<td>
24/01/2013 7:37:00 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
ProductCategory
</td>
<td>
FHHT
</td>
</tr>
<tr>
<td>
Line5
</td>
<td>
24/01/2013 7:37:00 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Target
</td>
<td>
92.6652171770756
</td>
</tr>
<tr>
<td>
P2_Bundler
</td>
<td>
24/01/2013 7:35:00 AM
</td>
<td>
24/01/2013 8:00:10 AM
</td>
<td>
Actual
</td>
<td>
7
</td>
</tr>
</table>
I know that if the records have the same line, start and end times, the records are related.
What I want to do is get a sum of the Actual and Target values grouped by the ProductCategory. In other words:
ProductCategory | Sum(Actual) | Sum(Target)
FFAC | 1000 | 2000
FHHT | 200 | 175
Any guidance would be appreciated!
Regards,
Chris
Upvotes: 1
Views: 147
Reputation: 3892
Oi, I thought this was going to be more difficult than anticipated, but it ended up being easier and I rewrote this. You perform your grouping on the line, but then you just have to query the rows in each group to get the ProductCategory.
First, you're going to group by the main key, which in this case I believe is the Line
column. So:
myDataTable.AsEnumerable()
.GroupBy(m => m.Field<string>("Line"))
Next, we're going to have to find the category for each of these groupings. Since each grouping is an IEnumerable<T>
, just perform a Select
after filtering our Attribute
as ProductCategory
and get the first value. I use a little "defensive" coding by accounting for the situation where no ProductCategory
attribute exists:
...
.Select(g => new
{
ProductCategory = g.Where(r => r.Field<string>("Attribute") == "ProductCategory")
.Select(r => r.Field<string>("Value"))
.FirstOrDefault() ?? "No Category",
SumActual = g.Sum(x => x.Field<decimal>("Actual")),
SumTarget = g.Sum(x => x.Field<decimal>("Target"))
})
Edit: Alright, so now I see what you're saying. My original thoughts were correct in that you're grouping each original group (based on Line) into further groups. There really isn't an easy way, and the options aren't pretty. It's the fact that you have to group each group, but in order to group the group, you have to query the group for a single entity, then aggregate based on another 2 entities (making it very difficult to use LINQ here).
myDataTable.AsEnumerable()
.GroupBy(m => m.Field<string>("Line"))
.Select(g => new
{
ProductCategory = g.Where(r => r.Field<string>("Attribute") == "ProductCategory")
.Select(r => r.Field<string>("Value"))
.FirstOrDefault() ?? "No Category",
Actual = g.Where(r => r.Field<string>("Attribute") == "Actual")
.Select(r =>
{
decimal d = 0m;
Decimal.TryParse(r.Field<string>("Value"), out d);
return d;
}
.FirstOrDefault(),
Target = g.Where(r => r.Field<string>("Attribute") == "Target")
.Select(r =>
{
decimal d = 0m;
Decimal.TryParse(r.Field<string>("Value"), out d);
return d;
}
.FirstOrDefault()
})
.GroupBy(n => n.ProductCategory)
.Select(g => new
{
ProductCategory = g.Key,
SumActual = g.Sum(x => x.Actual),
SumTarget = g.Sum(x => x.Target)
})
Again, not very pretty... Especially since your "Value" column is string, you have to parse them in order to get a meaningful value (in this case, summing a number). The premise here is that you group all your records into "blocks", then compose these blocks into single anonymous objects. So each anonymous object represents all datarow Attribute/Value pairs for a particular Line. From there, you just group based on your desired key (ProductCategory in this case), and perform your required aggregates.
PS, I would actually perform this query on the DB side using a PIVOT. Might take a bit more setup, but then you give the system designed to perform this type of data crunching/aggregation the work that it can handle, and keep your front-end CPU cycles for something more important.
Upvotes: 0
Reputation: 60493
You won't have any massage here ;)
The structure of your dataTable would be more usefull than the grid code, but to get what you want from your DataTable, you should do something like that.
var result = myDataTable.AsEnumerable()
.GroupBy(m => m.Field<string>("ProductCategory"))
.Select(g => new {
productCategory = g.Key,
sumActual = g.Sum(x => x.Field<decimal>("Actual")),
sumTarget = g.Sum(x => x.Field<decimal>("Target"))
});
Upvotes: 2