Datatable group by sum - c#

in a Queue i have datatables in the following format
some table in the Queue
Name Rank
AAA 9
BBB 5
CCC 1
DDD 5
some other table in the Queue
Name Rank
AAA 1
SSS 5
MMM 1
DDD 8
using LINQ need to process those tables table by table continously and add the results to a global DataTable in the following format:
Name Rank1 Rank2 Rank3 Rank>3
AAA 1 0 0 1
BBB 0 0 0 1
CCC 1 0 0 0
DDD 0 0 0 2
SSS 0 0 0 1
MMM 0 0 0 0
in the global table 4 columns state how many times a name was ranked in ranks 1,2,3 or >3.
now if the name already exists in global table i will not add it but only increment the rank count columns, and if does not exist then add it.
i've done this with nested looping but i wonder if anyone can help me with the LINQ syntax to do such thing,also will using LINQ make the process faster than with nested looping?
note that new tables are added to the Queue every second and i will be getting sometable from the Queue and process it to the global datatable

table1.AsEnumerable().Concat(table2.AsEnumerable())
.GroupBy(r => r.Field<string>("Name"))
.Select(g => new {
Name = g.Key,
Rank1 = g.Count(x => x.Field<int>("Rank") == 1),
Rank2 = g.Count(x => x.Field<int>("Rank") == 2),
Rank3 = g.Count(x => x.Field<int>("Rank") == 3),
OtherRank = g.Count(x => x.Field<int>("Rank") > 3)
}).CopyToDataTable();
You will need implementation of CopyToDataTable method where Generic Type T Is Not a DataRow.
A little optimized solution (single parsing and single loop over grouped ranks):
(from row in table1.AsEnumerable().Concat(table2.AsEnumerable())
group row by row.Field<string>("Name") into g
let ranks = g.Select(x => x.Field<int>("Rank")).ToList()
select new {
Name = g.Key,
Rank1 = ranks.Count(r => r == 1),
Rank2 = ranks.Count(r => r == 2),
Rank3 = ranks.Count(r => r == 3),
OtherRank = ranks.Count(r => r > 3)
}).CopyToDataTable();

Related

using group by in IEnumerable result

I have a query like below :
IEnumerable<qryTable1> templates = from t in db.qryTable1
where (t.GID == 1&&
t.RID == 4 && t.CID == "user")
select t;
Let say, Above query returns following result:
TId GId RId CID
1 1 1 a
1 1 2 a
2 1 1 a
2 1 2 a
3 1 1 a
3 1 2 a
Now I want to get result from the above query like below:(removing RId column so that result have duplicate entries having only TId,GId,CID and then group the result by TId).
TId GId CID
1 1 a
2 1 a
3 1 a
Also , I want to get the desired output in template object only.It means result should be in 'IEnumerable<qryTable1>' object.
var distinct = db.qryTable1.Select(r => new {Tid = r.TId, GId = r.GId, CId = r.CId})
.Distinct();

Linq to sql calculating a percentage of a group of items in query

