Sum column where (condition) with datatable - c#

Suppose I have the following datatable, and i want to sum all rows where column is "money" where the month is may.
+--------------------+-------+
| Date | money |
+--------------------+-------+
| 5/3/2020 8:00:00 | 20 |
+--------------------+-------+
| 5/3/2020 11:00:00 | 10 |
+--------------------+-------+
| 8/3/2020 12:00:00 | 5 |
+--------------------+-------+
| 9/7/2020 10:00:00 | 56 |
+--------------------+-------+
| 2/11/2020 13:00:00 | 45 |
+--------------------+-------+
Expected result is 30, my code gives 0 as answer
My code is:
string data= dt.AsEnumerable()
.Where(y => y.Field<DateTime>("Date").Month==m)
.Sum(x => x.Field<int>("money"))
.ToString();
Note, I don't want a sulotion with iteration, but similar to mine (Like sql style "select from where...")

My result is 30.
var dt = new DataTable();
dt.Columns.Add("Date", typeof(DateTime));
dt.Columns.Add("money", typeof(int));
var row = dt.NewRow();
row["Date"] = new DateTime(2020, 5, 3);
row["money"] = 20;
dt.Rows.Add(row);
row = dt.NewRow();
row["Date"] = new DateTime(2020, 5, 3);
row["money"] = 10;
dt.Rows.Add(row);
row = dt.NewRow();
row["Date"] = new DateTime(2020, 8, 3);
row["money"] = 5;
dt.Rows.Add(row);
row = dt.NewRow();
row["Date"] = new DateTime(2020, 9, 7);
row["money"] = 56;
dt.Rows.Add(row);
row = dt.NewRow();
row["Date"] = new DateTime(2020, 2, 11);
row["money"] = 45;
dt.Rows.Add(row);
var sumOfValuesInMarch = dt.AsEnumerable()
.Where(x => x.Field<DateTime>("Date").Month == 5)
.Sum(x => x.Field<int>("money"))
.ToString(); // 30

Related

Merge columns of two DataTables using linq

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!

issue looping through SQL table C#

