How to groupby resultset with linq? - c#

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());
}
}
}

Related

How to use group by in rows?

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();
}
}
}

Return Unique values and Sum LINQ

I have two tables:
Retailers
Invoices
Retailers has two columns:
1.1. RetailerID
1.2. RetailerName
Invoices has three columns:
2.1. InvoiceID
2.2. InvoiceProfit
2.3. RetailerID
Retailers.RetailerID is linked to Invoices.RetailerID (one-to-many).
What I want to do is write a linq (or in the form of a lambda exp) that returns Retailer.RetailerID, Retailer.RetailerName, Invoice.InvoiceProfit.
I can do this like so:
var retailers = from r in db.Retailers select t;
var invoices = from i in db.Invoices select i;
var retailersAndInvoices = from r in retailers join i in invoices on r.RetailerID equals i.RetailerID select new {t.RetailerName, i.InvoiceProfit};
I want to return only Distinct RetailerNames and the Sum of all Invoices.InvoiceProfit next to each one - the purpose being "Top Ten Retailers"!
How can i do this?
Use GroupBy to convert a flat list to groups by RetailerName
Use Sum(i => i.InvoiceProfit) to compute totals
Use new { ... } to pair up retailers with their profit totals
Use OrderByDescending(p => p.TotalProfit) to get high-profit retailers to the top
Use Take(10) to limit the list to ten items.
Overall query would look like this:
var topTen = retailersAndInvoices
.GroupBy(ri => ri.RetailerName)
.Select(g => new {
Retailer = g.Key
, TotalProfit = g => g.Sum(i => i.InvoiceProfit)
})
.OrderByDescending(p => p.TotalProfit)
.Take(10)
.ToList();
I use combination of lambda and linq. See msdn : https://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication28
{
class Program
{
static void Main(string[] args)
{
DataTable retailers = new DataTable();
retailers.Columns.Add("RetailerID", typeof(int));
retailers.Columns.Add("RetailerName", typeof(string));
retailers.Rows.Add(new object[] { 123, "abc" });
retailers.Rows.Add(new object[] { 124, "abd" });
retailers.Rows.Add(new object[] { 125, "abe" });
DataTable invoices = new DataTable();
invoices.Columns.Add("InvoiceID", typeof(int));
invoices.Columns.Add("InvoiceProfit", typeof(decimal));
invoices.Columns.Add("RetailerID", typeof(int));
invoices.Rows.Add(new object[] { 100, 200, 123 });
invoices.Rows.Add(new object[] { 101, 201, 123 });
invoices.Rows.Add(new object[] { 102, 202, 123 });
invoices.Rows.Add(new object[] { 103, 203, 123 });
invoices.Rows.Add(new object[] { 104, 204, 124 });
invoices.Rows.Add(new object[] { 105, 205, 124 });
invoices.Rows.Add(new object[] { 106, 206, 124 });
invoices.Rows.Add(new object[] { 107, 207, 125 });
invoices.Rows.Add(new object[] { 108, 208, 125 });
invoices.Rows.Add(new object[] { 109, 209, 125 });
var retailersAndInvoices = (from r in retailers.AsEnumerable()
join i in invoices.AsEnumerable() on r.Field<int>("RetailerID") equals i.Field<int>("RetailerID")
select new { name = r.Field<string>("RetailerName"), profit = i.Field<decimal>("InvoiceProfit") })
.GroupBy(x => x.name)
.Select(x => new { name = x.Key, totalProfit = x.Select(y => y.profit).Sum() }).ToList();
}
}
}

Convert row values into columns using LINQ in c#