New to Linq to SQL and novice in SQL. First post so please be gentle.
I have something similar to the following table I am querying into a dataGridView based on a date range in C#.
HeatNumber ChargeNumber Weight DOB
1 1 500 8/26/15
1 2 3500 8/26/15
1 3 2200 8/26/15
2 1 2000 8/27/15
2 2 1100 8/27/15
var query = from SU in dct.GetTable<ScrapInCharge>()
where ((SU.DOB >= dateTimePicker2.Value.Date) &&
(SU.DOB <= dateTimePicker1.Value.Date))
orderby SU.HeatNumber descending
select SU;
scrapInChargeBindingSource.DataSource = query;
I need to add a column that shows the percentage of the total HeatNumber weight that each ChargeNumber makes up. I did figure how to get the total weight of each heat by HeatNumber.
var TotalHeatWgt = from a in dct.ScrapInCharges
where ((a.DOB >= dateTimePicker2.Value.Date) &&
(a.DOB <= dateTimePicker1.Value.Date))
group a.Weight by a.HeatNumber
into b
select new { HeatNumber = b.Key, TotalWgt = b.Sum() };
I am currently stuck on how to combine this into a single C# query inserting a % of Heat column after the weight column. Can this be done or would I need to add an unbound column to the dataGridView % of Heat and iterate over the rows using my return values of the TotalHeatWgt query?
Simple attempt:
var results = data.Select(d => new {
d.HeatNumber,
d.ChargeNumber,
d.Weight,
Percent = 100.0 * d.Weight / data.Where(dd => dd.HeatNumber == d.HeatNumber).Sum(dd => dd.Weight)
});
or
from row in data
select new {
row.HeatNumber,
row.ChargeNumber,
row.Weight,
Percent = 100.0 * row.Weight / (from innerRow in data
where innerRow.HeatNumber == row.HeatNumber
select innerRow.Weight
).Sum()
};
The one below will be more performant (at least in memory, I'm not sure if it'll be better in SQL):
var results = data.GroupBy (d => d.HeatNumber)
.SelectMany (grp => grp.Select(row => new {
row.HeatNumber,
row.ChargeNumber,
row.Weight,
Percent = 100.0 * row.Weight / grp.Sum(dd => dd.Weight)
})
);
And with query syntax:
from row in data
group row by row.HeatNumber into grp
from innerRow in grp
select new {
innerRow.HeatNumber,
innerRow.ChargeNumber,
innerRow.Weight,
Percent = 100.0 * innerRow.Weight / grp.Sum(dd => dd.Weight)
};
They both print this result:
HeatNumber ChargeNumber Weight Percent
1 1 500 8.06451612903226
1 2 3500 56.4516129032258
1 3 2200 35.4838709677419
2 1 2000 64.5161290322581
2 2 1100 35.4838709677419
Is that what you're after? I've omitted the filtering by date just for simplicity, since the actual problem comes after that

How to filter data by LINQ when data is coming from CSV file

