Create a table of data by month with totals - c#

I have 2 database tables, joined on a FK (ResponseHeader) examples below (date is 6 October in the example)
ResponseHeader
ResponseHeaderId DateTime
0e24cf96-81eb-2122-7e4a-0d200474692f 06/10/2018 11:15:59
ResponseData
ResponseDataId ResponseHeaderId Response
41c831f1-0adc-2bd5-053e-00406fa526b6 0e24cf96-81eb-2122-7e4a-0d200474692f 1
78967068-82a6-4098-ba35-03211a923f46 0e24cf96-81eb-2122-7e4a-0d200474692f 2
854bc8a6-5877-a6fb-9072-00e358323350 0e24cf96-81eb-2122-7e4a-0d200474692f 2
fe2a667d-ca0e-49a6-b330-f4d4232bfe89 0e24cf96-81eb-2122-7e4a-0d200474692f 3
30f0270e-3e69-3408-7add-02a85f4b9aeb 0e24cf96-81eb-2122-7e4a-0d200474692f 1
There are multiple ResponseHeaders and each response header has 5 rows of data each)
The responses for each group of response data needs to be added up (example = 9)
What I want to achieve though is a table like this where everything is totalled by Month for the given year set.
2018
Month Responses Sum
October 1 9
But, as there are multiple rows for each Responseheader, what I eventually want is this:
2018
Month Responses Sum
October 185 234
September 564 985
...
2017
Month Responses Sum
January 54 123
...
I've got linq for summing by month, for example:
var total = 0;
var filteredResponses = Model.SurveyResponseHeader.Where(x => x.StartDate > DateTime.Today.LastMonth() && x.StartDate > DateTime.Today.AddMonths(1).LastMonth()).ToList();
foreach (var response in filteredResponses)
{
var responseTotal = (response.SurveyResponses.Sum(x => x.Response));
total += responseTotal;
}
#total
Given all this, how do I?
Loop through the data by month, summing each response set of data for the month
Group the data by month and year
I have no problem with some basic linq and C# for querying data by dates and summing, but grouping and summing like this is a bit beyond me

Try following code :
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable dt1 = new DataTable();
dt1.Columns.Add("ResponseHeaderId", typeof (string));
dt1.Columns.Add("DateTime", typeof (DateTime));
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d200474692f", DateTime.Parse("06/10/2018 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746930", DateTime.Parse("05/10/2018 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746931", DateTime.Parse("04/10/2018 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746932", DateTime.Parse("03/10/2017 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746933", DateTime.Parse("02/10/2017 11:15:59") });
DataTable dt2 = new DataTable();
dt2.Columns.Add("ResponseDataId", typeof (string));
dt2.Columns.Add("ResponseHeaderId", typeof (string));
dt2.Columns.Add("Response", typeof (int));
dt2.Rows.Add(new object[] { "41c831f1-0adc-2bd5-053e-00406fa526b6","0e24cf96-81eb-2122-7e4a-0d200474692f", 1 });
dt2.Rows.Add(new object[] { "78967068-82a6-4098-ba35-03211a923f46","0e24cf96-81eb-2122-7e4a-0d200474692f", 2 });
dt2.Rows.Add(new object[] { "854bc8a6-5877-a6fb-9072-00e358323350","0e24cf96-81eb-2122-7e4a-0d2004746930", 2 });
dt2.Rows.Add(new object[] { "fe2a667d-ca0e-49a6-b330-f4d4232bfe89","0e24cf96-81eb-2122-7e4a-0d2004746931", 3 });
dt2.Rows.Add(new object[] { "30f0270e-3e69-3408-7add-02a85f4b9aeb","0e24cf96-81eb-2122-7e4a-0d2004746932", 1 });
dt2.Rows.Add(new object[] { "30f0270e-3e69-3408-7add-02a85f4b9aeb","0e24cf96-81eb-2122-7e4a-0d2004746933", 1 });
var join = (from d1 in dt1.AsEnumerable()
join d2 in dt2.AsEnumerable() on d1.Field<string>("ResponseHeaderId") equals d2.Field<string>("ResponseHeaderId")
select new { d1 = d1, d2 = d2 })
.OrderByDescending(x => x.d1.Field<DateTime>("DateTime"))
.GroupBy(x => new { year = x.d1.Field<DateTime>("DateTime").Year, month = x.d1.Field<DateTime>("DateTime").ToString("MMMM") })
.Select(x => new { year = x.Key.year, month = x.Key.month, total = x.Select(y => y.d2.Field<int>("Response")).Sum()})
.ToList();
}
}
}

