Linq syntax with group by and having clauses - c#

I try to get a list from a datatable by a linq syntax with having clause.
But I don't get what I want.
Here's the SQL syntax :
SELECT ID_BEN,
GAR1,
FIRST(FIRST_NAME) FIRST_NAME,
FIRST(LAST_NAME) LAST_NAME,
FIRST(ADR1) ADR1,
FIRST(ADR2) ADR2,
FIRST(ZIP_CODE) ZIP_CODE,
FIRST(CITY) CITY,
SUM(AMOUNT) SUM_AMOUNT,
SUM(CASE WHEN STATUS_TAB <> 'OK' THEN 1 ELSE 0 END) NUM_STATUS_TAB
FROM T_AMOUNT
WHERE STATUS_ENR = 'OK' AND STATE_ENR = '1'
GROUP BY ID_BEN, GAR1
HAVING SUM(CASE WHEN STATUS_TAB <> 'OK' THEN 1 ELSE 0 END) = 0
Here is my linq syntax :
var oLstReglementGrp = objDataSet.T_AMOUNT
.AsEnumerable()
.Select(sel => new
{
ID_BEN = sel.ID_BEN,
GAR1 = sel.GAR1,
LI_NOM = sel.First().FIRST_NAME,
LI_ADR1 = sel.First().LAST_NAME,
LI_ADR2 = sel.First().ADR1,
LI_ADR3 = sel.First().ADR2,
LI_ADR4 = sel.First().ZIP_CODE,
CD_PST = sel.First().CITY
})
.Where(x => x.STATUS_ENR == "OK"
&& x.STATE_ENR == "1")
.GroupBy(row => new { ID_BEN = (long?)row.ID_BEN, GAR1 = row.GAR1 },
(g, r) => new
{
NUM_STATUS_TAB = r.Sum(s => s.STATUS_TAB != "OK" ? 1 : 0),
SUM_AMOUNT = r.Sum(s => (decimal?)s.AMOUNT)
})
.Where(p => p.NUM_STATUS_TAB == 0)
.ToList();
Here are the SQL results :
FIRST_NAME LAST_NAME ADR1 ZIP_CODE CITY SUM_AMOUNT NUM_STATUS_TAB
Jack Palance 3 bd One 1234 New York 12000 0
John Wayne 4 ave 2 4567 Los Angeles 5500 0
Jimmy Page 5 street 2 2345 Chicago 450 0
And in my list from the linq :
SUM_AMOUNT NUM_STATUS_TAB
12000 0
5500 0
450 0
Do you have an idea ?

When writing LinQ you should no try to translate to SQL query like you would read it.
LinQ syntax is closer to SQL Execution order. In this way Linq is more "Logical".
SQL execution order is the following:
FROM and JOINs
WHERE
GROUP BY
HAVING
SELECT
objDataSet
// 1. FROM and JOINs
.T_AMOUNT
//2. WHERE
.Where(x => x.STATUS_ENR == "OK" && x.STATE_ENR == "1")
//3. GROUP BY
.GroupBy(row => new { ID_BEN = (long?)row.ID_BEN, GAR1 = row.GAR1 })
//5. SELECT
.Select(sel => new
{
ID_BEN = sel.Key.ID_BEN,
GAR1 = sel.Key.GAR1,
LI_NOM = sel.First().FIRST_NAME,
LI_ADR1 = sel.First().LAST_NAME,
LI_ADR2 = sel.First().ADR1,
LI_ADR3 = sel.First().ADR2,
LI_ADR4 = sel.First().ZIP_CODE,
CD_PST = sel.First().CITY,
NUM_STATUS_TAB = sel.Sum(s => s.STATUS_TAB != "OK" ? 1 : 0),
SUM_AMOUNT = sel.Sum(s => (decimal?)s.AMOUNT)
})
//4. HAVING
.Where(p => p.NUM_STATUS_TAB == 0)
.ToList();
Step 4 and 5 have been swap because we are filtering on a field that is not present in the groupby, so we need the select in order to have it avaidable.
At Step 3. GROUP BY, notice the overload used. Order by has 9 overload, MS Docs.
I will advice on using the simple one till you get familiar with it

Related

How to convert this sql query into linq query

My SQL query looks like this, But when I try to convert LINQ. I can't do that.
select case when Status = 0 then 'Pending'
when Status = 1 then 'Approved'
when Status = 2 then 'Denied' else '' end Status,
count(1) totalCount
from Client_BurnOuts group by Status
Here I have an enum that contains 3 values, Pending, Approved, and Denied. SQL output is well, But I can't convert it to SQL.
Suppose, your current value is 0 which means status would be pending so the corresponding linq would be as following:
var statusCaseLinq = new List<Status>()
{
new Status(){ StatusName = "Accepted",StatusId =2},
};
var caseToLinq =
(
from n in statusCaseLinq
where n != null
select new
{
CaseId = n,
CaseSatus =
(
n.StatusId == 0 ? "Pending" :
n.StatusId == 1 ? "Accepted" :
n.StatusId == 2 ? "Denied" : "Unknown"
)
}
);
var getCaseStstusFromId = caseToLinq.FirstOrDefault().CaseSatus;
Output:
You can convert a case statement to a switch expression.
from o in Client_BurnOuts
group o by o.Status into g
select new
{
Status = g.Key switch
{
0 => "Pending",
1 => "Approved",
2 => "Denied",
_ => ""
},
totalCount = g.Count()
};

