I have data like this..
ID
1234-001
1234-002
1234-003
5678-001
7890-001
7890-002
I am holding this data in a datatable. I am attempting to do some processing on the rows by groups based on the base number i.e. 1234, 5678, 7890
How can I iterate through this datatable and hold in new (temp) datatable
1234-001,1234-002, 1234-003
clear the temp datatable then hold
5678-001
clear the temp datatable then hold
7890-001,7890-002
I am working on an old code base and LINQ is not available. I cant come up with an elegant solution. Maybe something to do with dataviews im not sure?
You say you don't want to use LINQ but would prefer an elegant solution... Unless I am missing something vital in your question, this LINQified code seems to let you do what you want.
var grouped = from d in data
group d by d.Id.Split('-').FirstOrDefault();
foreach(var g in grouped) {
// do something with each group
}
Non-LINQ, non-var answer:
DataTable data = new DataTable();
data.Columns.Add("ID");
data.Columns.Add("Value");
data.Rows.Add("1234-001", "Row 1");
data.Rows.Add("1234-002", "Row 2");
data.Rows.Add("1234-003", "Row 3");
data.Rows.Add("5678-001", "Row 4");
data.Rows.Add("7890-001", "Row 5");
data.Rows.Add("7890-002", "Row 5");
Dictionary<String, List<DataRow>> grouped = new Dictionary<String, List<DataRow>>();
foreach(DataRow r in data.Select()) {
List<DataRow> groupedRows;
String key = r["ID"].ToString().Split('-')[0];
if(!grouped.TryGetValue(key, out groupedRows)) {
groupedRows = new List<DataRow>();
grouped[key] = groupedRows;
}
groupedRows.Add(r);
}
foreach(KeyValuePair<String, List<DataRow>> g in grouped) {
String groupKey = g.Key;
Console.WriteLine(groupKey);
foreach(DataRow r in g.Value) {
Console.WriteLine("\t{0}", r["Value"]);
}
}
I get the following output, so I'm not seeing "it only groups the first 3 and stops":
1234
Row 1
Row 2
Row 3
5678
Row 4
7890
Row 5
Row 5
Here is a non Linq example. Since you say it's sorted you can do it in one loop.
DataTable dt1 = new DataTable();
dt1.Columns.Add("ID", typeof (string));
dt1.Rows.Add("1234-001");
dt1.Rows.Add("1234-002");
dt1.Rows.Add("1234-003");
dt1.Rows.Add("5678-001");
dt1.Rows.Add("7890-001");
dt1.Rows.Add("7890-002");
int i = 0;
while (i < dt1.Rows.Count)
{
DataRow row = dt1.Rows[i];
string key = row.Field<string>("ID").Split('-')[0];
DataView dv = new DataView(dt1);
dv.RowFilter = String.Format("ID LIKE '{0}*'", key.Replace("'", "''"));
DataTable tempdt = dv.ToTable();
i = i + tempdt.Rows.Count;
}
Does this help some?
DataTable dt = new DataTable();
dt.Columns.Add("Data", typeof(string));
dt.Rows.Add("1234-001");
dt.Rows.Add("1234-002");
dt.Rows.Add("1234-003");
dt.Rows.Add("5678-001");
dt.Rows.Add("7890-001");
dt.Rows.Add("7890-002");
var stuff = from dr in dt.Select()
group dr by dr["Data"].ToString().Split('-')[0] into g
select new {First = g.Key, Records = g.ToList()};
stuff.Dump();
var groups = table.AsEnumerable();
List<List<DataRow>> groupList = (from g in table.AsEnumerable()
group g by g.Field<string>("id").ToString().Split('-').First() into Group1
select Group1.ToList()).ToList();
Related
I have the following piece of code.
dt3 = new System.Data.DataTable();
foreach (DataRow sourceRow in dt2.Rows) {
DataRow destRow = dt3.NewRow();
destRow[0] = sourceRow[2];
dt3.Rows.Add(destRow);
}
And it is generating following error on line destRow[0] = sourceRow[2];
System.IndexOutOfRangeException: Cannot find column 0.
What am I doing wrong? Is there any way around it without declaring columns beforehand?
Here we create a "source" DataTable with 3 columns, and select two of those columns for the new DataTable:
DataTable srcDataTable = new DataTable();
srcDataTable.Columns.Add("Column A", typeof(string));
srcDataTable.Columns.Add("Column B", typeof(int));
srcDataTable.Columns.Add("Column C", typeof(int));
DataTable dstDataTable = new DataTable();
var desiredColumns = new[] { "Column A", "Column C" };
foreach (DataColumn col in srcDataTable.Columns)
{
if (desiredColumns.Contains(col.ColumnName))
{
dstDataTable.Columns.Add(col.ColumnName, col.DataType, col.Expression);
}
}
Now you can simply loop through the source table and copy the row data as you need it. Example for copying the rows:
foreach (DataRow srcRow in srcDataTable.Rows)
{
var newRow = dstDataTable.NewRow();
foreach (var columnName in desiredColumns)
{
newRow[columnName] = srcRow[columnName];
}
dstDataTable.Rows.Add(newRow);
}
Alternative approach using column numbers:
DataTable srcDataTable = new DataTable();
srcDataTable.Columns.Add("Column A", typeof(string));
srcDataTable.Columns.Add("Column B", typeof(int));
srcDataTable.Columns.Add("Column C", typeof(int));
DataTable dstDataTable = new DataTable();
var desiredColumns = new int[] { 0, 2 };
Dictionary<int, int> columnMap = new Dictionary<int, int>();
for (int colNum = 0; colNum < desiredColumns.Length; ++colNum)
{
columnMap[colNum] = desiredColumns[colNum];
dstDataTable.Columns.Add(srcDataTable.Columns[desiredColumns[colNum]].ColumnName, srcDataTable.Columns[desiredColumns[colNum]].DataType, srcDataTable.Columns[desiredColumns[colNum]].Expression);
}
foreach (DataRow srcRow in srcDataTable.Rows)
{
var newRow = dstDataTable.NewRow();
for (int colNum = 0; colNum < desiredColumns.Length; ++colNum)
{
newRow[colNum] = srcRow[columnMap[colNum]];
}
dstDataTable.Rows.Add(newRow);
}
This line dt3 = new System.Data.DataTable(); creates a new DataTable. But this table doesn't contain any columns yet. In fact, it's an empty table.
This line destRow[0] = sourceRow[2]; tries to set the value of the first column of your table. However, your table doesn't contain any columns yet. And this is what the error message is trying to tell you.
You have to create your column after creating the table. You can do it like this:
DataColumn idColumn = new DataColumn();
idColumn.DataType = System.Type.GetType("System.Int32");
idColumn.ColumnName = "id";
dt3.Columns.Add(idColumn);
Only after this, you will be able to put data into your first column.
Please take a look at this example in the Microsoft docs
What am I doing wrong?
Table dt3 has no column 0, because you haven't added one
Is there any way around it without declaring columns beforehand?
Even though you don't necessarily know the column name or type of dt2's column 2 at compile time, you seem to know that you definitely want column 2 from dt2 to be column 0 of dt3, so make sure you add a column to dt3 that is the same type (and giving it the same name seems reasonable too) as column 2 of dt2:
dt3 = new System.Data.DataTable();
dt3.Columns.Add(dt2.Columns[2].ColumnName, dt2.Columns[2].DataType);
foreach (DataRow sourceRow in dt2.Rows) {
DataRow destRow = dt3.NewRow();
destRow[0] = sourceRow[2];
dt3.Rows.Add(destRow);
}
You can try to use Clone() method to copy structure/columns from source table to destination table:
var dt3 = dt2.Clone();
From the docs.
I'm making a function which group by 1 value and shows average results against group value. but when i write query in sql it shows average like this value(73.6868877912465). but when i get out average from c# using data table it gives this (4.59396937660475) value. Here is the code which i wrote first
protected DataTable Group(DataTable _dt)
{
DataTable dTable = new DataTable();
dTable.Columns.Add("Region_Name", typeof(string));
dTable.Columns.Add("PercentComplete", typeof(Double));
var query = from row in _dt.AsEnumerable()
group row by row.Field<string>("Region_Name") into grp
let PercentComplete = grp.Select(r => r.Field<Double>("PercentComplete")).Count()
select new
{
Region_Name = grp.Key,
PercentComplete = grp.Sum(r => r.Field<Double>("PercentComplete"))/PercentComplete
};
foreach (var grp in query)
{
dTable.Rows.Add(grp.Region_Name, grp.PercentComplete);
}
return dTable;
}
and then i modified it:
protected DataTable Group(DataTable _dt)
{
DataTable dTable = new DataTable();
dTable.Columns.Add("Region_Name", typeof(string));
dTable.Columns.Add("PercentComplete", typeof(Double));
var query = from row in _dt.AsEnumerable()
group row by row.Field<string>("Region_Name") into grp
select new
{
Region_Name = grp.Key,
PercentComplete = grp.Average(r => r.Field<Double>("PercentComplete"))
};
foreach (var grp in query)
{
dTable.Rows.Add(grp.Region_Name, grp.PercentComplete);
}
return dTable;
}
but both functions are producing same results. Kindly help me out of this
problem.
This image is on web which shows average resuly
enter image description here
This Image is on Database
enter image description here
I have written a program to compare two datatables with unique ids and create another datatable to insert certain columns which have the same id in common. I have demonstrated my requirement below.
These are the tables that needs to be compared:
And i need the output as below
but i receive an empty table as the result. I cannot understand where have i gone wrong. Could you please help me on this. I have provided my coding below.Please not that quantity and input are two datatables
DataTable result = new DataTable();
result.Columns.AddRange(new DataColumn[2] { new DataColumn("id"), new DataColumn("qty") });
foreach (DataRow row1 in input.Rows)
{
foreach (DataRow row2 in quantity.Rows)
{
if (row1["id"].ToString() == row2["id"].ToString())
{
result.ImportRow(row2);
}
else
{
result.ImportRow(row1);
}
}
}
return result;
You need a Left Join of 2 Data tables.
DataTable dtinput = new DataTable();
DataTable dtquantity = new DataTable();
dtinput.Columns.Add("id",typeof(int));
dtinput.Rows.Add("2");
dtinput.Rows.Add("4");
dtinput.Rows.Add("7");
dtquantity.Columns.Add("id", typeof(int));
dtquantity.Columns.Add("qty", typeof(int));
dtquantity.Rows.Add("1", "12");
dtquantity.Rows.Add("2", "13");
dtquantity.Rows.Add("3", "5");
dtquantity.Rows.Add("4", "6");
dtquantity.Rows.Add("7", null);
var results = from table1 in dtinput.AsEnumerable()
join table2 in dtquantity.AsEnumerable()
on (int)table1["id"] equals (int)table2["id"]
into outer
from row in outer.DefaultIfEmpty<DataRow>()
select row;
DataTable dt = results.CopyToDataTable();
This diagram should help you in future:
Try something like this:
var result = input.Rows.Where(x => quantity.Rows.Amy(y => x == y));
I hope this helps!
My Datatable 1 (dtOutput) Format (termid,faultid,faultdesc,faulttime,devicetype)
My Datatable 2 (dtOpenEvent) Format (termid,faultid)
I want to retrieve those values which are present in Datatable 2 but not in Datatable 1...based on two columns (termid,faultid) no table have primary keys.
I Searched on net and find code which return diff between two data table...
Now how can i retrieve column values from it ? either in another data table or in string variable
Code :-
DataTable dtOpenEvent;
dtOpenEvent = Generix.getOpenEvents(ref Connection);
DataTable dtOutput;
dtOutput = Generix.getFeedData(ref Connection);
var matched = from table1 in dtOpenEvent.AsEnumerable()
join table2 in dtOutput.AsEnumerable() on table1.Field<string>("ATM") equals table2.Field<string>("termid")
where table1.Field<int>("Event") == table2.Field<int>("faultid")
select table1;
var missing = from table1 in dtOpenEvent.AsEnumerable()
where !matched.Contains(table1)
select table1;
you can remove all of the columns in dt1 and then do except.
like this:
var diff =dt2.AsEnumerable().Except(dt1.AsEnumerable(), DataRowComparer.Default);
full example:
DataTable dt1 = new DataTable();
DataTable dt2 = new DataTable();
dt1.Columns.Add("termid", typeof(Int32));
dt1.Columns.Add("faultid", typeof(Int32));
dt1.Columns.Add("faultdesc");
dt2.Columns.Add("termid", typeof(Int32));
dt2.Columns.Add("faultid", typeof(Int32));
dt1.Rows.Add(1,2,"desc");
dt1.Rows.Add(3, 4, "desc");
dt1.Rows.Add(5, 6, "desc");
dt2.Rows.Add(1, 2);
dt2.Rows.Add(3, 4);
dt2.Rows.Add(7, 8);
dt1.Columns.Remove("faultdesc");
var diff =dt2.AsEnumerable().Except(dt1.AsEnumerable(), DataRowComparer.Default);
foreach (var row in diff)
{
Console.WriteLine(row["termid"] + " " + row["faultid"]); //prints 7 8
}
or instead of removing columns you can select them through linq or dataview like this:
var view = new DataView(dt1);
DataTable dt3 = view.ToTable(true, "termid", "faultid");
modified example:
DataTable dt1 = new DataTable();
DataTable dt2 = new DataTable();
dt1.Columns.Add("termid", typeof(Int32));
dt1.Columns.Add("faultid", typeof(Int32));
dt1.Columns.Add("faultdesc");
dt2.Columns.Add("termid", typeof(Int32));
dt2.Columns.Add("faultid", typeof(Int32));
dt1.Rows.Add(1,2,"desc");
dt1.Rows.Add(3, 4, "desc");
dt1.Rows.Add(5, 6, "desc");
dt2.Rows.Add(1, 2);
dt2.Rows.Add(3, 4);
dt2.Rows.Add(7, 8);
var view = new DataView(dt1);
DataTable dt3 = view.ToTable(true, "termid", "faultid");
var diff =dt2.AsEnumerable().Except(dt3.AsEnumerable(), DataRowComparer.Default);
foreach (var row in diff)
{
Console.WriteLine(row["termid"] + " " + row["faultid"]);
}
As you said : I want to retrieve those values which are present in Datatable 2
but not in Datatable 1...based on two columns `(termid,faultid)`
Translation according to the context of question : You have two tables dtOutput and dtOpenEvent. You want to get values of dtOutput in a third table such that no row of third table has same value with first two cells of any row of dtOpenEvent. Then here it is
DataTable dt3 = new DataTable();
dt3.Columns.Add("termid");
dt3.Columns.Add("faultid");
int nr = 0;
for (int i = 0; i < dtOutput.Rows.Count; i++)
{
bool found = false;
for (int j = 0; j < dtOpenEvent.Rows.Count; j++)
{
if (dtOutput.Rows[i][0] == dtOpenEvent.Rows[j][0]
&& dtOutput.Rows[i][1] == dtOpenEvent.Rows[j][1])
{
found = true;
break;
}
}
if (!found)
{
dt3.Rows.Add(dt3.NewRow());
dt3.Rows[nr][0] = dtOutput.Rows[i][0];
dt3.Rows[nr][1] = dtOutput.Rows[i][1];
nr++;
}
}
I have a datatable with 20 columns. But i don't need all the columns for the current processing except 5. So i did the below to remove the columns
List<string> clmnames = new List<string>() { "clm6","clm7"..."clm20" };
foreach (string dcName in clmnames)
{
TestAndRemoveColumn(dcName, ds.Tables["TestTable"]);
}
private void TestAndRemoveColumn(string dcName,DataTable datatable)
{
DataColumnCollection dcCollection = datatable.Columns;
if (dcCollection.Contains(dcName))
{
dcCollection.Remove(dcName);
}
}
Instead of looping through the 15 times is there any other way to achieve using easily ?
try this
List<string> listtoRemove = new List<string> { "CLM6", "CLM7", "CLM20" };
for (int i = dt.Columns.Count - 1; i >= 0; i--)
{
DataColumn dc = dt.Columns[i];
if (listtoRemove.Contains(dc.ColumnName.ToUpper()))
{
dt.Columns.Remove(dc);
}
}
In some scenarios may be preferable to clone DataTable and specify columns to copy.
DataView view = new DataView(table);
DataTable table2 = view.ToTable(false, "clm6", "clm7", ...);
Problem seems to be in your code, you get all the comlumns from the datatable then remove the columns but you have not again assign the columns to that datatable
first you get columns
DataColumnCollection dcCollection = datatable.Columns; // get cols
if (dcCollection.Contains(dcName))
{
dcCollection.Remove(dcName); /// remove columns
// but you have not updated you datatable columns.
here should be something like this
datatable.Columns = dcCollection; /// i don't know this will work or not check it
}
Try this
DataTable dt;
dt.Columns.Remove("columnName");
dt.Columns.RemoveAt(columnIndex);
you can use them as
private void TestAndRemoveColumn(string dcName,DataTable datatable)
{
DataTable dt = datatable;
dt.Columns.Remove("dcName");
}
Alternatively you can select only the required columns(Only 5 in your case) like this.
DataTable dt = new DataTable();
dt.Columns.Add("ID");
dt.Columns.Add("Value");
dt.Rows.Add("1", "One");
dt.Rows.Add("2", "Two");
string[] arr= new string[1];
arr[0] = "Value";//add the required columns to the array
//return only the required columns.
DataTable dt2 = dt.DefaultView.ToTable(false, arr);
You could join the columns you want remove with the available columns:
var keepColNames = new List<String>(){ "clm5" };
var allColumns = tbl.Columns.Cast<DataColumn>();
var allColNames = allColumns.Select(c => c.ColumnName);
var removeColNames = allColNames.Except(keepColNames);
var colsToRemove = from r in removeColNames
join c in allColumns on r equals c.ColumnName
select c;
while (colsToRemove.Any())
tbl.Columns.Remove(colsToRemove.First());
If you know that you only have few remaining columns, you could add the column(s):
var colsToAdd = (from keepCol in keepColNames
join col in tbl.Columns.Cast<DataColumn>()
on keepCol equals col.ColumnName
select col).ToList();
tbl.Columns.Clear();
foreach (var colToAdd in colsToAdd)
tbl.Columns.Add(colToAdd);