I have a list as below
PillarId Quarter Feature
1 Q12106 France
1 Q12016 Germany
1 Q22016 Italy
1 Q32016 Russia
2 Q22016 India
2 Q32016 USA
3 Q22016 China
3 Q32016 Australia
3 Q32016 New Zeland
3 Q42016 Japan
I want convert this into a list which looks like this
pillarId Q12016 Q22016 Q32016 Q42016
1 France Italy Russia
1 Germany
2 India USA
3 China Australia Japan
3 New Zeland
Can anybody suggest some sample code
Thanks
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication16
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("PillarId", typeof(int));
dt.Columns.Add("Quarter", typeof(string));
dt.Columns.Add("Feature", typeof(string));
dt.Rows.Add(new object[] {1, "Q12116", "France"});
dt.Rows.Add(new object[] {1, "Q12116", "Germany"});
dt.Rows.Add(new object[] {1, "Q22116", "Italy"});
dt.Rows.Add(new object[] {1, "Q32116", "Russia"});
dt.Rows.Add(new object[] {2, "Q22116", "India"});
dt.Rows.Add(new object[] {2, "Q32116", "USA"});
dt.Rows.Add(new object[] {3, "Q22116", "China"});
dt.Rows.Add(new object[] {3, "Q32116", "Austrailia"});
dt.Rows.Add(new object[] {3, "Q32116", "New Zeland"});
dt.Rows.Add(new object[] {3, "Q42116", "Japan"});
string[] uniqueQuarters = dt.AsEnumerable().Select(x => x.Field<string>("Quarter")).Distinct().ToArray();
var groups = dt.AsEnumerable()
.GroupBy(x => x.Field<int>("PillarId")).Select(x => x.GroupBy(y => y.Field<string>("Quarter")).Select(z => new { id = x.Key, quarter = z.Key, feature = z.Select((a,i) => new { feature = a.Field<string>("Feature"), index = i}).ToList()}).ToList()).ToList();
DataTable pivot = new DataTable();
pivot.Columns.Add("PillarId", typeof(int));
foreach (string quarter in uniqueQuarters)
{
pivot.Columns.Add(quarter, typeof(string));
}
foreach (var group in groups)
{
int maxNewRows = group.Select(x => x.feature.Count()).Max();
for (int index = 0; index < maxNewRows; index++)
{
DataRow newRow = pivot.Rows.Add();
foreach (var row in group)
{
newRow["PillarId"] = row.id;
newRow[row.quarter] = row.feature.Skip(index) == null || !row.feature.Skip(index).Any() ? "" : row.feature.Skip(index).First().feature;
}
}
}
}
}
}
This should work.
public struct temp
{
public int PillarID;
public string Quarter;
public string Feature;
}
static void Main(string[] args)
{
List<temp> list = new List<temp>
{
new temp {PillarID = 1, Quarter= "Q12106", Feature = "France"},
new temp {PillarID = 1, Quarter= "Q12106", Feature = "Germany"},
new temp {PillarID = 1, Quarter= "Q22016", Feature = "Italy"},
new temp {PillarID = 1, Quarter= "Q32016", Feature = "Russia"},
new temp {PillarID = 2, Quarter= "Q22016", Feature = "India"},
new temp {PillarID = 2, Quarter= "Q32016", Feature = "USA"},
new temp {PillarID = 3, Quarter= "Q22016", Feature = "China"},
new temp {PillarID = 3, Quarter= "Q32016", Feature = "Australia"},
new temp {PillarID = 3, Quarter= "Q32016", Feature = "New Zeland"},
new temp {PillarID = 3, Quarter= "Q42016", Feature = "Japan"}
};
IEnumerable<IGrouping<string, temp>> byQuarter = list.GroupBy(x => x.Quarter);
Console.WriteLine(byQuarter.ToString());
}
It does order them in the wanted way, but displaying it isn't so simple. I'm currently trying to make it display nicely
You can use dynamic keyword to create object as per your requirement at run time. I am not inserting multiple entries for a pillerId. In my case i am adding multiple features in quarter field for a given pillerId. Please look into below code for the same:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Dynamic;
namespace Test
{
public class Piller
{
public int PillarId;
public string Quarter;
public string Feature;
public static List<dynamic> Generate()
{
List<Piller> pillers = new List<Piller>();
pillers.Add(new Piller { PillarId = 1, Quarter = "Q12106", Feature = "France" });
pillers.Add(new Piller { PillarId = 1, Quarter = "Q12106", Feature = "Germany" });
pillers.Add(new Piller { PillarId = 1, Quarter = "Q22016", Feature = "Italy" });
pillers.Add(new Piller { PillarId = 1, Quarter = "Q32016", Feature = "Russia" });
pillers.Add(new Piller { PillarId = 2, Quarter = "Q22016", Feature = "Italy" });
pillers.Add(new Piller { PillarId = 2, Quarter = "Q32016", Feature = "USA" });
pillers.Add(new Piller { PillarId = 3, Quarter = "Q22016", Feature = "China" });
pillers.Add(new Piller { PillarId = 3, Quarter = "Q32016", Feature = "Australia" });
pillers.Add(new Piller { PillarId = 3, Quarter = "Q32016", Feature = "New Zeland" });
pillers.Add(new Piller { PillarId = 3, Quarter = "Q42016", Feature = "Japan" });
var pillerIds = (from p in pillers
select p.PillarId).Distinct();
var quarters = (from p in pillers
select p.Quarter).Distinct();
List<dynamic> transformedData = new List<dynamic>();
foreach (var pillerId in pillerIds)
{
var data = new ExpandoObject() as IDictionary<string, Object>;
data.Add("pillerId",pillerId);
foreach (var quarter in quarters)
{
var features = (from p in pillers
where p.PillarId == pillerId && p.Quarter == quarter
select p.Feature);
data.Add(quarter,features);
}
transformedData.Add(data);
}
return transformedData;
}
public static void Print(List<dynamic> data)
{
var dic = data[0] as IDictionary<string, Object>;
foreach (var field in dic.Keys)
{
Console.Write(field+" ");
}
Console.WriteLine();
foreach (dynamic record in data)
{
dic = record as IDictionary<string, Object>;
foreach (var field in dic.Keys)
{
if (field == "pillerId")
Console.Write(dic[field] + " ");
else
{
var value = dic[field];
if (value == null)
{
Console.Write(" ");
}
else
{
StringBuilder sb = new StringBuilder();
foreach (var item in (value as IEnumerable<string>))
{
if (sb.Length == 0)
sb.Append(item);
else
sb.Append(","+item);
}
Console.Write(sb.ToString());
}
}
Console.Write(" ");
}
Console.WriteLine();
}
}
}
}

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();
}
}
}

