I'm having a datatable like mentioned below.
ID Percentage
1 50
1 30
2 0
2 100
Result:
ID Percentage
1 80
2 100
I tried this and it doesn't work
var n = dt.AsEnumerable()
.Where(r => (int)r["ID"] != "0" || (int)r["ID"] != "100")
.Sum(r => (int)r["Percentage"]);
I'm new to linq and pls provide some suggestions.
Now I need to sum the percentage for each ID and the percentage for each ID should be 0 or 100 percentage.
If any one of the ID in table doesn't have 0 or 100 I need to alert. Pls suggest me how I can do this in linq and I think its the best way.
var result = from row in dt.AsEnumerable()
group row by row["ID"]
into g
select new
{
ID = g.Key,
Sum = g.Sum(x => int.Parse(x["Percentage"].ToString()))
};
var errorItems = result.Where(x => x.Sum != 100 && x.Sum != 0);
if (errorItems.Any())
{
var ids = errorItems.Select(x => x.ID);
string msg = string.Format("ID(s): [{0}] don't meet condition.", string.Join(",", ids));
MessageBox.Show(msg);
}
You are not trying get the sum of "Percentage" for the whole table so directly doing a sum on it wont give you the desired result.
You're trying to find the sum of the percentage for each ID value so you need to group it by ID.
That's what GroupBy(g => g.Field<int>("ID")) does. Then you take the group(g), and for each group, you sum the "Percentage" Column of the members i.e.. .Select(g => g.Sum(p => p.Field<int>("Percentage")))
Here is the complete code.
dt.AsEnumerable().Where(r => r.Field<int>("ID") == 0 || r.Field<int>("ID") == 100).GroupBy(g => g.Field<int>("ID")).Select(g => g.Sum(p => p.Field<int>("Percentage")));
to put an alert message you can use Any instead of the where to check for the presence of the values
if(dt.AsEnumerable().Any(r => r.Field<int>("ID") != 0 && r.Field<int>("ID") != 100)
{
Console.WriteLine("Alert");
}
I guess that you want a new DataTable with the same columns as the first but with grouped percentage by ID? Then have a look at GroupBy and Sum:
var groupQuery = dt.AsEnumerable()
.Select(r => new { ID = r.Field<int>("ID"), Percentage = r.Field<int>("Percentage") })
.Where(x => x.ID != 0 && x.ID != 100)
.GroupBy(x => x.ID);
DataTable groupingTable = dt.Clone(); // empty, same columns
foreach(var grp in groupQuery)
groupingTable.Rows.Add(grp.Key, grp.Sum(x => x.Percentage));
This presumes that the type of the columns is actually int. If they are strings the best way is to change it to int, if you can't do that you have to use int.Parse.
For example:
ID = int.Parse(r.Field<int>("ID"))`
Update: Although it's not clear what you want if i reread your qustion, especially:
If any one of the ID in table doesn't have 0 or 100 I need to alert
You could use this to get all ID-groups without 0 or 100 percentage:
var without0Or100Perc = dt.AsEnumerable()
.Select(r => new { ID = r.Field<int>("ID"), Percentage = r.Field<int>("Percentage") })
.GroupBy(x => x.ID)
.Where(g => !g.Any(x => x.Percentage == 0 || x.Percentage == 100));
Now you can use Any, FirstOrDefault or a foreach loop to consume this query, so one of following approches:
bool anyWithout0Or100Perc = without0Or100Perc.Any();
var firstWithout0Or100Perc = without0Or100Perc.FirstOrDefault();
anyWithout0Or100Perc = firstWithout0Or100Perc != null;
foreach (var grp in without0Or100Perc)
{
Console.WriteLine("ID: {0} Percentages:{1}",
grp.Key,
String.Join(",", grp.Select(x => x.Percentage)));
}
Related
I have a table "register_operation with fields"
[Key]
int id_registru_casa ,
DateTime data ,
int id_cont_sintetic ,
decimal suma ,
string tip
tip can take only 2 value :"receipts" and "payments"
"Groupby" work with no problem
but when I add "where" clause not working
(it doesn't show me any records)
(although there are recordings in database with day 19, month 9 and tip=receipts)
var centralizator_rc = db.register_operation
.Where(i => (i.data.Day == 19) && (i.data.Month == 9) && (tip=="receipts"))
.GroupBy(i => i.id_cont_sintetic)
.Select(g => new {
id_cont_sintetic = g.Key,
total_receipts = g.Sum(i=>i.suma),
}).ToList();
Thanks!
SOLVED!
I change code like this:
var centralizator_rc = db.registru_casa
.Where(crc=>(crc.data.Month==8) && (crc.data.Day==16) && (crc.tip=="receipts"))
.GroupBy(crc=> new
{
crc.id_cont_sintetic,
crc.data.Month,
crc.data.Day,
crc.tip
})
.Select(g => new {
data = ziuaOK,
id_cont_sintetic = g.Key.id_cont_sintetic,
total_incasare = g.Sum(i => i.suma),
}).ToList();
I tried to divided Linq queries into 3 (total, success, fail) but so far "Total" Linq query is working fine. Please help me to get "Success", "Fail" columns (it has mulitple statuses and we have to check the last column of each transaction and destination)
Note: you need to group by ProcessTime, TransactionId, Destination and check last column whether it is success or Fail then apply count (we are using oracle as backend)
LINQ for Total count
var query = (from filetrans in context.FILE_TRANSACTION
join route in context.FILE_ROUTE on filetrans.FILE_TRANID equals route.FILE_TRANID
where
filetrans.PROCESS_STRT_TIME >= fromDateFilter && filetrans.PROCESS_STRT_TIME <= toDateFilter
select new { PROCESS_STRT_TIME = DbFunctions.TruncateTime((DateTime)filetrans.PROCESS_STRT_TIME), filetrans.FILE_TRANID, route.DESTINATION }).
GroupBy(p => new { p.PROCESS_STRT_TIME, p.FILE_TRANID, p.DESTINATION });
var result = query.GroupBy(x => x.Key.PROCESS_STRT_TIME).Select(x => new { x.Key, Count = x.Count() }).ToDictionary(a => a.Key, a => a.Count);
Check this solution. If it gives wrong result, then I need more details.
var fileTransQuery =
from filetrans in context.AFRS_FILE_TRANSACTION
where accountIds.Contains(filetrans.ACNT_ID) &&
filetrans.PROCESS_STRT_TIME >= fromDateFilter && filetrans.PROCESS_STRT_TIME <= toDateFilter
select filetrans;
var routesQuery =
from filetrans in fileTransQuery
join route in context.AFRS_FILE_ROUTE on filetrans.FILE_TRANID equals route.FILE_TRANID
select route;
var lastRouteQuery =
from d in routesQuery.GroupBy(route => new { route.FILE_TRANID, route.DESTINATION })
.Select(g => new
{
g.Key.FILE_TRANID,
g.Key.DESTINATION,
ROUTE_ID = g.Max(x => x.ROUTE_ID)
})
from route in routesQuery
.Where(route => d.FILE_TRANID == route.FILE_TRANID && d.DESTINATION == route.DESTINATION && d.ROUTE_ID == route.ROUTE_ID)
select route;
var recordsQuery =
from filetrans in fileTransQuery
join route in lastRouteQuery on filetrans.FILE_TRANID equals route.FILE_TRANID
select new { filetrans.PROCESS_STRT_TIME, route.CRNT_ROUTE_FILE_STATUS_ID };
var result = recordsQuery
.GroupBy(p => DbFunctions.TruncateTime((DateTime)p.PROCESS_STRT_TIME))
.Select(g => new TrendData
{
TotalCount = g.Sum(x => x.CRNT_ROUTE_FILE_STATUS_ID != 7 && x.CRNT_ROUTE_FILE_STATUS_ID != 8 ? 1 : 0)
SucccessCount = g.Sum(x => x.CRNT_ROUTE_FILE_STATUS_ID == 7 ? 1 : 0),
FailCount = g.Sum(x => failureStatus.Contains(x.CRNT_ROUTE_FILE_STATUS_ID) ? 1 : 0),
Date = g.Min(x => x.PROCESS_STRT_TIME)
})
.OrderBy(x => x.Date)
.ToList();
I use this linq query to group and calculate some values from datagridview.
var Sums = dataGridView1.Rows.Cast<DataGridViewRow>()
.Where(row => row.Cells[8].Value != null)
.GroupBy(row => row.Cells[8].Value.ToString())
.Select(g => new { Gruppo = g.Key, Serie = g.Sum(row => Convert.ToInt32(row.Cells[2].Value)) });
How can I improve this adding the percentage calculation between each group and the total?
What I want is that if the total of "Serie" column values is 20 (for example), in a third column I see that 3 (the value in the first cell) is the 15% etc.
Sums = dataGridView1.Rows.Cast<DataGridViewRow>()
.Where(row => row.Cells[8].Value != null)
.GroupBy(row => row.Cells[8].Value.ToString())
.Select(g => new
{
Gruppo = g.Key,
Serie = g.Sum(row => Convert.ToInt32(row.Cells[2].Value)),
Percente = (g.Sum(row => Convert.ToInt32(row.Cells[2].Value)) / Convert.ToInt32(label15.Text)) * 100
});
I am working on a LINQ query which includes some pivot data as below
var q = data.GroupBy(x => new
{
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g =>
new SummaryDto
{
Name= g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = g.Where(z => z.ProcessDate.Year == 2012).Count(),
Data2013 = g.Where(z => z.ProcessDate.Year == 2013).Count(),
Data2014 = g.Where(z => z.ProcessDate.Year == 2014).Count(),
Data2015 = g.Where(z => z.ProcessDate.Year == 2015).Count(),
Data2016 = g.Where(z => z.ProcessDate.Year == 2016).Count(),
Data2017 = g.Where(z => z.ProcessDate.Year == 2017).Count(),
TotalCount = g.Count(),
}).AsQueryable();
return q;
The above LINQ takes too long as it queries grp q.Count()*6 times. If there are 10000 records, then it queries 60000 times
Is there a better way to make this faster?
Add year to the group key, then group again, and harvest per-group counts:
return data.GroupBy(x => new {
x.Med.Name
, x.Med.GenericName
, x.ProcessDate.Year
}).Select(g => new {
g.Key.Name
, g.Key.GenericName
, g.Key.Year
, Count = g.Count()
}).GroupBy(g => new {
g.Name
, g.GenericName
}).Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, Data2012 = g.SingleOrDefault(x => x.Year == 2012)?.Count ?? 0
, Data2013 = g.SingleOrDefault(x => x.Year == 2013)?.Count ?? 0
, Data2014 = g.SingleOrDefault(x => x.Year == 2014)?.Count ?? 0
, Data2015 = g.SingleOrDefault(x => x.Year == 2015)?.Count ?? 0
, Data2016 = g.SingleOrDefault(x => x.Year == 2016)?.Count ?? 0
, Data2017 = g.SingleOrDefault(x => x.Year == 2017)?.Count ?? 0
, TotalCount = g.Sum(x => x.Count)
}).AsQueryable();
Note: This approach is problematic, because year is hard-coded in the SummaryDto class. You would be better off passing your DTO constructor an IDictionary<int,int> with counts for each year. If you make this change, the final Select(...) would look like this:
.Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, TotalCount = g.Sum(x => x.Count)
, DataByYear = g.ToDictionary(i => i.Year, i => i.Count)
}).AsQueryable();
I suggest grouping inside the group by year and then converting to a dictionary to access the counts. Whether it is faster to group with year first and then count in-memory depends on the distribution of the initial grouping, but with the database it may depend on how efficiently it can group by year, so I would test to determine which seems fastest.
In any case grouping by year after the initial grouping is about 33% faster than your query in-memory, but again it is vastly dependent on the distribution. As the number of initial groups increase, the grouping by Year queries slow down to match the original query. Note that the original query without any year counts is about 1/3 the time.
Here is grouping after the database grouping:
var q = data.GroupBy(x => new {
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g => {
var gg = g.GroupBy(d => d.ProcessDate.Year).ToDictionary(d => d.Key, d => d.Count());
return new SummaryDto {
Name = g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = gg.GetValueOrDefault(2012),
Data2013 = gg.GetValueOrDefault(2013),
Data2014 = gg.GetValueOrDefault(2014),
Data2015 = gg.GetValueOrDefault(2015),
Data2016 = gg.GetValueOrDefault(2016),
Data2017 = gg.GetValueOrDefault(2017),
TotalCount = g.Count(),
};
}).AsQueryable();
How to write a LINQ query to fetch only the boundary records for a condition. For example, Consider the below database table which represents tracking data received from a vehicle:
I wish to fetch only record 47890 and 47880. Which will give the start time and end time when vehicle was stopped.
Right now, in my query i fetch all the records and then take the first and the last. Also, the query need to be generic, I may have multiple stops for a vehicle. For Example:
Stop1 : 11:00 AM to 1:00 PM
Stop2 : 3:00 PM to 3:30 PM
and so on.
Here is the code I have written so far:
var sData = db.Vehicles
.Where(v => v.VehicleId == vehicleId)
.SelectMany(v => v.GsmDeviceLogs)
.Where(gs => gs.DateTimeOfLog > startDate && gs.DateTimeOfLog < endDate && gs.Speed < zeroSpeed && !gs.IgnitionOn)
.Select(v => new
{
DateTimeOfLog = v.DateTimeOfLog,
Location = v.Location
}).OrderBy(gs => gs.DateTimeOfLog).ToList();
The next result is tested with LinqPad. It can be optmized with T-SQL and used via stored procedure.
var indexedRowsAsc = arr.OrderBy(r => r.DateTimeOfLog)
.Select((r, index) => new { Row = r, Index = index});
// find intersection of current row and next row with condition (IgnitionOn)
// intersection can ignore first and last row
var foundRows = (from a in indexedRowsAsc
from b in indexedRowsAsc
where a.Index == (b.Index -1) &&
a.Row.IgnitionOn != b.Row.IgnitionOn
select new {a, b}
).ToArray();
var firstRow = arr.OrderBy(r => r.DateTimeOfLog).FirstOrDefault();
var lastRow = arr.OrderByDescending(r => r.DateTimeOfLog).FirstOrDefault();
// union found rows with first and last row
var distinctFoundRows = foundRows.Select(fr => fr.a.Row)
// comparer can be added for union for proper distinct gathering
.Union(foundRows.Select(fr => fr.b.Row))
// add first and last row
.Union(new Vehicle[]{firstRow})
.Union(new Vehicle[]{lastRow})
.Where(r => r!= null)
.OrderBy(r => r.DateTimeOfLog)
.ToArray();
// find result by grouping rows where IgnitionOn == 0
int groupId = 1;
var result = distinctFoundRows
.Select(row => new {Row =row, GroupId = (row.IgnitionOn == 0? groupId: ++groupId)})
.Where(res => res.Row.IgnitionOn == 0)
.GroupBy(res => res.GroupId)
.Select(gr => new {First = gr.First().Row, Last = gr.Last().Row})
.ToArray();
The secret of finding changed values in column is self joining.