using distinct in DataTable.Select function - c#

I have a data table and I want to populate two extra datatables using this datatable,here is a simple form of my table
My data table columns are
[name][family][id][propertyid][propertyEnergy]
John smith 1 12 Gas
John smith 1 13 Gas
John smith 1 14 null
John smith 1 15 Gas
Hannah smith 2 16 Gas
Hannah smith 2 17 Gas
Hannah smith 2 18 Gas
I want to use this query in datatable select distinct [name][family][id] from table
which results
John smith 1
Hannah smith 2
and again I use this query in another datatable select [id][propertyid][propertyEnergy] from table which results
1 12 Gas
1 13 Gas
1 14 null
1 15 Gas
2 16 Gas
2 17 Gas
2 18 Gas
I searched and found that I can DataTable.Select but examples that I have seen shows that I can only add Where sentense to DataTable.Select and I have no idea how to perform things like Distinct in it,
Can you please help me or give me some hints how to do it?
Thank you so much

I'd use Linq-To-DataTable instead:
var distinctNames = table.AsEnumerable()
.Select(row => new
{
Name = row.Field<string>("Name"),
Family = row.Field<string>("Family"),
ID = row.Field<int>("ID")
})
.Distinct();
var distinctProperties = table.AsEnumerable()
.Select(row => new
{
ID = row.Field<int>("ID"),
PropertyID = row.Field<int>("PropertyID"),
PropertyEnergy = row.Field<int>("PropertyEnergy")
})
.Distinct();
If you need two additional DataTables you have to create and fill them manually since the columns are different than the main-table. You can fill them in a loop from the queries above.
This should work as it is:
string[] nameColumns = { "Name", "Family", "ID" };
DataTable tblNames = table.Clone();
var removeColumns = tblNames.Columns.Cast<DataColumn>()
.Where(c => !nameColumns.Contains(c.ColumnName)).ToList();
removeColumns.ForEach(c => tblNames.Columns.Remove(c));
foreach (var x in distinctNames)
tblNames.Rows.Add(x.Name, x.Family, x.ID);
string[] propertyColumns = { "ID", "PropertyID", "PropertyEnergy" };
DataTable tblProperties = table.Clone();
removeColumns = tblProperties.Columns.Cast<DataColumn>()
.Where(c => !propertyColumns.Contains(c.ColumnName)).ToList();
removeColumns.ForEach(c => tblProperties.Columns.Remove(c));
foreach (var x in distinctProperties)
tblProperties.Rows.Add(x.ID, x.PropertyID, x.PropertyEnergy);

Related

C# linq lambda expression group by multiple columns and get the first items of group

This is how my table data looks like ,
Id C1 C2 C3
-------------------------------------------------
1 John 1990 A
2 John 1990 B
3 John 2000 C
4 Mary 2001 D
5 Mary 2010 E
6 Mary 2010 F
7 Jack 2010 G
8 Jack 2010 H
9 Jack 2011 I
What I want to do is I want to group by C1,C2 columns and get the first items of each group which is order by C3 column descending.
So the result will be like
Id C1 C2 C3
-------------------------------------------------
1 John 1990 B
3 John 2000 C
4 Mary 2001 D
5 Mary 2010 F
7 Jack 2010 H
9 Jack 2011 I
And I also want to filter row number 2 to 5 using skip and take function.
So the Final result should be like
Id C1 C2 C3
-------------------------------------------------
3 John 2000 C
4 Mary 2001 D
5 Mary 2010 F
7 Jack 2010 H
What I have tried is
await data.GroupBy(d => new { d.C1, d.C2 }).Skip(1).Take(4).FirstAsync();
But it only retrun one row.
How can I solve for this ?
You need to do a Select and since there will be multiple results you'd need to use ToListAsync instead.
data.GroupBy(d => new { d.C1, d.C2 })
.Select(grp => grp.First())
.Skip(1)
.Take(4)
.ToListAsync();
You can do it by,
using System;
using System.Linq;
using System.Collections.Generic;
...
var result = await data.GroupBy(d => new { d.C1, d.C2 }) //Group by C1 and C2
.Select(s => s.First()) //Select first record from each Group
.Skip(1) //Skip first record
.Take(4) //Pick up next 4 records
.ToListAsync() //Convert 4 records to List.

