Calculate Year-Over-Year Change in DataTable - c#

I have DataTable mocked-up below:
+----+------+-------------+--------+
| ID | YEAR | PERSON_NAME | AMOUNT |
+----+------+-------------+--------+
| 1 | 2004 | BARBARA | 500 |
| 2 | 2004 | BOB | 100 |
| 3 | 2004 | JANE | 30 |
| 4 | 2004 | JOHN | 200 |
| 5 | 2005 | BARBARA | 505 |
| 6 | 2005 | BOB | 150 |
| 7 | 2005 | JANE | 15 |
| 8 | 2005 | JOHN | 215 |
| 10 | 2006 | BARBARA | 523 |
| 11 | 2006 | BOB | 185 |
| 12 | 2006 | JANE | 25 |
| 13 | 2006 | JOHN | 207 |
+----+------+-------------+--------+
I am trying to add a new column that will track the year-over-year change of the amounts of each person:
+----+------+-------------+--------+-------+
| ID | YEAR | PERSON_NAME | AMOUNT | Y-O-Y |
+----+------+-------------+--------+-------+
| 1 | 2004 | BARBARA | 500 | |
| 2 | 2004 | BOB | 100 | |
| 3 | 2004 | JANE | 30 | |
| 4 | 2004 | JOHN | 200 | |
| 5 | 2005 | BARBARA | 505 | 5 |
| 6 | 2005 | BOB | 150 | 50 |
| 7 | 2005 | JANE | 15 | -15 |
| 8 | 2005 | JOHN | 215 | 15 |
| 10 | 2006 | BARBARA | 523 | 18 |
| 11 | 2006 | BOB | 185 | 35 |
| 12 | 2006 | JANE | 25 | 10 |
| 13 | 2006 | JOHN | 207 | -8 |
+----+------+-------------+--------+-------+
I've achieved this easily in SQL by joining the table to itself with some ON conditions, and was trying to mimic the same logic to c# DataTable and got it to somehow work in a convoluted way. I was wondering if there is a cleaner way with LINQ or DataViews or just a compact algorithm to achieve the same effect. Thanks!

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("ID", typeof(int));
dt.Columns.Add("YEAR", typeof(int));
dt.Columns.Add("PERSON_NAME", typeof(string));
dt.Columns.Add("AMOUNT", typeof(int));
dt.Rows.Add(new object[] { 1, 2004, "BARBARA", 500 });
dt.Rows.Add(new object[] { 2, 2004, "BOB", 100 });
dt.Rows.Add(new object[] { 3, 2004, "JANE", 30 });
dt.Rows.Add(new object[] { 4, 2004, "JOHN", 200 });
dt.Rows.Add(new object[] { 5, 2005, "BARBARA", 505 });
dt.Rows.Add(new object[] { 6, 2005, "BOB", 150 });
dt.Rows.Add(new object[] { 7, 2005, "JANE", 15 });
dt.Rows.Add(new object[] { 8, 2005, "JOHN", 215 });
dt.Rows.Add(new object[] { 10, 2006, "BARBARA", 523 });
dt.Rows.Add(new object[] { 11, 2006, "BOB", 185 });
dt.Rows.Add(new object[] { 12, 2006, "JANE", 25 });
dt.Rows.Add(new object[] { 13, 2006, "JOHN", 207 });
dt.Columns.Add("Y-O-Y", typeof(int));
List<List<DataRow>> groups = dt.AsEnumerable()
.OrderBy(x => x.Field<int>("YEAR"))
.GroupBy(x => x.Field<string>("PERSON_NAME"))
.Select(x => x.ToList())
.ToList();
foreach (List<DataRow> person in groups)
{
for (int i = 1; i < person.Count(); i++)
{
person[i]["Y-O-Y"] = person[i].Field<int>("AMOUNT") - person[i - 1].Field<int>("AMOUNT");
//or
//person[i]["Y-O-Y"] = (int)person[i]["AMOUNT"] - (int)person[i - 1]["AMOUNT"];
}
}
}
}
}

Related

Devexpress Help to create This Report

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.

