How to use group by in rows? - c#

How to group the below data ? as I am looping through the collection and it gives me only 1 row as there is no grouping in place.
I have to group the below records based on Id column and if there are repeating Ids ? I have to populate model with that many rows.
id name trID trName
1 a 5 x
2 b 6 y
2 c 7 z
3 d 8 m
3 e 9 n
4 f 10 0
class DataModel
{
Public int Id{get;set;}
Public string name{get;set;}
Public RepeatedIDs RepeatedIDCollection{get;set;}
}
class RepeatedIDs
{
Public int trId{get;set;}
Public string trname{get;set;}
}
(from DataRow dr in dataTable.Rows
select new IdModel
{
Id = Convert.ToInt32(dr["ID"]),
name = Convert.ToString(dr["name"]),
// need to group the records here and populate below mode with that many rows
RepeatedIDCollection = new List<RepeatedIDs>
{
new RepeatedIDs()
{
trId = Convert.ToInt32(dr["trId"]),
trname = Convert.ToString(dr["trname"]),
}
}
}).ToList();

What you need is:
var query = dataTable.AsEnumerable()
.GroupBy(r => r.Field<int>("ID"))
.Select(grp => new DataModel
{
Id = grp.Key,
name = String.Join(",", grp.Select(t => t.Field<string>("name"))), //Because there could be multiple names
RepeatedIDCollection = grp.Select(t => new RepeatedIDs
{
trId = t.Field<int>("trID"),
trname = t.Field<string>("trName")
}).ToList(),
});
What this query is doing:
Grouping the data based on ID column in DataTable
Later selecting an object of DataModel
The Id in DataModel is the key from group.
There will be multiple names in the grouped data
Later it creates a List<RepeatedIDCollection> by getting the trId and trname from grouped collection.
Make sure you specify the correct types in Field method.

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("name", typeof (string));
dt.Columns.Add("trID", typeof (int));
dt.Columns.Add("trName", typeof (string));
dt.Rows.Add(new object[] { 1,"a", 5,"x"});
dt.Rows.Add(new object[] { 2,"b", 6,"y"});
dt.Rows.Add(new object[] { 2,"c", 7,"z"});
dt.Rows.Add(new object[] { 3,"d", 8,"m"});
dt.Rows.Add(new object[] { 3,"e", 9,"n"});
dt.Rows.Add(new object[] { 4,"f", 510,"0"});
var groups = dt.AsEnumerable().GroupBy(x => x.Field<int>("id")).ToList();
}
}
}

Related

Linq group by with return count

