LINQ Nested query with appropriate quantifiers and restrictions - c#

I have two tables A & B as below:
Table A
W X Y
1 7 5
2 0 7
3 1 7
4 4 4
5 4 7
Table B
Q Z C D
1 1 7 5
2 1 0 7
3 1 1 7
4 1 4 4
I want to get those values of W for whom X & Y combination exactly matches those combination of C & D in Table B for whom Z = 1.
I have tried following query:
var query = A.Where(u =>
B.Where(a => a.Z == 1)
.Select(a => a.C)
.Contains(u.X))
.Where(u =>
B.Where(a => a.Z == 1)
.Select(a => a.D)
.Contains(u.Y))
.Select(a => new { WIds = a.W });
so in above cases, query result should give: W = {1,2,3,4} however I am getting extra value of 5 as well. W = {1,2,3,4,5}. I think it is not considering the combination as a whole. Can anyone help me what am I doing wrong in this query?

You can simply do it using Any:-
var result = A.Where(x =>
B.Any(p => p.Z == 1 && p.C == x.X && p.D == x.Y)
)
.Select(x => x.W);
Fiddle

The problem is that you are just checking whether the current X matches any C and the current Y matches any D, instead of matching them as a group. You could do this with a simpler query:
var query = A
.Where(a => B.Any(b => b.Z == 1 && b.C == a.X && b.D == a.Y))
.Select(a => new { WIds = a.W });

Can you try this ?
var query = A.Where(a =>
B.Any(b => b.Z == 1 && a.X == b.C && a.Y == b.D))
.Select(a => new { WIds = a.W });

Related

Count active boxes in the rack from transaction logs

I have 2 tables here
Rack
Id
RackName
1
Rack A
2
Rack B
RackTransaction
Id
RackId
Barcode
Event
DateTime
1
1
ABC1234
IN
2021-07-12
2
1
ABC1234
OUT
2021-07-20
3
1
ABC1235
IN
2021-07-21
4
1
ABC1236
IN
2021-07-21
5
1
ABC1236
OUT
2021-07-23
6
1
ABC1237
IN
2021-07-21
7
1
ABC1238
IN
2021-07-21
8
1
ABC1238
OUT
2021-07-23
Barcode ABC1235 and ABC1237 not check out yet. So the total count boxes inside the RACK A is 2.
So from given sample data, how I can generate using the linq?
var getTotal = await _context.RackBoxLogs.Where(c => c.RackId == rackId)
.GroupBy(c => new { c.Barcode,c.Event })
.Select(g => new { g.Key, MaxDate = g.Max(c => c.CreatedOn),Event = g.Key.Event })
.Where(c=> c.Event == "IN")
.CountAsync();
Even I already group by the barcode and filter the max date I still get the wrong result. Any idea?
This should work with EF 5, I think. Unfortunately EF 5 can't (yet) translate GroupBy().Select(g => g.Last()).
var getTotal = RackTransaction
.Where(rt => rt.RackId == rackId)
.Select(rt => rt.Barcode)
.Distinct()
.Select(b => RackTransaction.Where(rt => rt.Barcode == b).OrderBy(rt => rt.DateTime).Last())
.Where(rt => rt.Event == "IN")
.Count();
This query should get desired count:
var getTotal = await _context.RackBoxLogs
.Where(c => c.RackId == rackId)
.GroupBy(c => new { c.Barcode })
.Where(g => g.Sum(x => x.Event == "IN" ? 1 : 0) >
g.Sum(x => x.Event == "OUT" ? 1 : 0)
)
.CountAsync();

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

GroupBy and Sum with two Different period time - Linq