how to add new row into by column name in datatable

I try to add new row to footer of column at calculated for Average of each cell in column.
Original datatable:
col1 | col2 | col3 | ... | col20 |
------|------|------|-----|-------|
5 | 10 | 5 | ... | 8 |
------|------|------|-----|-------|
8 | 7 | 8 | ... | 2 |
And I would like to do below :
col1 | col2 | col3 | ... | col20 |
------|------|------|-----|-------|
5 | 10 | 5 | ... | 8 |
------|------|------|-----|-------|
8 | 7 | 8 | ... | 2 |
-------|------|------|-----|-------|
6.5 | 8.5 | 6.5 | ... | 5 |
Here's my code:
if(sum[i] == 0 )
{
dt.Columns.Remove("BIN"+i.ToString()+"");
}
else
{
dt.Columns.Add("%BIN"+i.ToString()+"", typeof(decimal),"(BIN"+i.ToString()+" /
[In System])*100");
if (i == 1)
{
dt.Columns.Add("100-%BIN1", typeof(double), "100 -[%BIN1]");
Avg[i] = Convert.ToDouble(dt.Compute("Avg([col"+i.Tostring()+"])", string.Empty));
}
else
{
Avg[i] = Convert.ToDouble(dt.Compute("Avg([col" + i.ToString() + "])", string.Empty));
}
dt.Rows.Add("Avg_xbar"+i.ToString()+"", Avg[i]);
}
From my code above,There is a problem about result is below:
col1 | col2 | col3 | ... | col20 |
-----------|------|------|-----|-------|
5 | 10 | 5 | ... | 8 |
-----------|------|------|-----|-------|
8 | 7 | 8 | ... | 2 |
-----------|------|------|-----|-------|
Avg_xbar1 | 6.5 | | | |
-----------|------|------|-----|-------|
Avg_xbar2 | 8.5 | | | |
-----------|------|------|-----|-------|
Avg_xbar3 | 6.5 | | | |
-----------|------|------|-----|-------|
... | ... | | | |
-----------|------|------|-----|-------|
Avg_xbar20 | 5 | | | |
I'm assuming that you've got that code running in a loop for each column. You'll want to move the addition of rows outside of this loop.
for (var i = 0; i < columns.length; i++) {
// existing looping logic
}
dt.Rows.Add(Avg);

Linq Group By and merge rows