Aggregate hours between two dates with IQueryable from DB

I have table with these columns:
id int, name string, startDate DateTime, endDate DateTime
I want to get from DB SUM of HOURS between these dates for all records.
I use IQueryable, but I don't know how correctly form the query..
public int GetSumOfHours(int? assignedProjectId)
{
var query = GetAll(); //GetAll() returns IQueryable<T>,
if (assignedProjectId.HasValue)
{
query = query.Where(solution => solution.AssignedProject.Id == assignedProjectId.Value);
}
// Get sum of hours ----> query = query...??
}
Thanks for HELP !
Try something like below. Get seconds and then sum the seconds.
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 query = new DataTable();
query.Columns.Add("id", typeof(int));
query.Columns.Add("name", typeof(string));
query.Columns.Add("startDate", typeof(DateTime));
query.Columns.Add("endDate", typeof(DateTime));
query.Rows.Add(new object[] { 1, "John", DateTime.Parse("1/1/1 0:0:12"), DateTime.Parse("1/1/1 0:1:12") });
query.Rows.Add(new object[] { 1, "John", DateTime.Parse("1/1/1 0:3:12"), DateTime.Parse("1/1/1 0:5:12") });
query.Rows.Add(new object[] { 2, "Bob", DateTime.Parse("1/1/1 0:0:12"), DateTime.Parse("1/1/1 0:1:12") });
query.Rows.Add(new object[] { 2, "Bob", DateTime.Parse("1/1/1 0:0:12"), DateTime.Parse("1/1/1 0:1:12") });
var totalSeconds = query.AsEnumerable()
.Where(x => x.Field<int>("id") == 1)
.Select(x => (x.Field<DateTime>("endDate") - x.Field<DateTime>("startDate")).TotalSeconds).Sum();
}
}
}
​
You can use some math and the Sum() method:
public class hours
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
...
List<hours> allHrs = new List<hours>{
new hours{Start = DateTime.Now.AddHours(-3.2), End = DateTime.Now.AddHours(-2)},
new hours{Start = DateTime.Now.AddHours(-3.9), End = DateTime.Now.AddHours(-2.03)},
new hours{Start = DateTime.Now.AddHours(-3.8), End = DateTime.Now.AddHours(-2.9)}
};
//Project a new collection with the math done for number of minutes in each row
var mins = from h in allHrs
select new {nbrMinutes = (h.End - h.Start).Minutes};
//Sum all rows, divide by 60 to get hours
Double total = mins.Sum (s => s.nbrMinutes / 60.0 );
Console.WriteLine(total);
You could modify CrowCoder's example to sum the hours directly using TimeSpan and methods Subtract and TotalHours:
{
DateTime d = DateTime.Today;
List<hours> exampleHours = new List<hours>{
new hours{Start = d.AddHours(-3.2), End = d.AddHours(-2)},
new hours{Start = d.AddHours(-3.9), End = d.AddHours(-2.03)},
new hours{Start = d.AddHours(-3.8), End = d.AddHours(-2.9)}
};
double totalHours =
(from h in exampleHours
select new {allHours = (h.End.Subtract(h.Start).TotalHours)})
.Sum(t => t.allHours);
Console.WriteLine(totalHours.ToString());
}

Categories