Net core and Linq. I have table like below
Orders Table
OrderId Status
1 New
2 New
3 In Progress
4 In Progress
5 Closed
6 Closed
I have below model
public class SummaryEntity
{
public int New { get; set; }
public int InProgress { get; set; }
public int Closed { get; set; }
}
Then I need to return and bind to below model like below
New : 2
InProgress : 2
Closed : 2
I have tried something like below
SummaryEntity result = (from item in Orders
group item by new { item.Status } into g
select new SummaryEntity{ //not sure how to get count and assign it to model }
);
I am finding hard to group by and assign values to model. Can someone help me to write query. Any help would be appreciated. Thank you
You've already grouped the items by status, so g.Key will contain the status value and g itself is an enumerable of the grouped items. If you want to calculate their count use Count(), eg :
var counts = from item in Orders
group item by new { item.Status } into g
select new {status=g.Key, count=g.Count()};
This will return one object per status value with its count. Getting different columns for each status is essentially pivoting, converting the rows to columns.
In this case though, where you know the status names in advance, you can convert the results into a dictionary and retrieve the counts by name, eg :
var dict=counts.ToDictionary(x=>x.status,x=>x.count);
var model= new SummaryEntity
{
New = dict.TryGetValue("New",out var c_n)
? c_n : 0,
InProgress = dict.TryGetValue("InProgress",out var c_p)
? c_p : 0,
Closed = dict.TryGetValue("Closed",out var c_c)
? c_c : 0,
};
Dictionary.TryGetValue is used to avoid exceptions if a status value is missing
You model makes no sense. You have only status not New, InProgress, Closed. Try following :
DataTable dt = new DataTable();
dt.Columns.Add("OrderId", typeof(int));
dt.Columns.Add("Status", typeof(string));
dt.Rows.Add( new object[] { 1, "New"});
dt.Rows.Add( new object[] { 2, "New"});
dt.Rows.Add( new object[] { 3, "In Progress"});
dt.Rows.Add( new object[] { 4, "In Progress"});
dt.Rows.Add( new object[] { 5, "Closed"});
dt.Rows.Add( new object[] { 6, "Closed"});
var results = dt.AsEnumerable()
.GroupBy(x => x.Field<string>("Status"))
.Select(x => new { status = x.Key, count = x.Count() })
.ToList();

Adding values ​to the correct subject with LINQ C# Code [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I need someone's help because I've been here for a while with this problem and can't figure it out. Would be super cool if you could help me!
What the list looks like now (1 object):
datatype:
Id1 = string
Id2 = string
Value = string
**The reason value is a String is because it is a result of a percentage of a previous method
I would like to add the values ​​of the 2 unique id in the new table.
So with an foreach loop from the list above i want as result as below:
If anyone could help me that would be great! thanks in advance!!!
You can solve your problem by grouping objects by Id1 and Id2. By the way, you sould share the test model and initialize the collection on problem definition next time πŸ˜‰
internal class TestModel
{
public TestModel(string id1, string id2, string value)
{
Id1 = id1;
Id2 = id2;
Value = value;
}
public string Id1 { get; set; }
public string Id2 { get; set; }
public string Value { get; set; }
}
private void ProcessTest()
{
TestModel[] items = new TestModel[]
{
new TestModel("1","1","20.2"),
new TestModel("1","2","18.5"),
new TestModel("1","2","11.5"),
new TestModel("2","1","20"),
new TestModel("2","1","20"),
new TestModel("2","2","50"),
new TestModel("2","2","40"),
};
var result = items.GroupBy(w => new { w.Id1, w.Id2 })//group by Id1 and Id2
.Select(w => new TestModel(w.Key.Id1, w.Key.Id2,
w.Sum(q => Convert.ToDecimal(q.Value)).ToString()))//calculate the sum of group values
.ToArray();
}
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("Id1", typeof(int));
dt.Columns.Add("Id2", typeof(int));
dt.Columns.Add("Value", typeof(decimal));
dt.Rows.Add(new object[] { 1, 1, 20.2 });
dt.Rows.Add(new object[] { 1, 2, 18.5 });
dt.Rows.Add(new object[] { 1, 2, 11.5 });
dt.Rows.Add(new object[] { 2, 1, 20 });
dt.Rows.Add(new object[] { 2, 1, 20 });
dt.Rows.Add(new object[] { 2, 2, 50 });
dt.Rows.Add(new object[] { 2, 2, 40 });
var groups = dt.AsEnumerable()
.GroupBy(x => new { id1 = x.Field<int>("Id1"), id2 = x.Field<int>("Id2") })
.ToList();
DataTable results = dt.Clone();
foreach (var group in groups)
{
results.Rows.Add(new object[] { group.Key.id1, group.Key.id2, group.Sum(x => x.Field<decimal>("Value"))});
}
}
}
}

Group by column values with spaces Using linq