My application is reading off a SQL query table and matching and displaying values in certain fields in a listview. My query SQL table looks like this
+--------------+---------+-------+----------+--------+
| process name | subtask | total | employee | date |
+--------------+---------+-------+----------+--------+
| process 1 | sub1 | 1 | 1111 | 01-May |
| process 2 | | 1 | 2222 | 05-May |
| process 3 | | 1 | 3333 | 10-May |
| process 4 | | 1 | 4444 | 07-May |
+--------------+---------+-------+----------+--------+
I have run into a problem. I have noticed by stepping through my loops (using messegebox) that process 1 is displayed in my messagebox multiple times (in MessageBox.Show(dr[0].ToString());) when it should only be displayed once and when the loop gets to process 4 the messagebox displays process 4 (in (in MessageBox.Show(dr[0].ToString());)) then the loop stops and thus does not grab the total, employee and date values. How can I fix this.
DateTime now = DateTime.Now;
var startDate = new DateTime(now.Year, now.Month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
string[,] report = new string[,] { {"process 1", "sub1", "3", "0", "", "" },
{"process 2", "", "3", "0", "", "" },
{"process 3", "", "3", "0", "", "" }
*** there are multiple entries in this array *****
}
string totalsquery = "select Process_Name, Sub_Process1_Name, count(id) as total, Completed_By_Employee_Number, max(Refresh_Date) from testDB.dbo.Quality_Data_Master where Refresh_Date between '" + startDate + "' and '" + endDate + "' group by Process_Name, Sub_Process1_Name, Completed_By_Employee_Number, Refresh_Date";
SqlConnection con = new SqlConnection();
SqlDataAdapter ada = new SqlDataAdapter(totalsquery, con);
DataTable dt = new DataTable();
ada.Fill(dt);
listView1.View = View.Details;
listView1.Columns.Add("Process Name", 250);
listView1.Columns.Add(" Sub Task", 200);
listView1.Columns.Add("Target", 45, HorizontalAlignment.Center);
listView1.Columns.Add("Total", 40, HorizontalAlignment.Center);
listView1.Columns.Add("Employee", 100, HorizontalAlignment.Center);
listView1.Columns.Add("Date", 100);
for (int i = 0; i < dt.Rows.Count; i++)
{
DataRow dr = dt.Rows[i];
//MessageBox.Show(dr.ToString());
for (int j = 0; j < report.GetLength(0); j++)
{
if (report[j, 0].Equals(dr[0].ToString()))
{
MessageBox.Show(dr[0].ToString());
if (report[j, 1].Equals(dr[1].ToString()))
{
MessageBox.Show(dr[1].ToString());
report[j, 3] = (Int32.Parse(report[j, 3]) + (int)dr[2]).ToString();
MessageBox.Show(dr[2].ToString());
report[j, 4] = report[j, 4] + dr[3].ToString();
MessageBox.Show(dr[3].ToString());
report[j, 5] = report[j, 5] + dr[4].ToString();
MessageBox.Show(dr[4].ToString());
}
}
}
}
String[] temp = new String[7];
for (int i = 0; i < report.GetLength(0); i++)
{
temp[0] = report[i, 0].ToString();
temp[1] = report[i, 1].ToString();
temp[2] = report[i, 2].ToString();
temp[3] = report[i, 3].ToString();
temp[4] = report[i, 4].ToString();
temp[5] = report[i, 5].ToString();
//temp[6] = report[i, 6].ToString();
ListViewItem listItem = new ListViewItem(temp);
listView1.Items.Add(listItem);
}
con.Close();
I recomend to refactor your code, also.
Instead of using ListView, look at DataGridView.
With DataGridView you can simple do:
// dataGridView defined at design
// more info on [msdn][1] pages
DataTable dt = GetDataTable(...);
dataGridView.DataSource = dt;

Get MAX date per day and return all rows with those dates

I have a table that has a datetime field. There are multiple rows with various,sometimes identical, dates.
I am trying to find a LINQ query that can return to me an IList of ALL entries over the last n days, but for each day of rows returned, I should only see the rows from the MAX time.
e.g. if I have rows in a day with times of 4/4/16 1:00:00 and 4/4/16 2:00:00, I would expect to retrieve all rows with the latter date.
Additional clarification:
The entity has an attribute, let's say Category. I want the query to return ONE IList, that is populated with every entity sorted by max date PER DAY and PER CATEGORY. e.g.
Item1 | Category1 | 10/10/2016 11:00:00
Item1 | Category1 | 10/10/2016 11:00:00
Item1 | Category1 | 10/10/2016 11:00:00
Item1 | Category2 | 10/10/2016 11:00:05
Item1 | Category1 | 10/10/2016 11:00:05
Item1 | Category1 | 10/10/2016 11:00:05
Item1 | Category2 | 10/11/2016 11:00:00
Item1 | Category1 | 10/11/2016 11:00:00
I would expect the result to look like a list of:
Item1 | Category2 | 10/10/2016 11:00:05
Item1 | Category1 | 10/10/2016 11:00:05
Item1 | Category1 | 10/10/2016 11:00:05
Item1 | Category2 | 10/11/2016 11:00:00
Item1 | Category1 | 10/11/2016 11:00:00
var n = new DateTime(2016, 10, 10);
var res = from data in list
where data.Date >= n
group data by new { data.Date.Year, data.Date.Month, data.Date.Day }
into dataGroup
select dataGroup.OrderBy(eg => eg.Date).Last();
Here's a little example:
class Data
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
static void Main(string[] args)
{
var list = new List<Data> {
new Data() { Date = new DateTime(2016, 10, 9, 11, 0 , 0) },
new Data() { Date = new DateTime(2016, 10, 10, 11, 0 , 0) },
new Data() { Date = new DateTime(2016, 10, 10, 13, 0, 0) },
new Data() { Date = new DateTime(2016, 11, 10) },
new Data() { Date = new DateTime(2016, 11, 11, 10, 0, 0) },
new Data() { Date = new DateTime(2016, 11, 11, 9, 0, 0) },
};
var n = new DateTime(2016, 10, 10);
var res = from data in list
where data.Date >= n
group data by new { data.Date.Year, data.Date.Month, data.Date.Day }
into dataGroup
select dataGroup.OrderBy(eg => eg.Date).Last();
foreach (Data r in res.ToList())
{
Console.WriteLine(r.Date);
}
Console.ReadKey();
}
Output:
10/10/2016 1:00:00 PM
11/10/2016 12:00:00 AM
11/11/2016 10:00:00 AM
I am trying to find a LINQ query that can return to me an IList of ALL entries over the last n days, but for each day of rows returned, I should only see the rows from the MAX time.
Then you need to filter the last n days data, group by date, find the max time within each date, select all rows within the date with the max time, and finally flatten the result:
var endTime = DateTime.Today; // uncomment to include today .AddDays(1);
var startTime = endTime.AddDays(-n);
var result =
from e in db.Table
where e.DateTime >= startTime && e.DateTime < endTime
group e by e.DateTime.Date into dateGroup
let maxTime = (from e in dateGroup select e.DateTime).Max()
from e in dateGroup
where e.DateTime == maxTime
select e;
Try this, assuming n is an int for representing the last n days.
DateTime sinceLast = DateTime.Now.AddDays(-n);
DateTime sinceLastAtMidnight = new DateTime(sinceLast.Year, sinceLast.Month, sinceLast.Day);
var result = lstDate.Where(x => x >= sinceLastAtMidnight)
.Select(x => new
{ DateActual = x,
DateWitoutTime = new DateTime(x.Year, x.Month, x.Day)
})
.GroupBy(x => x.DateWitoutTime)
.Select(x => x.OrderByDescending(y => y.DateActual).FirstOrDefault())
.Select(x => x.DateActual)
.ToList();

Improve SQL query to calculate timespan between two consecutive rows

So... I have a a table like this:
RowID | DocID | Time | DepartmentID
1 | 1001 | 2015-11-20 | 1
2 | 1001 | 2015-11-21 | 2
3 | 1002 | 2015-11-20 | 1
4 | 1001 | 2015-11-25 | 1
5 | 1002 | 2015-11-22 | 3
6 | 1002 | 2015-11-30 | 1
My goal is to get the time in days a department spends with a document before sending it to another department.
I successfully achieved this by passing above table from SQL to a datatable in C#. Then get list of DocsID, and iterate throw each item in that list filtering the datatable with DocID and only then calculate the time between consecutive rows.
So the final result looks like:
DepartmentID | DocID | Time (Days)
1 | 1001 | 2
2 | 1001 | 5
1 | 1002 | 3
3 | 1002 | 9
The problem is this function in C# is taking about 30 seconds to get this results, so I'm looking for ways to improve it.
Is it possible to get this throw SQL only without making anything in C#?
My C# function (dt is a datatable with first table):
List<Int32> listDocIDs = new List<Int32>();
foreach (DataRow dr in dt.Rows)
{
int str = Convert.ToInt32(dr["DocID"].ToString());
if (!listDocIDs.Contains(str))
listDocIDs.Add(str);
}
DataTable times = new DataTable();
times.Columns.AddRange(new DataColumn[3] { new DataColumn("DepartmentID", typeof(Int32)),
new DataColumn("DocID",typeof(Int32)),
new DataColumn("Days",typeof(Int32)) });
foreach (int DocID in listDocIDs)
{
DataTable DocID_times = new DataTable();
using (SqlConnection conn = new SqlConnection(strCon))
{
conn.Open();
SqlDataAdapter adapter = new SqlDataAdapter("getRecordsByDocID", conn);
adapter.SelectCommand.Parameters.Add("#DocID", SqlDbType.Int).Value = DocID;
adapter.SelectCommand.CommandType = CommandType.StoredProcedure;
adapter.Fill(DocID_times);
conn.Close();
}
int j = 0;
for (int i = 0; i < DocID_times.Rows.Count; i++)
{
j = i + 1;
if (i < (DocID_times.Rows.Count - 1))
{
DateTime tempo1 = DateTime.ParseExact(DocID_times.Rows[i]["Time"].ToString(), "dd-MM-yyyy HH:mm:ss",
System.Globalization.CultureInfo.InvariantCulture);
DateTime tempo2 = DateTime.ParseExact(DocID_times.Rows[j]["Time"].ToString(), "dd-MM-yyyy HH:mm:ss",
System.Globalization.CultureInfo.InvariantCulture);
double mins = (tempo2 - tempo1).TotalMinutes;
TimeSpan result = TimeSpan.FromMinutes(mins);
double days = result.TotalDays;
var rows = times.Select(string.Format("DepartmentID = {0} AND DocID = {1}", DepartmentID, DocID));
if (rows.Length == 0)
{
// Add your Row
times.Rows.Add(DepartmentID, DocID, days);
}
else
{
// Update your Days
rows[0]["days"] = Convert.ToInt32(rows[0]["days"].ToString()) + days;
}
}
}
}
If you're listing all the rows, I would calculate the days between records inside a while loop. It can be done purely with SQL, but it won't be as good as the while loop (which can have access to two rows at a time). To be able to do it purely in SQL, you would have to join the table with itself, joining each record with the next one.
IEnumerable<MySummarizedRow> GetSummarizedRows()
{
using (var entries = GetRowsOrderedByDocIdAndRowId().GetEnumerator())
{
if (entries.MoveNext())
{
var previous = entries.Current;
while (entries.MoveNext())
{
var current = entries.Current;
if (current.DocId == previous.DocId)
yield return new MySummarizedRow(previous.DepartmentId, current.DocId, current.Time.Substract(previous.Time).TotalDays + 1);
previous = current;
}
}
}
}
This function ignores the rows for a document that hasn't been passed to another department yet. You can easily change that yielding a new row with -1 days or something like that.

How to replace duplicates in datatable

I have Datatable 1:-----------------------------should be like that:
ID Name Lastname ID Name Lastname
------------------- -----------------------
1 | koki ha 1 | koki ha
------------------- | ----------------- //merge Rows[0][0]
1 | lola mi | lola mi //with Rows[1][0] if the same
------------------- -----------------------
2 | ka xe 2 ka xe
how to replace "1" with "" or empty if is already exist? I spend for this for 2 hours but can't find the solution. I tried with linq but dont find the key to do it right, maybe distinct or group?
DataTable table = new DataTable("table");
table.Columns.Add("ID", typeof(Int32));
table.Columns.Add("Name", typeof(String));
table.Columns.Add("Lastname", typeof(String));
object[] o1 = { 1, "Kiki", "ha"};
object[] o2 = { 1,"lola","mi"};
object[] o4 = { 2, "ka", "xe" };
table.Rows.Add(o1);
table.Rows.Add(o2);
table.Rows.Add(o4);
dataGridView2.DataSource = table;
Here's how you can do this using LINQ:
var dataRows = table.Rows.Cast<System.Data.DataRow>()
.GroupBy(r => r[0])
.Where(g => g.Count() > 1);
foreach (var dataRowGroup in dataRows) {
int idx = 0;
foreach (DataRow row in dataRowGroup) {
if (idx++ > 0) {
row[0] = DBNull.Value;
}
}
}

Categories

Resources