Linq query to sum by group

I have a data table like this:
Category Description CurrentHours CTDHours
LC1 Cat One 5 0
LC2 Cat Two 6 0
LC3 Cat Three 18 0
LC1 Cat One 0 9
LC2 Cat Two 0 15
LC4 Cat Four 0 21
That I need to Group and Sum to this:
Category Description CurrentHours CTDHours
LC1 Cat One 5 14
LC2 Cat Two 6 21
LC3 Cat Three 18 0
LC4 Cat Four 0 21
In other words I need to sum the two Hours columns grouping by the Category and Description columns.
I know that I could build a new table and loop through the existing data and sum the data into the new table but I thought there would be an easier way to do it using Linq. I've googled it for a few hours but all the examples I found didn't seem to fit what I was trying to do.
BTW, the odbc driver that creates the data table does not have the capability for sub queries, etc. or I would have just done it using SQL.
Use anonymous object to group by category and description. Here is Linq to DataSet query which returns grouped hours:
from r in table.AsEnumerable()
group r by new {
Category = r.Field<string>("Category"),
Description = r.Field<string>("Description")
} into g
select new {
Category = g.Key.Category,
Description = g.Key.Description,
CurrentHours = g.Sum(x => x.Field<int>("CurrentHours"),
CTDHours = g.Sum(x => x.Field<int>("CurrentHours") + x.Field<int>("CTDHours"))
}
If you are querying database (not clear from question):
from r in context.Table
group r by new {
r.Category,
r.Description
} into g
select new {
g.Key.Category,
g.Key.Description,
CurrentHours = g.Sum(x => x.CurrentHours),
CTDHours = g.Sum(x => x.CTDHours + x.CurrentHours)
}
You need to sum CurrentHours and CTDhours, so -
select new {
...
CTDHours = g.Sum(x => x.Field<int>("CTDHours") + g.Sum(x => x.Field<int>("CurrentHours")
}

Split a DataTable into 2 or more DataTables based on Column value

I have a DataTable called "DTHead" which has the following records,
MIVID Quantity Value
------ ---------- --------
1 10 3000
1 20 3500
1 15 2000
2 20 3000
2 50 7500
3 25 2000
Here, I need to split the above DataTable into three tables based on the MIVID such as follows;
DTChild1:
MIVID Quantity Value
------- ---------- ---------
1 10 3000
1 20 3500
1 15 2000
DTChild2:
MIVID Quantity Value
------- ---------- ---------
2 20 3000
2 50 7500
DTChild3:
MIVID Quantity Value
------- ---------- ---------
3 25 2000
Suppose, if the Header DataTable contains 4 different MIVID means, then 4 Child DataTable should be created based on the MIVID. How to do this?
Use LINQ to DataTable to group the first column by GroupBy, and use method CopyToDataTable to copy list of rows to DataTable
List<DataTable> result = DTHead.AsEnumerable()
.GroupBy(row => row.Field<int>("MIVID"))
.Select(g => g.CopyToDataTable())
.ToList();
Then you can get the result as a list of DataTables as you expected.
DataTable tbl = new DataTable("Data").AsEnumerable()
.Where(r => r.Field<int>("ParentId") == 1) // ParentId == 1
.Where(r => r.Field<int>("Id") > 3) // Id > 3
.Where(r => r.Field<string>("Name").Contains("L")) // Name contains L
.OrderBy(r => r.Field<int>("Id")) // Order by Id
.CopyToDataTable();

linq group by, order by

I have the following list
ID Counter SrvID FirstName
-- ------ ----- ---------
1 34 66M James
5 34 66M Keith
3 55 45Q Jason
2 45 75W Mike
4 33 77U Will
What I like to do is to order by ID by ascending and then
get the first value of Counter, SrvID which are identical (if any).
So the output would be something like:
ID Counter SrvID FirstName
-- ------ ----- ---------
1 34 66M James
2 45 75W Mike
3 55 45Q Jason
4 33 77U Will
Note how ID of 5 is removed from the list as Counter and SrvID was identical to what I had for ID 1 but as ID 1 came first
I removed 5.
This is what I would do but not working
var result = (from ls in list1
group ts by new {ls.Counter, ls.SrvID}
order by ls.ID
select new{
ls.ID,
ls.Counter.FirstOrDefault(),
ls.SrvID.First,
ls.FirstName}).ToList()
list1.GroupBy(item => new { Counter = item.Counter, SrvID = item.SrvID })
.Select(group => new {
ID = group.First().ID,
Counter = group.Key.Counter,
SrvID = group.Key.SrvID,
FirstName = group.First().FirstName})
.OrderBy(item => item.ID);
Group the records up, and pick a winner from each group.
var query =
from record in list1
group record by new {record.Counter, record.SrvID } into g
let winner =
(
from groupedItem in g
order by groupedItem.ID
select groupedItem
).First()
select winner;
var otherQuery = list1
.GroupBy(record => new {record.Counter, record.SrvID })
.Select(g => g.OrderBy(record => record.ID).First());

LinQ with Count and Where condition

Hihi, I have a table with the following data:
SampleID | SampleKey | SampleData
1 | 1 | abc
1 | 2 | def
2 | 1 | xxx
2 | 3 | yyy
3 | 3 | zzz
3 | 4 | qqq
I would like to retrieve all rows with at least one SampleKey as 3, which should give me
2 | 1 | xxx
2 | 3 | yyy
3 | 3 | zzz
3 | 4 | qqq
both SampleID with 2 and 3 should be returned as they are considered as one pair.
Pls advice how can I achieve this? May thanks!
I would suggest not using Contains, but the built-in Join method for performance reasons..
var keys = source.Where(s => s.SampleKey == 3).Select(s => s.SampleID).Distinct();
var result = source.Join(keys, s => s.SampleID, k => k, (s, k) => s);
var idsToSelect = from x in MyTable where x.SampleKey == 3 select x.SampleID;
var results = from x in MyTable where idsToSelect.Contains(x.SampleID) select x;
Not sure I fully understand the question, but here's my bid:
var results = from r in MyTable
where r.SampleID == 3 || r.SampleKey == 3
select r;
var nResults = results.Count();
Though, I'll be honest, I don't know why your column named ID isn't actually an ID. Never mind, I think I get it now. You're linking the two columns as a unique key (or so I hope).
--
EDIT
Nappy actually had a great solution, and I'm not sure why s/he deleted it. Grabbing all rows with a 3 then rejoining them works perfect.
In the non-SQL-like syntax you could use
var groupsById = MyData.GroupBy(x => x.SampleId);
var groupsThatMatch = groupsById.Where(g => g.Any(x => x.SampleKey == 3));
var allRows = groupsThatMatch.SelectMany(g => g);
i.e group by ID, find the groups that match then flatten those back into rows. I don't know the SQL-like syntax, sorry.
DataTable dt = new System.Data.DataTable();
dt.Columns.Add("SampleID", typeof(Int32));
dt.Columns.Add("SampleKey", typeof(Int32));
dt.Columns.Add("SampleData", typeof(string));
dt.Rows.Add(1, 1, "abc");
dt.Rows.Add(1, 2, "def");
dt.Rows.Add(2, 1, "xxx");
dt.Rows.Add(2, 3, "yyy");
dt.Rows.Add(3, 3, "zzz");
dt.Rows.Add(3, 4, "qqq");
var result = from DataRow myRow in dt.Rows
where (int)myRow["SampleID"] == 3 || (int)myRow["SampleKey"] == 3
select myRow;
You could probably do with:
var result = data.Where(
y => data.Where(x => x.SampleKey == 3)
.Select(x => x.SampleID)
.Contains(y.SampleID));

Categories