Related

Combine one to many lists into one flat list of objects C#

So here is the issue I am facing. I have a one to many list relationship.
Here are examples of how the lists would look.
The objects in list 1 are dynamic and have a few properties with the most important one being "id".
The second list has a defined object looking something like this "id" "value desc" "actual value".
The second list can have many rows belonging to the first list. The "value desc" is the property name and the "actual value".
I need to combine these lists into something that looks like this. An object with all list 1 properties and all corresponding rows in the second list. If the second list had 3 items belonging to an item in list 1 then the new object should have all properties of list 1 along with all the rows gathered from list 2 in a flat structure like way.
Data examples
Table 1:
id
name
1
bob
2
joe
Table 2
id
propname
value
1
length
2
1
age
12
1
haircolor
blue
2
length
5
2
age
90
2
haircolor
red
How I want the data to look
id
name
length
haircolor
age
1
bob
2
blue
12
2
joe
5
red
90
Currently, I have this working.
public IEnumerable<dynamic> test(List<dynamic> data, List<modal>
dataset)//closest
{
var query = (from a in data
join b in dataset
on a.id equals b.id into t
select new {
a,
t
});
return query;
}
However, the result is an object with properties of list 1 and then a property on that object that is an array of items found in list 2. I need these items to not be in an array and be property names with values on the new created object.
I hope my explanation was clear enough!
Use a pivolt 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 dt1 = new DataTable();
dt1.Columns.Add("id", typeof(int));
dt1.Columns.Add("name", typeof(string));
dt1.Rows.Add(new object[] { 1, "bob" });
dt1.Rows.Add(new object[] { 2, "bob" });
DataTable dt2 = new DataTable();
dt2.Columns.Add("id", typeof(int));
dt2.Columns.Add("propname", typeof(string));
dt2.Columns.Add("value", typeof(object));
dt2.Rows.Add(new object[] { 1, "length", 2 });
dt2.Rows.Add(new object[] { 1, "age", 12 });
dt2.Rows.Add(new object[] { 1, "haircolor", "blue"});
dt2.Rows.Add(new object[] { 2, "length", 5 });
dt2.Rows.Add(new object[] { 2, "age", 90 });
dt2.Rows.Add(new object[] { 2, "haircolor", "red" });
DataTable pivot = new DataTable();
string[] properties = dt2.AsEnumerable().Select(x => x.Field<string>("propname")).Distinct().OrderBy(x => x).ToArray();
pivot.Columns.Add("id", typeof(int));
pivot.Columns.Add("name", typeof(string));
DataColumn[] columns = properties.Select(x => new DataColumn(x, typeof(object))).ToArray();
pivot.Columns.AddRange(columns);
var joins = from id1 in dt1.AsEnumerable()
join id2 in dt2.AsEnumerable() on id1.Field<int>("id") equals id2.Field<int>("id")
select new { id = id1.Field<int>("id"), name = id1.Field<string>("name"), id2 = id2 };
var groups = joins.GroupBy(x => x.id);
foreach (var group in groups)
{
DataRow newRow = pivot.Rows.Add();
newRow["id"] = group.Key;
newRow["name"] = group.First().name;
foreach (var row in group)
{
newRow[row.id2.Field<string>("propname")] = row.id2.Field<object>("value");
}
}
}
}
}

