say I've got a DataTable in this format:
id | key1 | key2 | data1 | data2 | parentID
10 | AA | one | 10.3 | 0.3 | -1
10 | AA | two | 20.1 | 16.2 | -1
10 | BB | one | -5.9 | 30.1 | -1
20 | AA | one | 403.1 | -20.4 | 10
30 | AA | one | 121.5 | 210.3 | -1
and a second DataTable like so:
id | data
10 | 5500
20 | -3000
30 | 500
what I want to do is aggregate the data at the "id" level, with the second table's "data" field added to the first's net "data1", and "data2" just summed up by itself. I figured out how to do this, but what I'm stuck at is this: I want data for anything with "parentID" != -1 to be added to it's parent. so the output of the above data should be
id | data1 | data2
10 | 2927.6 | 26.2
30 | 621.5 | 210.3
is there an efficient way to do this?
edit: code sample
DataTable dt1 = new DataTable();
dt1.Columns.Add("id", typeof(int));
dt1.Columns.Add("key1", typeof(string));
dt1.Columns.Add("key2", typeof(string));
dt1.Columns.Add("data1", typeof(double));
dt1.Columns.Add("data2", typeof(double));
dt1.Columns.Add("parentID", typeof(int));
DataTable dt2 = new DataTable();
dt2.Columns.Add("id", typeof(int));
dt2.Columns.Add("data", typeof(double));
dt1.Rows.Add(new object[] { 10, "AA", "one", 10.3, 0.3, -1 });
dt1.Rows.Add(new object[] { 10, "AA", "two", 20.1, 16.2, -1 });
dt1.Rows.Add(new object[] { 10, "BB", "one", -5.9, 30.1, -1 });
dt1.Rows.Add(new object[] { 20, "AA", "one", 403.1, -20.4, 10 });
dt1.Rows.Add(new object[] { 30, "AA", "one", 121.5, 210.3, -1 });
dt2.Rows.Add(new object[] { 10, 5500 });
dt2.Rows.Add(new object[] { 20, -3000 });
dt2.Rows.Add(new object[] { 30, 500 });
var groups = dt1.AsEnumerable()
.GroupBy(e => e["id"])
.Select(e => new
{
id = e.Key,
net_data1 = e.Sum(w => (double)w["data1"]),
net_data2 = e.Sum(w => (double)w["data2"])
})
.GroupJoin(dt2.AsEnumerable(), e1 => e1.id, e2 => e2["id"],
(a1, a2) => new
{
id = a1.id,
net_data1 = a1.net_data1 + a2.Sum(w => (double)w["data"]),
net_data2 = a1.net_data2
});
Unfortunately, SQL (and, by extension, LINQ) is not well-suited to recursion. Can the parentID column go multiple levels deep? Like this:
ID Parent
------------------
10 -1
20 10
30 10
40 20
If you want to retrace the steps up from ID 40 to ID 10, then you should abandon a SQL/LINQ approach and just do it in code.
It sounds like a good use of a group join. Something like this might work (though it's completely untested):
var items = from parent in context.dataTable
join child in context.dataTable on parent.id equals child.parentID into children
where parent.parentID == -1
select new { id = parent.id,
data1 = (parent.data1 + children.Sum(c => c.data1)),
data2 = (parent.data2 + children.Sum(c => c.data2)) };
Related
I have the following C# models:
public class RawData
{
public int questionnaireId { get; set; }
public int coachNodeId { get; set; }
public int questionnaireNumber { get; set; }
public float score { get; set; }
}
public class AveragedData
{
public int coachNodeId { get; set; }
public int questionnaireNumber { get; set; }
public float averageScore { get; set; }
}
I have an API endpoint which is returning data from a database, mapped as List<RawData>. The values are like this:
questionnaireId | coachNodeId | questionnaireNumber | score
1 | 30 | 1 | 2
2 | 40 | 1 | 3
3 | 30 | 2 | 1
4 | 30 | 3 | 4
5 | 40 | 2 | 5
6 | 40 | 1 | 5
7 | 30 | 1 | 1
8 | 30 | 1 | 2
9 | 40 | 1 | 2
10 | 30 | 2 | 4
What I need to do now, in a LINQ query, is to average out the score values grouped by coachNodeId and questionnaireNumber and return a list of type AveragedData.
The values returned by averaging and grouping the example data above, should be:
coachNodeId | questionnaireNumber | averageScore
30 | 1 | 1.66666666 (calculated by: (2 + 1 + 2) / 3))
30 | 2 | 2.5 (calculated by: (1 + 4) / 2))
30 | 3 | 4 (calculated by: (4 / 1))
40 | 1 | 3.33333333 (calculated by: (3 + 5 + 2) / 3))
40 | 2 | 5 (calculated by: (5 / 1))
I'm not experienced with LINQ so am struggling to put together a query that groups by both coachNodeId and questionnaireNumber and averages the score, returning an object of type List<AveragedData>. Could anyone suggest how to accomplish this?
Many thanks.
assuming you have a List<RawData> called list, you are wanting:
var results = list.GroupBy(x => new
{
questionnaire = x.questionnaireId,
coach = x.coachNodeId
})
.Select(x => new AveragedData
{
coachNodeId = x.Key.coach,
questionnaireNumber = x.Key.questionnaire,
averageScore = x.Average(xx => xx.score)
})
.ToList();
Do the grouping, then use a Select to project the data to your type, using LINQ's Average as well.
Try following :
DataTable dt = new DataTable();
dt.Columns.Add("questionnaireId", typeof(int));
dt.Columns.Add("coachNodeId", typeof(int));
dt.Columns.Add("questionnaireNumber", typeof(int));
dt.Columns .Add("score", typeof(int));
dt.Rows.Add(new object[] {1,30, 1, 2});
dt.Rows.Add(new object[] {2,40, 1, 3});
dt.Rows.Add(new object[] {3,30, 2, 1});
dt.Rows.Add(new object[] {4,30, 3, 4});
dt.Rows.Add(new object[] {5,40, 2, 5});
dt.Rows.Add(new object[] {6,40, 1, 5});
dt.Rows.Add(new object[] {7,30, 1, 1});
dt.Rows.Add(new object[] {8,30, 1, 2});
dt.Rows.Add(new object[] {9,40, 1, 2});
dt.Rows.Add(new object[] {10,30, 2, 4});
var averages = dt.AsEnumerable()
.GroupBy(x => new { coachNodeId = x.Field<int>("coachNodeId"), questionnaireNumber = x.Field<int>("questionnaireNumber") })
.Select(x => new { coachNodeId = x.Key.coachNodeId, questionnaireNumber = x.Key.questionnaireNumber, average = x.Average(y => y.Field<int>("score")) })
.ToList();
I have two Table one is Target and another is Table sales
Table Target
+------+--------------+--------+
| ID |Category Name | Target |
+------+--------------+--------+
| 1 | AAAA | 15000 |
| 2 | BBBB | 50000 |
| 3 | CCCC | 20000 |
| 4 | DDDD | 40000 |
| 5 | EEEE | 30000 |
+------+--------------+--------+
Table Sales
+------+---------+--------------+--------+
| ID | Date |Category Name | Sale |
+------+---------+--------------+--------+
| 1 | 01/01/20| AAAA | 5000 |
| 2 | 01/01/20| BBBB | 1000 |
| 3 | 02/01/20| CCCC | 2000 |
| 4 | 03/01/20| DDDD | 4000 |
| 5 | 03/01/20| EEEE | 3000 |
| 6 | 03/01/20| AAAA | 1000 |
| 7 | 05/01/20| EEEE | 3000 |
| 8 | 06/01/20| BBBB | 3000 |
| 9 | 02/01/20| CCCC | 1000 |
| 10 | 02/01/20| DDDD | 2000 |
+------+---------|--------------+--------+
I want to create following report from above table. How to create this report by devexpress.
+----------+--------------+------+
| Target |Category Name | Sale |
+----------+--------------+------+
| 15000 | AAAA | 6000 |
| 50000 | BBBB | 4000 |
| 20000 | CCCC | 3000 |
| 40000 | DDDD | 6000 |
| 30000 | EEEE | 6000 |
+----------+--------------+------+
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable targetTable = new DataTable();
targetTable.Columns.Add("ID", typeof(int));
targetTable.Columns.Add("Category Name", typeof(string));
targetTable.Columns.Add("Target", typeof(int));
targetTable.Rows.Add(new object[] { 1, "AAAA", 150000 });
targetTable.Rows.Add(new object[] { 2, "BBBB", 500000 });
targetTable.Rows.Add(new object[] { 3, "CCCC", 20000 });
targetTable.Rows.Add(new object[] { 4, "DDDD", 40000 });
targetTable.Rows.Add(new object[] { 5, "EEEE", 30000 });
DataTable salesTable = new DataTable();
salesTable.Columns.Add("ID", typeof(int));
salesTable.Columns.Add("Date", typeof(DateTime));
salesTable.Columns.Add("Category Name", typeof(string));
salesTable.Columns.Add("Sale", typeof(int));
salesTable.Rows.Add(new object[] { 1, DateTime.Parse("01/01/20"), "AAAA", 5000 });
salesTable.Rows.Add(new object[] { 2, DateTime.Parse("01/01/20"), "BBBB", 1000 });
salesTable.Rows.Add(new object[] { 3, DateTime.Parse("02/01/20"), "CCCC", 2000 });
salesTable.Rows.Add(new object[] { 4, DateTime.Parse("03/01/20"), "DDDD", 4000 });
salesTable.Rows.Add(new object[] { 5, DateTime.Parse("03/01/20"), "EEEE", 3000 });
salesTable.Rows.Add(new object[] { 6, DateTime.Parse("03/01/20"), "AAAA", 1000 });
salesTable.Rows.Add(new object[] { 7, DateTime.Parse("05/01/20"), "EEEE", 3000 });
salesTable.Rows.Add(new object[] { 8, DateTime.Parse("06/01/20"), "BBBB", 3000 });
salesTable.Rows.Add(new object[] { 9, DateTime.Parse("02/01/20"), "CCCC", 1000 });
salesTable.Rows.Add(new object[] { 10, DateTime.Parse("02/01/20"), "DDDD", 2000 });
DataTable table = new DataTable();
table.Columns.Add("Target", typeof(int));
table.Columns.Add("Category Name", typeof(string));
table.Columns.Add("Sale", typeof(int));
var joins = from t in targetTable.AsEnumerable()
join s in salesTable.AsEnumerable() on t.Field<string>("Category Name") equals s.Field<string>("Category Name")
select new { t = t, s = s};
var groups = joins.GroupBy(x => x.t.Field<string>("Category Name"));
foreach (var group in groups)
{
table.Rows.Add(new object[] { group.First().t.Field<int>("Target"), group.Key, group.Sum(x => x.s.Field<int>("Sale")) });
}
}
}
}
You may follow the tutorial how to create a devexpress report
and when you will create a query for the sql datasource, specify a grouping and aggregate the Target column using Sum function like
select Target.Target, sum(Sales.Sale)
from Target
join Sales ...
group by Target.Target
And you can Also create two queries for the report to produce the master-detail report layout, group & sum your Targets using the reporting summary mechanism so it will give you possibility to add a drill down feature to your report to view detailed reports.
This question already has answers here:
Is it possible to Pivot data using LINQ?
(7 answers)
Closed 3 years ago.
I received a request to export data from my asp.net mvc project using linq to an excel spreadsheet. Usually this is an easy task, however, in this scenario the person requesting the data would like the export from example or list A to look like example B
Example A (current export)
Id | CustomerNum | CustomerName | FruitName | Charge
____________________________________________________
1 | 1026 | Bob | Banana | 3.00
2 | 1032 | Jill | Apple | 2.00
3 | 1026 | Bob | Apple | 3.00
4 | 1144 | Marvin | Banana | 1.00
5 | 1753 | Sam | Pear | 4.00
6 | 1026 | Bob | Banana | 3.00
Example B (requested export format)
Id | CustomerNum | CustomerName | Banana | Apple | Pear
_________________________________________________________
1 | 1026 | Bob | 6.00 | 3.00 |
2 | 1032 | Jill | 0 | 2.00 |
3 | 1144 | Marvin | 1.00 | 0 |
5 | 1753 | Sam | 0 | 0 | 4.00
I have never seen where distinct row values were used as columns. How should I go about this?
Create a pivot table :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("CustomerNum", typeof(int));
dt.Columns.Add("CustomerName", typeof(string));
dt.Columns.Add("FruitName", typeof(string));
dt.Columns.Add("Charge", typeof(decimal));
dt.Rows.Add(new object[] {1,1026, "Bob", "Banana", 3.00});
dt.Rows.Add(new object[] {2,1032, "Jill", "Apple", 2.00});
dt.Rows.Add(new object[] {3,1026, "Bob", "Apple", 3.00});
dt.Rows.Add(new object[] {4,1144, "Marvin", "Banana", 1.00});
dt.Rows.Add(new object[] {5,1753, "Sam", "Pear", 4.00});
dt.Rows.Add(new object[] {6,1026, "Bob", "Banana", 3.00});
string[] fruits = dt.AsEnumerable().Select(x => x.Field<string>("FruitName")).Distinct().OrderBy(x => x).ToArray();
DataTable pivot = new DataTable();
pivot.Columns.Add("CustomerNum", typeof(int));
pivot.Columns.Add("CustomerName", typeof(string));
foreach (string fruit in fruits)
{
pivot.Columns.Add(fruit, typeof(decimal));
}
var groups = dt.AsEnumerable().GroupBy(x => x.Field<int>("CustomerNum"));
foreach (var group in groups)
{
DataRow newRow = pivot.Rows.Add();
newRow["CustomerNum"] = group.Key;
newRow["CustomerName"] = group.First().Field<string>("CustomerName");
foreach (DataRow row in group)
{
string fruitName = row.Field<string>("FruitName");
decimal oldvalue = (newRow[fruitName] == DBNull.Value) ? 0 : (decimal)newRow[fruitName];
newRow[fruitName] = oldvalue + row.Field<decimal>("Charge");
}
}
}
}
}
I have two DataTables: dt1 and dt2.
dt1:
ID | Name | Address | QTY
-------+----------+---------+-----
A1 | Dog | C1 | 272
A2 | Cat | C3 | 235
A3 | Chicken | C2 | 254
A4 | Mouse | C4 | 259
A5 | Pig | C5 | 233
dt2:
ID | Name | Address | QTY MAX
-------+----------+---------+--------
A1 | Dog | C1 | 250
A2 | Cat | C3 | 200
A3 | Chicken | C2 | 300
A6 | Rabbit | C6 | 350
But, I want to merge dt1 and dt2 to dt3 like below:
ID | Name | Address | QTY | QTY MAX
-------+----------+---------+-------+--------
A1 | Dog | C1 | 272 | 250
A2 | Cat | C3 | 235 | 200
A3 | Chicken | C2 | 254 | 300
A4 | Mouse | C4 | 259 | 0
A5 | Pig | C5 | 233 | 0
A6 | Rabbit | C6 | 0 | 350
Can any one help me?
If your DataTables haven't primary key and you can't or don't want to change those DataTables you can use a code like this:
// At first you need to define your result `DataTable`
// So make it by cloning from first `DataTable`
var dt3 = dt1.Clone();
// Then add extra columns to it
dt3.Columns.Add("Qty Max", typeof(int));
// Second, you need to add rows of first `DataTable`
foreach (DataRow row in dt1.Rows)
{
// When you don't have a primary key you need a code like this to find same rows:
var dt2Row = dt2.Rows.OfType<DataRow>().SingleOrDefault(w => w["ID"].Equals(row["ID"]));
var qtyMax = dt2Row?["Qty Max"] ?? 0; // Here I set default value to `0`
dt3.Rows.Add(row["ID"], row["Name"], row["Address"], row["Qty"], qtyMax);
}
// Third, you need to add rows of second `DataTable` that is not in first
var dt2OnlyRows =
dt2.Rows.OfType<DataRow>().Where(w => dt1.Rows.OfType<DataRow>().All(x => x["ID"] != w["ID"]));
foreach (var row in dt2OnlyRows)
{
dt3.Rows.Add(row["ID"], row["Name"], row["Address"], 0, row["Qty Max"]);
}
This solution is not a linq solution as you could simply use DataTable.Merge & DataTable.PrimaryKey to get the desired output.
Here is a dummy example which you can use:
var dt1 = new DataTable();
var p1 = dt1.Columns.Add("a", typeof(int)); //Use this to add Primary Key constraint
dt1.Columns.Add("b");
dt1.Columns.Add("c");
dt1.Rows.Add("1", "apple", "10");
dt1.Rows.Add("2", "mango", "20");
dt1.Rows.Add("3", "orange", "30");
dt1.Rows.Add("4", "banana", "40");
dt1.PrimaryKey = new DataColumn[] { p1 }; //This removes duplication of rows
var dt2 = new DataTable();
var p2 = dt2.Columns.Add("a", typeof(int)); //Use this to add Primary Key constraint
dt2.Columns.Add("b");
dt2.Columns.Add("d");
dt2.Rows.Add("1", "apple", "50");
dt2.Rows.Add("2", "mango", "60");
dt2.Rows.Add("3", "orange", "70");
dt2.Rows.Add("5", "grapes", "80");
dt2.PrimaryKey = new DataColumn[] { p2 }; //This removes duplication of rows
var dt3 = dt1.Copy();
dt3.Merge(dt2); // Merge here merges the values from both provided DataTables
Taking your question into consideration:
var dt1 = new DataTable();
var p1 = dt1.Columns.Add("ID", typeof(string));
dt1.Columns.Add("Name", typeof(string));
dt1.Columns.Add("Address", typeof(string));
dt1.Columns.Add("Qty", typeof(int));
dt1.Columns["Qty"].DefaultValue = 0; //Setting default value
dt1.Rows.Add("A1", "Dog", "C1", 100);
dt1.Rows.Add("A2", "Cat", "C3", 200);
dt1.Rows.Add("A3", "Chicken", "C2", 300);
dt1.Rows.Add("A4", "Mouse", "C4", 400);
dt1.Rows.Add("A5", "Pig", "C5", 500);
dt1.PrimaryKey = new DataColumn[] { p1 };
var dt2 = new DataTable();
var p2 = dt2.Columns.Add("ID", typeof(string));
dt2.Columns.Add("Name", typeof(string));
dt2.Columns.Add("Address", typeof(string));
dt2.Columns.Add("Qty Max", typeof(int));
dt2.Columns["Qty Max"].DefaultValue = 0; //Setting default value
dt2.Rows.Add("A1", "Dog", "C1", 600);
dt2.Rows.Add("A2", "Cat", "C3", 700);
dt2.Rows.Add("A3", "Chicken", "C2", 800);
dt2.Rows.Add("A6", "Rabbit", "C6", 900);
dt2.PrimaryKey = new DataColumn[] { p2 };
var dt3 = dt1.Copy();
dt3.Merge(dt2);
Output:
Thanks #shA.t for suggesting to include DataColumn.DefaultValue so that blank cells could be replaced with 0. Also his answer seems to include linq features which I guess is what you are looking for!
Given
(1) A database table, stored as a list of lists. The size of the table in terms of rows and columns is undefined.
List<List<string>> table = new List<List<string>>();
For example:
table.Add(new List<string>() { "a1", "b1", "c1", "d1", "e1" });
table.Add(new List<string>() { "a2", "b2", "c2", "d2", "e2" });
table.Add(new List<string>() { "a3", "b3", "c3", "d3", "e3" });
| a1 | b1 | c1 | d1 | e1 |
| a2 | b2 | c2 | d2 | e2 |
| a3 | b3 | c3 | d3 | e3 |
(2) A list of integers. These integers resemble the indexes of database columns (zero-based), e.g.:
List<int> indexes = new List<int>() { 1, 3, 4 };
Problem
My aim is to project those columns from table of which the indexes occur in the list indexes. Given the above examples, the result should be:
| b1 | d1 | e1 |
| b2 | d2 | e2 |
| b3 | d3 | e3 |
Current Solution
The best I could come up with is to iterate over all rows, like this:
List<List<string>> subtable = new List<List<string>>();
for (int index = 0; index < table.Count; index++)
{
subtable.Add(table[index].Where((t, i) => indexes.Contains(i)).ToList());
}
Request
A more elegant solution, if possible.
What about this:
List<List<string>> subtable =
table.Select(row => indexes.Select(i => row[i]).ToList()).ToList();
In case you need to check the array bounds, you can do this:
List<List<string>> subtable =
table.Select(row => indexes.Where(i => i >= 0 && i < row.Count)
.Select(i => row[i]).ToList()).ToList();
Or if you prefer query syntax:
List<List<string>> subtable =
(from row in table
select
(from i in indexes
where i >= 0 && i < row.Count
select row[i]
).ToList()
).ToList();
Select all the rows, then for each row filter out the columns that are not in your index list:
var subtable = table
.Select(row => row.Where((value, colIndex) => indexes.Contains(colIndex)))
.ToList();
If you want just to print, it is simpler (and more efficient ) to do it without queries like this:
List<List<string>> table = new List<List<string>>();
table.Add(new List<string>() { "a1", "b1", "c1", "d1", "e1" });
table.Add(new List<string>() { "a2", "b2", "c2", "d2", "e2" });
table.Add(new List<string>() { "a3", "b3", "c3", "d3", "e3" });
List<int> indexes = new List<int>() { 1, 3, 4 };
for (int index = 0; index < table.Count; index++)
{
foreach (var columnIndex in indexes)
Console.Write(table[index][columnIndex] +" ");
Console.WriteLine();
}
Console.ReadLine();