here i am giving a idea how my csv file has data
Call start Call duration Ring duration Direction Is_Internal Continuation Party1Name
---------- ------------- ------------- --------- ----------- ------------ -----------
09-06-15 7:27 0:00:06 0 I 1 1 ACC
09-06-15 7:27 0:00:06 0 I 0 1 ACC
09-06-15 11:27 0:00:06 0 ) 0 1 Sales
09-06-15 7:27 0:00:06 0 I 0 1 ACC
09-06-15 7:27 0:00:06 0 I 1 0 Suzzy
09-06-15 03:27 0:00:06 0 I 0 1 Suzzy
09-06-15 7:27 0:00:06 0 I 0 1 ACC
09-06-15 7:27 0:00:06 0 O 0 0 Sales
09-06-15 7:27 0:00:06 0 I 1 1 ACC
09-06-15 12:27 0:00:06 0 I 0 1 Matt
09-06-15 10:27 0:00:06 0 I 0 1 VM Channel
09-06-15 7:27 0:00:06 0 ) 0 0 VM Channel
09-06-15 7:27 0:00:06 0 I 0 1 Voice Mail
Now i want to show employee wise data like below
CSR Name Incomming outgoing call transfer
-------- ---------- -------- -------------
ACC 10 12 11
SALES 05 06 02
Suzy 7 5 5
Matt 2 2 2
my condition would be
1) for incoming direction has to be I and for outgoing direction will be O
2) employee name VM Channel and Voice mail will not be consider
and more
i am not good in linq and that is why posting this question for help. i have linq query sample which is bit similar but still no idea what to rectify in it or add to get my desired out put.
here is linq code
void Main()
{
var csvlines = File.ReadAllLines(#"M:\smdr(backup08-06-2015).csv");
var csvLinesData = csvlines.Skip(1).Select(l => l.Split(',').ToArray());
//int iDir = csvLinesData.Count(w => w.direction='I');
var users = csvLinesData.Select(data => new User
{
CSRName = data[12],
Incomming = csvLinesData.Count(w => w[4] == "I"),
outgoing = csvLinesData.Count(w => w[4] == "O")
}).ToList();
users.Dump();
}
class User
{
public string CSRName;
public int outgoing;
public int Incomming;
public int calltransfer;
}
how to remove employee name VM Channel and Voice mail from the result. in sql we can use not like and not in clause but what similar things available in linq ?
how to fetch employee name wise data
CSRName = data[12],
Incomming = csvLinesData.Count(w => w[4] == "I")
if this line CSRName = data[12] return suzy then suzy related incoming data will be stored in Incomming variable. how to achieve it.
just wonder if anyone can help with linq query.
i have tried a bit aground and got the code below.
But i don't know how much of this works because i am not that good of LINQ.
If this does not work you may be able to work with my solution and make a better one :)
var csvlines = File.ReadAllLines(#"M:\smdr(backup08-06-2015).csv");
var csvLinesData = csvlines.Skip(1).Select(l => l.Split(',').ToArray());
// i am assuming that line[7] is the Party1Name Column
// now you have a (sorted) group with n "members" (ACC, Sales, ..., n )
var groupOfUser = from line in csvLinesData
group line by line[7] into newGroup
orderby newGroup.Key
select newGroup;
// The Key of your userOfGrp is the Name e.g. "ACC"
// i am assuming that x[4] is the direction Column
// I count all I or O and put them into the new User
var user = (from userOfGrp in groupOfUser
select
new User()
{
CSRName = userOfGrp.Key,
Incomming = userOfGrp.Count(x => x[4] == "I"),
Outgoing = userOfGrp.Count(x => x[4] == "O")
}).ToList();
the group queue is copied from MSDN. You may have a look into this
But why do you want LINQ? Other solutions can be great too!
Thanks to #Jens who help me to construct the below linq query. i post my full code which is working fine. thanks
void Main()
{
var csvlines = File.ReadAllLines(#"M:\smdr(backup08-06-2015).csv");
var csvLinesData = csvlines.Skip(1).Select(l => l.Split(',').ToArray());
// i am assuming that line[7] is the Party1Name Column
// now you have a (sorted) group with n "members" (ACC, Sales, ..., n )
var groupOfUser = from line in csvLinesData
where !line[12].Contains("VM") && !line[12].Contains("Voice Mail")
group line by line[12] into newGroup
orderby newGroup.Key
select newGroup;
// The Key of your userOfGrp is the Name e.g. "ACC"
// i am assuming that x[4] is the direction Column
// I count all I or O and put them into the new User
var user = (from userOfGrp in groupOfUser
select
new User()
{
CSRName = userOfGrp.Key,
Incomming = userOfGrp.Count(x => x[4] == "I"),
Outgoing = userOfGrp.Count(x => x[4] == "O")
}).ToList();
user.Dump();
}
class User
{
public string CSRName;
public int Outgoing;
public int Incomming;
public int calltransfer;
}

Grouping records that haven't groups values

Please consider this records:
Id Week Value
-----------------------------
1 1 1000
2 1 1200
3 2 800
4 3 1800
5 3 1100
6 3 1000
I want to group records for 4 weeks but we haven't record for week 4.For Example:
Week Count
---------------------
1 2
2 1
3 3
4 0
How I can do this with linq?
Thanks
First you need an array of weeks then this query might help
var weeks = new List<int>{1,2,3,4}
var q = from w in weeks
join rw in (
from r in table
group r by r.Week into g
select new {week = g.Key, count = g.Count()}) on w equals rw.week into p
from x2 in p.DefaultIfEmpty()
select new {w, count = (x2 != null ? x2.count : 0)};
online result in .net fiddle
You can try
var result = Enumerable.Range(1, 4)
.GroupJoin(table,
week => week,
record => record.Week,
(week, records) => new { Week = week, Count = records.Count() });
As suggested by jessehouwing, the Enumerable.Range will return the possible week numbers to be used as left outer keys within the join.
GroupJoin will then accept as parameters
A lambda/delegate/method that returns the left outer key
A lambda/delegate/method that extracts the right key from your table.
A lambda/delegate/method that builds an item of the result.
Regards,
Daniele.

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")
}

Categories