Optimise LINQ query

I just want to optimise my LINQ, I couldn't figure out how to optimise it or do it better way...
Basically I have got a client and incident tables.
Incident has a 3 status, NEW, VERIFIED and COMPLETED.
I just want to get a list of each clients incidents with the number of each incident status.
Incident Pogress number if it is 0, it means a NEW incident, if it is 1 a VERIFIED and 2 if it is COMPLETED
This is my table
IncidentID ClientID IncidentProgress
1 1 0
2 1 0
3 1 0
4 1 1
5 1 1
6 1 2
7 2 0
8 2 1
9 2 2
10 2 2
What I need
ClientID total New Confirmed Completed
1 6 3 2 1
2 4 1 1 2
I tried this`
First Group BY Each Client
List<ReportIncidentList> list = (from incident in incidentRepository.IncidentModels
join client in clientRepository.ClientModel on incident.ClientID equals client.ClientID
where client.ClientStatus == true && incident.IncidentStatus == true
group incident by new { client.ClientID, client.ClientName, incident.IncidentProgress } into newGroup
orderby newGroup.Key.ClientID
select new ReportIncidentList
{
ClientID = newGroup.Key.ClientID,
ClientName = newGroup.Key.ClientName,
NumberOfIncidents = newGroup.Count(),
NewT = newGroup.Where(x=>x.IncidentProgress == Models.IncidentProgressStatus.New && x.ClientID == newGroup.Key.ClientID).Count(),
Completed = newGroup.Where(x => x.IncidentProgress == Models.IncidentProgressStatus.Completed && x.ClientID == newGroup.Key.ClientID).Count(),
Confirmed = newGroup.Where(x => x.IncidentProgress == Models.IncidentProgressStatus.Confirmed && x.ClientID == newGroup.Key.ClientID).Count(),
IncidentProgress = newGroup.Key.IncidentProgress
}).ToList();
Then Group again
List<ReportIncidentList> list2 = (from client in list
group client by new { client.ClientID, client.ClientName } into newGroup
orderby newGroup.Key.ClientID
select new ReportIncidentList
{
ClientID = newGroup.Key.ClientID,
ClientName = newGroup.Key.ClientName,
NumberOfIncidents = list.Where(c=>c.ClientID==newGroup.Key.ClientID).Sum(s=>s.NumberOfIncidents),
NewT = list.Where(x => x.IncidentProgress == Models.IncidentProgressStatus.New && x.ClientID == newGroup.Key.ClientID).Select(x=>x.NewT).SingleOrDefault(),
Confirmed = list.Where(x => x.IncidentProgress == Models.IncidentProgressStatus.Confirmed && x.ClientID == newGroup.Key.ClientID).Select(x => x.Confirmed).SingleOrDefault(),
Completed = list.Where(x => x.IncidentProgress == Models.IncidentProgressStatus.Completed && x.ClientID == newGroup.Key.ClientID).Select(x => x.Completed).SingleOrDefault(),
}).ToList();
What about keeping things simple?
We can just query list of incidents as is and merge data in a single loop then.
var incidents = incidentRepository.IncidentModels.ToList();
var reportsIncidentList = new Dictionary<int,ReportIncidentList>();
foreach (var incident in incidents)
{
if (!reportsIncidentList.ContainsKey(incident.ClientID))
reportsIncidentList.Add(incident.ClientID, new ReportIncidentList(){ClientID = incident.ClientID});
reportsIncidentList[incident.ClientID].Total++;
switch (incident.IncidentProcess)
{
case 0:
reportsIncidentList[incident.ClientID].New++;
break;
case 1:
reportsIncidentList[incident.ClientID].Confirmed++;
break;
case 2:
reportsIncidentList[incident.ClientID].Completed++;
break;
}
}
var result = reportsIncidentList.Values.ToList();
Here is the linq query that will get you the results:
var results = data
.GroupBy(x => x.ClientID)
.Select(g => new {
ClientID = g.Key,
Total = g.Count(),
New = g.Count(i => i.IncidentProgress == 0),
Confirmed = g.Count(i => i.IncidentProgress == 1),
Verified = g.Count(i => i.IncidentProgress == 2)});

How to display monthly attendance data

I have two tables
TblEmployee
attendance_id employee_id Scan_type Date_and_time
1 1 IN 15-Jan-19 8:00:00 AM
2 1 IN 15-Jan-19 8:00:02 AM
3 2 IN 15-Jan-19 8:05:01 AM
4 2 OUT 15-Jan-19 4:00:00 PM
5 1 IN 16-Jan-19 8:05:30 AM
AttendanceTable
emp_id emp_name
1 Salman
2 Tahir
3 Jameel
I want to display monthly record data of all employees with in and out times. In time will be the first time with "iN" and out time will be the last time with "OUT". Employee may forget to sign-in or sign-out so in that case the field will remain blank or show "absent".
I am trying to do like this but don't know what to do next
var InList = from a in _context.TblEmployee
from e in _context.AttendanceTable.Where(x =>
a.EmpId == x.EmployeeId)
.Where(x => x.ScanType == "IN")
.OrderBy (x => x.DateAndTime)
.DefaultIfEmpty()
select new
{
AttDate = e.DateAndTime.Date,
Emp_name = a.EmployeeName,
Emp_Id = e.EmployeeId
};
var OutList = from a in _context.TblEmployee
from e in _context.AttendanceTable.Where(x =>
a.EmpId == x.EmployeeId)
.Where(x => x.ScanType == "OUT")
.OrderBy (x => x.DateAndTime)
.DefaultIfEmpty()
select new
{
AttDate = e.DateAndTime.Date,
Emp_name = a.EmployeeName,
Emp_Id = e.EmployeeId
};

Group by Linq vs Transact Sql

I have this SQL query
select GrupoEmpaque,NumIdConceptoEmpaque,sum(NumCantidadEmpaques)
from Movimientos_Pedidos
where StrIdDocumento = '009000PV00000000000000599' and (GrupoEmpaque is null or GrupoEmpaque = 0 )
group by GrupoEmpaque , NumIdConceptoEmpaque
**It Returns:**
NULL 338 25
In the other side I have this Linq , Pedido allready has only '009000PV00000000000000599' data
var EmpaquesItemUnico = Pedido.Movimientos_Pedidos
.GroupBy(x => x.NumIdConceptoEmpaque)
.Select(x => new { GrupoEmpaque = x.FirstOrDefault().GrupoEmpaque, TipoEmpaque = x.FirstOrDefault().Merlin_ConceptosFacturacionEmpaque, Cantidad = x.Sum(y => y.NumCantidadEmpaques) })
.Where(x => x.GrupoEmpaque == 0 || x.GrupoEmpaque == null);
But now the results are
NULL 338 28
Now My questions are:
Why TSQL returns 25 and Linq Returns 28?
How to make those sentences return same results?
You have to filter results first before projecting, and your both groupby statements of t-sql and linq are not same:
var EmpaquesItemUnico = Pedido.Movimientos_Pedidos
.GroupBy(x => new
{
NumIdConceptoEmpaque =x.NumIdConceptoEmpaque,
GrupoEmpaque = x.GrupoEmpaque
}
)
.Where(x => x.Key.GrupoEmpaque == 0 || x.Key.GrupoEmpaque == null)
// now project here
.Select(x=> new
{
NumIdConceptoEmpaque = x.Key.NumIdConceptoEmpaque,
GrupoEmpaque = x.Key.GrupoEmpaque,
Sum = x.Sum(y => y.NumCantidadEmpaques)
});

Linq C# Sum in Group By

I'm trying to transform the SQL that is here http://sqlfiddle.com/#!6/a1c8d/2 in linq below. The expected result is what is in sqlfiddle, but my LINQ returns more rows.
PS: In sqlfiddle the fields are reduced to not increase pollution and stay focused on my problem.
resultado.Dados =
(
from a in db.AgendaHorario
join b in db.Agenda on a.AgendaID equals b.AgendaID
select new
{
a.AgendaID,
Horario = a.Horario,
Controle = a.Controle,
Cor = b.Cor,
Agenda = b.Sigla
}).AsEnumerable()
.GroupBy(g => new
{
g.AgendaID,
Horario = g.Horario.ToString("dd/MM/yyyy"),
Data = g.Horario.ToString("yyyy-MM-dd"),
g.Controle,
g.Agenda,
g.Cor
})
.Select(s => new
{
id = s.Key.AgendaID,
title = s.Key.Agenda,
start = s.Key.Data,
color = String.IsNullOrEmpty(s.Key.Cor) ? "3a87ad" : s.Key.Cor,
className = "",
someKey = 1,
allDay = false,
Resultado0 = s.Sum(m => m.Controle == "L" ? 1 : 0).ToString(),
Resultado1 = s.Sum(m => m.Controle == "B" ? 1 : 0).ToString()
});
As per the comments, this addresses the question of how to repeat your SqlFiddle in Linq. Note that the projection to a String Date cannot be converted to Sql directly, so I've had to early materialize with AsEnumerable() (obviously, in your real query, apply any filters prior to materializing!). You could probably do the grouping on just the date part using SqlFunctions, e.g. 3 x applications of SqlFunctions.DatePart will allow you to group by dd, MM and YYYY
var dados = db.AgendaHorarios1
.AsEnumerable()
.GroupBy(ah => ah.Horario.ToString("dd/MM/yyyy"))
.Select(g => new {Horario = g.Key,
Livre = g.Count(x => x.Controle == "L"),
Bloq = g.Count(x => x.Controle == "B"),
Aged = g.Count(x => x.Controle == "A")});

Categories