How to get sum with date filtering and distinct with EF?

In my system I have a table with participants weight history (ParticipantData):
ParticipantId Weight Date
1 86 2020-01-30
1 83 2020-02-03
2 98 2020-01-20
2 96 2020-01-26
3 75 2020-02-06
I need to get the sum of weights, but a participant must only count one time with their latest weight before or equal to a specific date.
Here's what I have so far:
DateTime dt = DateTime.Now;
DateTime? chartStartDate = new DateTime(Event.Event.StartDateTime.Value.Year, Event.Event.StartDateTime.Value.Month, Event.Event.StartDateTime.Value.Day);
DateTime? chartEndDate = dt < Event.Event.EndDateTime ? dt : Event.Event.EndDateTime;
bool chartLoop = true;
if (chartStartDate < dt) {
// Get all weights
var participantsWeightStats = await _context.ParticipantData.ToListAsync();
// Loop date with steps of 7
do {
// Get the sum of `participantsWeightStats` before or equal to the loop-date
// In the following I need to Distinct/GroupBy ParticipantId so every participant only counts with the latest weight
var chartData = participantsWeightStats.Where(x => x.Date <= chartStartDate).OrderByDescending(x => x.Date).ToList();
ViewData["PData_Values"] += chartData.Sum(x => x.Weight).ToString().Replace(".", "").Replace(",", ".") + ",";
ViewData["PData_Labels"] += "'" + chartStartDate.Value.ToString("d") + "',";
chartStartDate = chartStartDate.Value.AddDays(7);
if (chartStartDate > chartEndDate && chartLoop) {
chartStartDate = chartEndDate;
chartLoop = false;
}
} while (chartStartDate <= chartEndDate);
}
I've tried different approaches with GroupBy and Distinct but with no luck.
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 dt = new DataTable();
dt.Columns.Add("ParticipantId", typeof(int));
dt.Columns.Add("Weight", typeof(int));
dt.Columns.Add("Date", typeof(DateTime));
dt.Rows.Add(new object[] {1, 86, DateTime.Parse("2020-01-30")});
dt.Rows.Add(new object[] {1, 83, DateTime.Parse("2020-02-03")});
dt.Rows.Add(new object[] {2, 98, DateTime.Parse("2020-01-20")});
dt.Rows.Add(new object[] {2, 96, DateTime.Parse("2020-01-26")});
dt.Rows.Add(new object[] {2, 75, DateTime.Parse("2020-02-06")});
DateTime endDate = DateTime.Parse("2020-01-31");
int sum = dt.AsEnumerable()
.Where(x => x.Field<DateTime>("Date") <= endDate)
.OrderByDescending(x => x.Field<DateTime>("Date"))
.GroupBy(x => x.Field<int>("ParticipantId"))
.Sum(x => x.First().Field<int>("Weight"));
}
}
}

Distinct records based on a field string compare, average a field and return dynamic objects