I have a table similar to the following format:
id | name | year | Quality| Location |
------------------------------------------
1 | Apple | year1 | Good | Asia |
2 | Apple | year2 | Better | Asia |
3 | Apple | year3 | Best | Asia |
4 | Apple | year1 | Best | Africa |
5 | Apple | year2 | Bad | Africa |
6 | Apple | year3 | Better | Africa |
7 | Apple | year1 | Best | Europe |
8 | Apple | year2 | Bad | Europe |
9 | Apple | year3 | Better | Europe |
10 | Orange | year1 | Bad | Asia |
11 | Orange | year2 | Better | Asia |
12 | Orange | year3 | Bad | Asia |
13 | Orange | year1 | Best | Africa |
14 | Orange | year2 | Better | Africa |
15 | Orange | year3 | Bad | Africa |
16 | Orange | year1 | Best | Europe |
17 | Orange | year2 | Better | Europe |
18 | Orange | year3 | Best | Europe |
19 | Mango | year1 | Bad | Asia |
20 | Mango | year2 | Better | Asia |
21 | Mango | year3 | Better | Asia |
22 | Mango | year1 | Good | Africa |
23 | Mango | year2 | Better | Africa |
24 | Mango | year3 | Good | Africa |
25 | Mango | year1 | Best | Europe |
26 | Mango | year2 | Better | Europe |
27 | Mango | year3 | Best | Europe |
I need the list in this format in LINQ:
{ Location: Asia, year: year1, Good: 1, Bad: 2, Better: 0, Best: 0 }
{ Location: Asia, year: year2, Good: 0, Bad: 0, Better: 3, Best: 0 }
{ Location: Asia, year: year3, Good: 0, Bad: 1, Better: 1, Best: 1 }
.
.
.
.
My LINQ query is this:
var result = context.Fruits
.Groupby(f => new { f.Year,f.Location,f.Quality })
.Select(g => new
{
Year = g.Key.Year,
Location = g.Key.Location,
Quality = g.Key.Quality,
Count = g.Count()
});
This gives me something along the lines of:
{ Location: Asia, year: year1, Quality: Good, Count: 1 }
{ Location: Asia, year: year1, Quality: Bad, Count: 2 }
.
.
.
.
How do I get the required format with LINQ? Do I have to get the result and then use for each to get it to the format that I need?
You want to Count() the Quality property, so it does make sense to use it as a key in the grouping. If you just omit it and group only by Year and Location, you are getting the desired output.
public class Program
{
public static void Main()
{
var fruits = new List<Fruit> {
new Fruit { Id = 1, Name = "Apple", Year = "year1", Quality = "Good", Location ="Asia"},
new Fruit { Id = 2, Name = "Apple", Year = "year2", Quality = "Better", Location ="Asia"},
new Fruit { Id = 3, Name = "Apple", Year = "year3", Quality = "Better", Location ="Asia"},
new Fruit { Id = 4, Name = "Apple", Year = "year1", Quality = "Best", Location ="Africa"},
new Fruit { Id = 5, Name = "Orange", Year = "year1", Quality = "Vad", Location ="Asia"},
new Fruit { Id = 6, Name = "Orange", Year = "year2", Quality = "Better", Location ="Asia"},
new Fruit { Id = 7, Name = "Orange", Year = "year3", Quality = "Bad", Location ="Asia"},
};
var result = fruits.GroupBy(f => new { f.Year,f.Location})
.Select(g => new
{
Year = g.Key.Year,
Location = g.Key.Location,
Good = g.Count(x => x.Quality == "Good"),
Better = g.Count(x => x.Quality == "Better"),
Best = g.Count(x => x.Quality == "Best"),
});
foreach(var line in result) {
Console.WriteLine(String.Format("Year: {0} - Location: {1} - Good: {2} - Better: {3} - Best: {4}", line.Year, line.Location, line.Good, line.Better, line.Best));
}
}
}
public class Fruit
{
public int Id { get; set; }
public string Name { get; set; }
public string Year { get; set; }
public string Quality { get; set; }
public string Location { get; set; }
}
Output:
Year: year1 - Location: Asia - Good: 1 - Better: 0 - Best: 0
Year: year2 - Location: Asia - Good: 0 - Better: 2 - Best: 0
Year: year3 - Location: Asia - Good: 0 - Better: 1 - Best: 0
Year: year1 - Location: Africa - Good: 0 - Better: 0 - Best: 1
Fiddle: https://dotnetfiddle.net/eg99at

Dividing a List into C # Blocks