I need to get sum of my products amount in different period of times in just one row.
for example:
ID Date Amount
-------------------------
1 2017/01/01 10
1 2017/01/01 12
1 2017/04/03 5
2 2017/01/02 10
I need to get sum for spring season and summer season of each product, so we have this for product 1:
ID SumSpring SumSummer
-----------------------------
1 22 5
I have used this code:
var pDetails = ordersTotal.Select(g => new
{
g.ProductID,
DateType = (((String.Compare(g.BuyDate, "2017/01/01") >= 0 && String.Compare(g.BuyDate, "2017/03/30") <= 0)) ? "Spring" : "Summer"),
g.Amount
}).GroupBy(x => new { id = x.ProductID, type = x.DateType }).Select(x => new
{
ProductID = x.Key.id,
SumSpring = (x.Where(z => z.DateType == "Spring").Count() == 0 ? 0 : x.Where(z => z.DateType == "Spring").Sum(z => z.Amount)),
SumSummer = (x.Where(z => z.DateType == "Summer").Count() == 0 ? 0 : x.Where(z => z.DateType == "Summer").Sum(z => z.Amount)),
});
but it returns several rows for each product which is not what I expected and I do not know why!
This is the output for one product:
ID SumSpring SumSummer
-----------------------------
1 22 0
1 0 5
two rows for one product, but it should be one!
You can get the quarter of a year in this way:
int quarter = (month + 2) / 3;
But don't include it in the GroupBy, you only want to group by ProductID
var pDetails = ordersTotal.Select(x => new
{
x.ProductID,
x.Amount,
Quarter = (x.BuyDate.Month + 2) / 3
})
.Where(x => x.Quarter == 1 || x.Quarter == 2) // it seems you only want these
.GroupBy(x => x.ProductID)
.Select(g => new
{
ProductID = g.Key,
SumSpring = g.Where(x => x.Quarter == 1)
.Select(x => x.Amount)
.DefaultIfEmpty(0)
.Sum(),
SumSummer = g.Where(x => x.Quarter == 2)
.Select(x => x.Amount)
.DefaultIfEmpty(0)
.Sum()
});
Note that this query doesn't care about the year. But it seems you don't care about it anyway.
Try following. the major issue is that you are grouping by Amount :
var pDetails = ordersTotal.Select(g => new
{
ProductID = g.ProductID,
DateType = ((g.BuyDate >= DateTime.Parse("1/1/17")) && (g.BuyDate <= DateTime.Parse("3/30.17"))) ? "Spring" : "Summer",
Amount = g.Amount
}).GroupBy(x => new { id = x.ProductID}).Select(x => new
{
ProductID = x.Key.id,
SumSpring = x.Where(z => z.DateType == "Spring").Sum(z => z.Amount),
SumSummer = x.Where(z => z.DateType == "Summer").Sum(z => z.Amount),
}).ToList();

Linq check for percentage in where clause

I am trying to check some percent of value in where clause in Linq
following is my code
List<string> v = await context.Categories.Where(w =>(w.id / 2) * 100 == 50)
.Select(w => w.id.ToString()).ToListAsync();
the problem is there is record that meets the condition but no record is selecting. I tried using Convert.ToDecimal(w.id/2) but its not working either.
How can I get the percent checking in where clause? and why the above code not working?
How about changing the order of operations....
List<string> v = await context.Categories.Where(w => 100 * w.id / 2 == 50)
.Select(w => w.id.ToString()).ToListAsync();
or
List<string> v = await context.Categories.Where(w => 50 * w.id == 50)
.Select(w => w.id.ToString()).ToListAsync();
or
List<string> v = await context.Categories.Where(w => w.id == 1)
.Select(w => w.id.ToString()).ToListAsync();
If id is an integer it does supportswhole numbers so 1/2 = 0. You need to force a cast to a double (a type that supports decimal places) before you operate on it.
List<string> v = await context.Categories.Where(w =>(w.id / 2.0) * 100 == 50)
.Select(w => w.id.ToString()).ToListAsync();
Unless I'm missing something, you don't seem to need the calculation:
w =>(w.id / 2) * 100 == 50
can be written as
w => w.id == 1

Linq select all items where in list with groupby

I have the following data as a list:
raceId data position
1 A 0
1 B 0
1 F 1
1 J 0
2 A 2
2 F 1
3 A 0
3 J 2
3 M 1
3 V 3
I need to get the total (count) of races where there are ALL matching letters with the same raceid.
I.E a search on 'A' and 'J' = 2 (race's 1 and 3)
In addition I need to get the position data for each.
raceId data position
1 A 0
1 J 0
3 A 0
3 J 2
So far I have the following code.
var dataValues = new string[] { 'A', 'J' };
var races = raceData
.GroupBy( ac => ac.raceId )
.Select( grp => grp.First() )
.Where( t =>
dataValues
.All( s =>
dataValues
.Contains( t.data )
)
);
var racecount = races.count()
The issue is that this returns all raceId values where there is either letter in the data.
This should work for you:
var results = raceData.GroupBy(rd => rd.raceId)
.Where(g => dataValues.All(dv => g.Select(g2 => g2.data).Contains(dv)));
int raceCount = results.Count();
var results2 = results
.SelectMany(g => g)
.Where(rd => dataValues.Contains(rd.data));
raceCount will give you 2 and results2 will give you the 4 records you're expecting.
It works for me with your provided data anyway!
var groupedRaces = from r in raceData
group r by r.raceId into gp
select new { raceId = gp.Key, Datas = gp.Select(g => g.data).ToArray() };
var raceIds = from r in groupedRaces
where dataVals.All(mv => r.Datas.Contains(mv))
select r.raceId;
var races = from r in raceData
where raceIds.Contains(r.raceId) && dataVals.Contains(r.data)
select r;
Try this,
list.GroupBy(t => t.raceID).SelectMany(k => k).Where(x => dataValues.Contains(x.data))
.Select(f=> new { f.data ,f.position,f.raceID}).ToList();
Result,
Hope helps,

Categories