I'm using MVC 5 where I have the following Model / table called Question;
I want to produce the following result;
My objective is to obtain a list of records reflecting DISTINCT Topics with an average of all the scores for the given Distinct Topic.
var result = from q in db.Questions
where q.Topic.Distinct()
select new Question
{
Category = q.Category,
Topic = q.Topic,
Store1Rating = db.Questions.Where(x => x.Topic == q.Topic).Average(s1 => s1.Store1Rating).ToString(),
Store2Rating = db.Questions.Where(x => x.Topic == q.Topic).Average(s2 => s2.Store2Rating).ToString()
};
I know I'm way off on the LINQ statement but I only have about 6 weeks of doing basic LINQ statements under my belt. This is a whole other layer of complexity over basic LINQ for me. Thanks in advance!
You can do this by using GroupBy
var result = db.Questions.GroupBy(x => new
//grouping data by category and topic, rows which have same category and topic will be in a group
{
x.Category,
x.Topic
}).Select(g => new
//now selecting category and topic for each group and the data we want
{
g.Key.Category,
g.Key.Topic,
//calculate Store1Rating average of all rows which has same category and topic as this group
Store1Rating = db.Questions.Where(x => x.Category == g.Key.Category && x.Topic == g.Key.Topic).Average(x => x.Store1Rating),
//calculate Store2Rating average of all rows which has same category and topic as this group
Store2Rating = db.Questions.Where(x => x.Category == g.Key.Category && x.Topic == g.Key.Topic).Average(x => x.Store2Rating),
});
Try code below. Use following webpage for reference : https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
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 Questions = new DataTable();
Questions.Columns.Add("Id", typeof(int));
Questions.Columns.Add("Category", typeof(string));
Questions.Columns.Add("Topic", typeof(string));
Questions.Columns.Add("Store1Rating", typeof(int));
Questions.Columns.Add("Store2Rating", typeof(int));
Questions.Rows.Add(new object[] { 1,"Food","Pizza", 5, 6 });
Questions.Rows.Add(new object[] { 2, "Food", "Pizza", 4, 5 });
Questions.Rows.Add(new object[] { 3, "Food", "Ice Cream", 4, 5 });
Questions.Rows.Add(new object[] { 4, "Beverage", "Beer", 3, 4 });
Questions.Rows.Add(new object[] { 5, "Beverage", "Soda", 4, 5 });
Questions.Rows.Add(new object[] { 6, "Food", "Pizza", 5, 6 });
Questions.Rows.Add(new object[] { 7, "Food", "Pizza", 4, 5 });
Questions.Rows.Add(new object[] { 8, "Food", "Ice Cream", 3, 4 });
Questions.Rows.Add(new object[] { 9, "Beverage", "Beer", 4, 5 });
Questions.Rows.Add(new object[] { 10, "Beverage", "Soda", 5, 6 });
var summary = Questions.AsEnumerable()
.GroupBy(x => x.Field<string>("Topic"))
.Select(x => new
{
id = x.FirstOrDefault().Field<int>("Id"),
category = x.FirstOrDefault().Field<string>("Category"),
topic = x.Key,
rating1 = x.Select(y => y.Field<int>("Store1Rating")).ToList().Average(),
rating2 = x.Select(y => y.Field<int>("Store2Rating")).ToList().Average()
}).ToList();
}
}
}
​

A special looping query