I have got this situation with a datatable like this
C1 C2 C3
A AA 4
BB 6
B CC 3
DD 3
EE 4
C FF 5
GG 5
and my output should be like this
C1 C2 C3
A AA,BB 10
B CC,DD,EE 10
C FF,GG 10
How can i group by the column with the space till the next value comes up
What i did was i took all the row itemarray and then using some string manipulation and regex got the row value as for the first two values like this and assigned to a variable in a query using Let
A,AA,BB,10|B,CC,DD,EE,10 but then i cannot add it using the
**DT.clone.rows.Add(x.split("|"c))* method as there its not incrementing and adding the whole joined string
Any other input where i can manipulate and add it (P.S i know linq is querying language)
Thank you for your time
You can use .GroupBy to get result needed
Here is your class:
public class Data
{
public string C1 { get; set; }
public string C2 { get; set; }
public int C3 { get; set; }
}
Imagine that you have list of Data objects, so your GroupBy expression will be following:
var result = list.GroupBy(g => g.C1, (a, b) => new {C1 = a, C2 = b.ToList()})
.Select(g => new
{
g.C1,
C2 = string.Join(",", g.C2.Select(m => m.C2)),
C3 = g.C2.Sum(m => m.C3)
})
.ToList();
A simple .GroupBy can give you expected result, Edited to handle Null or WhiteSpace Columns
var res = ListModel.Where(e => !string.IsNullOrWhiteSpace(e.C1)
&& !string.IsNullOrWhiteSpace(e.C2))
.GroupBy(e => e.C1).Select(e => new
{
e.Key,
c2 = string.Join(",", e.Select(x => x.C2).ToList()),
c3 = e.Sum(x => x.C3)
}).ToList();
Hello All first of all Thank you for your time and effort i Did this use case using this code
This gave me all row item array in string and than in the end with a little Split method i was able to add it to my datatable
String.Join("|",(System.Text.RegularExpressions.Regex.Replace(String.Join("|",(From roww In DT.AsEnumerable() Select String.Join(",",roww.ItemArray) ).ToList),"\|,",",")).Split("|"c).
Select(Function(q)CStr(q)+","+CStr(String.join("|",System.Text.RegularExpressions.Regex.Matches(CStr(q),"\d+").Cast(Of match)).Split("|"c).Sum(Function(r) CInt(r) ))).tolist),",\d+,",",")```
Try following code which is tested
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("C1", typeof(string));
dt1.Columns.Add("C2", typeof(string));
dt1.Columns.Add("C3", typeof(int));
dt1.Rows.Add(new object[] { "A", "AA", 4});
dt1.Rows.Add(new object[] { null, "BB", 6});
dt1.Rows.Add(new object[] { "B", "CC", 3});
dt1.Rows.Add(new object[] { null, "DD", 3});
dt1.Rows.Add(new object[] { null, "EE", 4});
dt1.Rows.Add(new object[] { "C", "FF", 5});
dt1.Rows.Add(new object[] { null, "GG", 5});
//replace nulls in column 1 with actual values
string previous = "";
foreach(DataRow row in dt1.AsEnumerable())
{
if (row.Field<string>("C1") == null)
{
row["C1"] = previous;
}
else
{
previous = row.Field<string>("C1");
}
}
DataTable dt2 = dt1.Clone();
var groups = dt1.AsEnumerable().GroupBy(x => x.Field<string>("C1")).ToList();
foreach (var group in groups)
{
dt2.Rows.Add(new object[] {
group.Key,
string.Join(",", group.Select(x => x.Field<string>("C2"))),
group.Select(x => x.Field<int>("C3")).Sum()
});
}
}
}
}
Yet another way using Skip, TakeWhile, and GroupBy extensions:
DataTable dt1 = new DataTable();
dt1.Columns.Add("C1", typeof(string));
dt1.Columns.Add("C2", typeof(string));
dt1.Columns.Add("C3", typeof(int));
//The output table.
DataTable dt2 = dt1.Clone();
dt1.Rows.Add(new object[] { "A", "AA", 3 });
dt1.Rows.Add(new object[] { null, "BB", 6 });
dt1.Rows.Add(new object[] { "B", "CC", 3 });
dt1.Rows.Add(new object[] { null, "DD", 3 });
dt1.Rows.Add(new object[] { null, "EE", 4 });
dt1.Rows.Add(new object[] { "C", "FF", 5 });
dt1.Rows.Add(new object[] { null, "GG", 6 });
var rows = dt1.Rows.Cast<DataRow>().AsEnumerable();
foreach (var row in rows.Where(r => r.Field<string>("C1") != null))
{
var indx = dt1.Rows.IndexOf(row) + 1;
var q = rows
.Skip(indx)
.TakeWhile(t => t.Field<string>("C1") == null)
.GroupBy(g => g.Field<string>("C1"))
.Select(g => new
{
C1 = row.Field<string>("C1"),
C2 = $"{row.Field<string>("C2")}, {string.Join(", ", g.Select(s => s.Field<string>("C2")))}",
C3 = row.Field<int>("C3") + g.Sum(s => s.Field<int>("C3")),
}).FirstOrDefault();
if (q != null)
dt2.Rows.Add(q.C1, q.C2, q.C3);
}
dataGridView1.DataSource = null;
dataGridView1.DataSource = dt2;
The idea behind this snippet is to:
Get the complete rows and iterate through them.
For each complete row, we get it's index from the original DataTable and add 1 to make a starting search point for the incomplete rows. The Skip extension is the method to achieve that.
The TakeWhile extension function gets the incomplete rows and stops at the next complete row.
The GroupBy extension function groups the incomplete rows to concatenate their C2 values and sum their C3 values, add the results to the values of the complete row and create a temporary anonymous object to hold these values.
Extract the anonymous object and add a new DataRow to the output DataTable.
And finally, bind the output DataTable to a DGV.
Happy 2020 for all.

How to groupby resultset with linq?

I have a stored procedure that looks like this:
SELECT UrlId, TypeId, DomainId, Url, d.OrgId AS orgId
FROM SystemUrls
JOIN Domaindata d ON d.Id = DomainId
It gives me this result:
in the code this looks like:
What I would like:
is to group this result on the domainId
so that I get two rows.
I have this class:
public class TestModel
{
public long DomainId { get; set; }
public List<SystemUrl> Urls { get; set; }
}
I am trying to get a resul like :
DomainId 79 Urls.count() = 2
DomainId 81 Urls.COunt = 2
My attempt:
var t =
(from u in urls
select
new TestModel
{
DomainId = u.DomainId,
Urls = new List<SystemUrl> {new SystemUrl {Url = u.Url}}
}).GroupBy(v => v.DomainId).ToList();
the problem is that I seem to get the domainId right but none of the other data seems to follow:
How can I solve this?
var t = urls.GroupBy(u => u.DomainId)
.Select(g => new TestModel{
DomainId = g.Key,
Urls = g.Select(u => new SystemUrl {Url = u.Url}).ToList()
})
.ToList();
Here is how you can do it:
var t = urls.GroupBy(v => v.DomainId).Select(g => new TestModel
{
DomainId = g.Key,
Urls = g.Select(u => new SystemUrl {Url = u.Url}).ToList()
}).ToList();
Put DataAdapter reults into a datatable . Then use following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
namespace ConsoleApplication49
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Urld", typeof(int));
dt.Columns.Add("Typeid", typeof(int));
dt.Columns.Add("DomainId", typeof(int));
dt.Rows.Add(new object[] { 13, 1, 79 });
dt.Rows.Add(new object[] { 14, 2, 79 });
dt.Rows.Add(new object[] { 15, 2, 81 });
dt.Rows.Add(new object[] { 16, 1, 81 });
var results = dt.AsEnumerable()
.GroupBy(x => x.Field<int>("DomainID"))
.ToDictionary(x => x.Key, y => y.Count());
}
}
}

Firebird group by period

I am pulling some historical data from Firebird database as below:
Product_ID Date Price
1 2001-01-01 10
1 2001-02-01 10
1 2001-03-01 15
1 2001-04-01 10
1 2001-05-01 20
1 2001-06-01 20
What I am trying to do is to extract the first for occurrence every price change.
Example of expected data set:
Product_ID Date Price
1 2001-01-01 10
1 2001-03-01 15
1 2001-04-01 10
1 2001-05-01 20
I know that on MSSQL I could leverage LAG for that. Is it possible to do that with Firebird?
You can try this, but be aware I didn't tested it:
CREATE PROCEDURE SP_Test
RETURNS (
Product_ID INTEGER,
Date DATE,
Price INTEGER
)
AS
DECLARE VARIABLE Last_Product_ID INTEGER;
DECLARE VARIABLE Last_Date DATE;
DECLARE VARIABLE Last_Price INTEGER;
BEGIN
FOR SELECT Product_ID, Date, Price
FROM xxxx
ORDER BY Product_ID, Date
INTO Product_ID, Date, Price
DO BEGIN
IF ((:Last_Product_ID IS NULL) OR
(:Last_Date IS NULL) OR
(:Last_Price IS NULL) OR
(:Product_ID <> :Last_Product_ID) OR
(:Price <> :Last_Price)) THEN
SUSPEND;
Last_Product_ID = :Product_ID;
Last_Date = :Date;
Last_Price = :Price;
END;
END;
in MoreLinq there is a Lag extension method but it is supported only in Linq to Objects...
What you can do, if you are looking for a C# linq answer for that you can:
Basically order your data the correct way and then add a row index for while price (and product_id) is still the same. Then group by it and select the min date.
int groupingIndex = 0;
int previousPrice = 0;
var response = data
.OrderBy(item => item.Product_ID)
.ThenBy(item => item.Date)
.Select(item =>
{
if (item.Price != previousPrice)
{
previousPrice = item.Price;
groupingIndex++;
}
return new { Index = groupingIndex, Item = item };
})
.GroupBy(item => new { item.Index, item.Item.Product_ID, item.Item.Price } )
.Select(group => new Record
{
Product_ID = group.Key.Product_ID,
Price = group.Key.Price,
Date = group.Min(item => item.Item.Date)
}).ToList();
And if you don't mind doing the operation in the C# and not the DB (and using a beta version of the MoreLinq) then:
int index = 0;
var result2 = data
.OrderBy(item => item.Product_ID)
.ThenBy(item => item.Date)
.Lag(1, (current, previous) => new { Index = (current.Price == previous?.Price ? index : ++index), Item = current })
.GroupBy(item => new { item.Index, item.Item.Product_ID, item.Item.Price })
.Select(group => new Record { Product_ID = group.Key.Product_ID, Price = group.Key.Price, Date = group.Min(item => item.Item.Date) })
.ToList();
This is a little complicated but it works
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Product_ID", typeof(int));
dt.Columns.Add("Date", typeof(DateTime));
dt.Columns.Add("Price", typeof(int));
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-01-01"), 10});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-02-01"), 10});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-03-01"), 15});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-04-01"), 10});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-05-01"), 20});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-06-01"), 20});
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-01-01"), 10 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-02-01"), 10 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-03-01"), 15 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-04-01"), 10 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-05-01"), 20 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-06-01"), 20 });
dt = dt.AsEnumerable().OrderBy(x => x.Field<DateTime>("Date")).CopyToDataTable();
List<DataRow> results = dt.AsEnumerable()
.GroupBy(g => g.Field<int>("Product_ID"))
.Select(g1 => g1.Select((x, i) => new { row = x, dup = (i == 0) || ((i > 0) && (g1.Skip(i - 1).FirstOrDefault().Field<int>("Price") != g1.Skip(i).FirstOrDefault().Field<int>("Price"))) ? false : true })
.Where(y => y.dup == false).Select(z => z.row)).SelectMany(m => m).ToList();
}
}
}

Categories