I have a Branch List, each one has a Number N of employees, I have a Branch object and a NumberEmployees property, now I need to iterate over that list sending the number of employees per block, I explain better with the following table: I order the List by Number of Employees, so far no problem.
+---------+-----------+
| Branch | Employees |
+---------+-----------+
|MEXICO | 800 |
|USA | 700 |
|INDIA | 500 |
|CHINA | 400 |
|AUSTRALIA| 300 |
+---------+-----------+
Now iterate through a list but dividing the number of employees into blocks something like this:
+-----------+------------+-------------+------------+
| Branch | FirstGroup | SecondGroup | ThirdGroup |
+-----------+------------+-------------+------------+
| Mexico | 267 | 267 | 267 |
| USA | 234 | 234 | 234 |
| India | 167 | 167 | 167 |
| China | 134 | 134 | 134 |
| Australia | 100 | 100 | 100 |
+-----------+------------+-------------+------------+
In the end I think the list that should result would be:
+-----------+-----------+
| Branch | Employees |
+-----------+-----------+
| Mexico | 267 |
| USA | 234 |
| India | 167 |
| China | 134 |
| Australia | 100 |
| Mexico | 267 |
| USA | 234 |
| India | 167 |
| China | 134 |
| Australia | 100 |
| Mexico | 267 |
| USA | 234 |
| India | 167 |
| China | 134 |
| Australia | 100 |
+-----------+-----------+
So far I can only order the List.
double TotalEmployees = ListBranch.Sum(item => item.EmployeeNumber);
double blockSize = TotalEmployees / ListBranch.Count();
double sizeQuery = Math.Ceiling(blockSize);
foreach (Branch branch in ListBranch.OrderByDescending(f => f. EmployeeNumber))
{
//to do
}
I appreciate your valuable help for any clues you can give me
This might do the trick for you
List<BranchEmployee> be = new List<BranchEmployee>();
be.Add(new BranchEmployee() { Branch = "MEXICO", Employee = 800 });
be.Add(new BranchEmployee() { Branch = "USA", Employee = 700 });
be.Add(new BranchEmployee() { Branch = "INDIA", Employee = 500 });
be.Add(new BranchEmployee() { Branch = "CHINA", Employee = 400 });
be.Add(new BranchEmployee() { Branch = "AUSTRALIA", Employee = 300 });
List<BranchEmployee> ExpectedBE = new List<BranchEmployee>();
for(int i = 0; i <= 2; i++)
{
foreach(BranchEmployee smbe in be)
{
ExpectedBE.Add(new BranchEmployee()
{
Branch = smbe.Branch,
Employee = smbe.Employee / 3
});
}
}
What I see is that every group has equal number of employees that is the total number of employees divided by 3.
To look the data in the way you have shown I have created a class like this
public class BranchEmployee
{
public string Branch { get; set; }
public int Employee { get; set; }
}

Data grouping in SQL

I am working on a small project using C# and EF5.0 and I need to group some data. Let say I have table of columns in a building like shown below.
+----------+--------Columns Table--+------+------+
| ColumnID |ColumnName|Width|Length|Height|number|
+----------+----------+-----+------+------+------+
| 1 | C101 | 50 | 70 | 250 | 1 |
| 2 | C102 | 70 | 70 | 250 | 1 |
| 3 | C103 | 70 | 60 | 250 | 1 |
| 4 | C104 | 90 | 70 | 250 | 1 |
| 5 | C105 | 40 | 50 | 250 | 1 |
| 6 | C106 | 50 | 70 | 250 | 1 |
| 7 | C107 | 50 | 60 | 250 | 1 |
| 8 | C108 | 70 | 70 | 250 | 1 |
+----------+----------+-----+------+------+------+
I need a C# code to see the above data groupped like this:
+----------+---Groupped Columns Table-----+------+
|G_ColumnID|ColumnName|Width|Length|Height|number|
+----------+----------+-----+------+------+------+
| 1 |C(101-106)| 50 | 70 | 250 | 2 |
| 2 |C(102-108)| 70 | 70 | 250 | 2 |
| 3 | C103 | 70 | 60 | 250 | 1 |
| 4 | C104 | 90 | 70 | 250 | 1 |
| 5 | C105 | 40 | 50 | 250 | 1 |
| 6 | C107 | 50 | 60 | 250 | 1 |
+----------+----------+-----+------+------+------+
I prefer clues than the exact solution.
EDIT : Below code shows my current state. I think I can find the columns with the same Height, Width and Length using this code. But I am not sure how to generate a new name for the group.
using (pehlivanEntities context = new pehlivanEntities())
{
foreach (var item in context.table1)
{
int id = item.ColumnID;
foreach (var item2 in context.table1)
{
int id2 = item2.ColumnID;
if (id != id2)
{
if (item.Width == item2.Width)
{
if (item.Length == item2.Length)
{
if (item.Height == item2.Height)
{
//Alter item.ColumnName
//increase item.number by one
//Remove item2
}
}
}
}
}
}
}
Well you'd start with grouping on a composite key:
var groups = myData.GroupBy(d => new{d.Width, d.Length, d.Height})
then
groups
.Select(g => new {
g.Key.Width,
g.Key.Length,
g.Key.Height,
columnNames = g.Select(x => x.ColumnName),
number = g.Count()})
then a bit of string manipulation on the columnNames field

Categories

Resources