I am reading an excel using .NET and need to perform certain checks on that excel.
The excel example is as below:
ColumnA ColumnB ColumnC
1001 Null 10
1001 W101 5
1001 W102 4
1001 W103 2
1002 Null 12
1002 W104 5
1002 W105 3
1003 W106 5
1003 W107 2
The requirement is as follows:
If there is a Null entry in ColumnB, then I need to compare the values under ColumnC,i.e. the sum of the values(5,4,2 as against the values W101, W102, W103) should be equal to 10(value against Null), if not then write an error in a log file.
My problem is that the there can be n values in ColumnC, how to loop against it.
In the above excel, the values corresponding to 1001 (ColumnA) are 4 in number whereas, the values corresponding to 1002, 1003 (ColumnA) are 3 and 2 in number respectively.
How to write a generic logic for it, I am not able to understand.
Below is the code which I have written, but it will work only when I have 4 values corresponding to 1001 in ColumnA.
FileStream file = File.Open(path, FileMode.Open, FileAccess.Read);
IExcelDataReader obj = ExcelReaderFactory.CreateOpenXmlReader(file);//uses a 3rd party library
obj.IsFirstRowAsColumnNames = true;
DataSet ds = obj.AsDataSet();
DataTable dt = ds.Tables[0];
for (int i = 0; i < dt.Rows.Count; i++)
{
if (dt.Rows[i][1].ToString() == "Null")
{
double a = (double)dt.Rows[i][2];
double x = (double)dt.Rows[i + 1][2];
double y = (double)dt.Rows[i + 2][2];
double z = (double)dt.Rows[i + 3][2];
if (a != (x+ y + z))
{
Response.Write("Mismatch in row: " + dt.Rows[i + 1][1]);
Response.Write("<br/>");
}
}
}
Also, I have used a 3rd party library to read the excel and convert it into a DataSet.
The following query will get the results you need:
Test data used:
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("A", typeof(string)));
dt.Columns.Add(new DataColumn("B", typeof(string)));
dt.Columns.Add(new DataColumn("C", typeof(int)));
dt.Rows.Add(new object[] { "1001", "Null", 10 });
dt.Rows.Add(new object[] { "1001", "W101", 5 });
dt.Rows.Add(new object[] { "1001", "W102", 4 });
dt.Rows.Add(new object[] { "1001", "W103", 1 });
dt.Rows.Add(new object[] { "1002", "Null", 12 });
dt.Rows.Add(new object[] { "1002", "W104", 5 });
dt.Rows.Add(new object[] { "1002", "W105", 3 });
dt.Rows.Add(new object[] { "1003", "W106", 5 });
dt.Rows.Add(new object[] { "1003", "W107", 2 });
LINQ:
var result =
dt
.AsEnumerable()
// Group by the A-column.
.GroupBy(r => (string)r["A"])
// Get only those groups where the first item in the B-column is 'Null':
.Where(g => g.FirstOrDefault(r => (string)r["B"] == "Null") != null)
// Get only those groups where the sum of the items of the C-column
// after the first one is different from the first item:
.Where(g => (int)g.First()["C"] != g.Skip(1).Sum(r => (int)r["C"]))
.ToList();
The result will contain rows groupped by the A-column that have invalid sums. If you are not interested in the actaul rows but only if they are valid then put .Any() in place of .ToList().
If you don't use a "Null" strings but a real null like:
dt.Rows.Add(new object[] { "1001", null, 10 });
you'll need to exchange the first Where to:
.Where(g => g.FirstOrDefault(r => r.Field<string>("B") == null) != null)

add similar rows of a datatable in c#

I have a datatable in which multiple data is populated. Need to check a column which contains same values in different rows and add up all the similar rows into one in the same datatable.
for example
id pid pname pAmountex vat vat
1 4 t1 123 2
2 3 t2 45 3
3 4 t3 56 7
4 3 t4 23 8
in the above table,pid column has similar values 4& 3 .i need to sum up the pamountex,vat column of 1st and 3 rd rows for pid 4 and sum up 2 and 3 rows for pid 3.
Here is an example with LINQ:
DataTable dt = new DataTable();
dt.Columns.Add("id");
dt.Columns.Add("pid");
dt.Columns.Add("pname");
dt.Columns.Add("pamountex",typeof(int));
dt.Columns.Add("vat",typeof(int));
dt.Rows.Add( new object[] { 1, 4, "t1", 123, 2 } );
dt.Rows.Add( new object[] { 2, 3, "t2", 45, 3 } );
dt.Rows.Add( new object[] { 3, 4, "t3", 56, 7 } );
dt.Rows.Add( new object[] { 4, 3, "t4", 23, 8 } );
dt.AsEnumerable()
.GroupBy(r => r.Field<string>("pid") )
.Where (r => r.Count() > 1)
.Select (gr =>
new { id = 0,
pid = gr.Key,
pname = "???",
pamountex = gr.Sum (g => g.Field<int>("pamountex")),
vat = gr.Sum (g => g.Field<int>("vat"))
})
.ToList()
.ForEach( r => dt.Rows.Add(
new object[] {
r.id,
r.pid,
r.pname,
r.pamountex,
r.vat } ));
You did not specify what values are required in id and pname columns for rows that keep sum so I added some defaults(id = 0 and pname = "???") - change it according to your needs.
I assumed that rows with source data should stay in the table.

Categories

Resources