LINQ - Group By with Having? - c#

I have a db table which is having data like below.
Name Tool Security QUANTITY PRICE
ABC ML XXX 100 50
ABC DB XXX -50 50
XYZ CS YYY 30 30
My requirement is to group the name and security and pick only that record which is having both negative and positive quantity. In T-SQL this is my query which is perfectly fine. Need similar in LINQ. For example in above it will give both rows for ABC & XXX.
select t1.* from MyTable as t1
inner join
(
select Name,Security from MyTable
group by Name, Security
HAVING min(Quantity)<0 and max(Quantity)>0
) as t2 on t1.Name=t2.Name and t1.Security =t2.Security
This is my inner query but it's not working.
var Positions = from r in lstpositions
group r by new { r.Name, r.Security} into grp
where grp.Min(x => x.Quantity<0) && grp.Max(x => x.Quantity >0)
select grp;
Any thoughts on this ?

The reason your query does not work is because you taking the min of the result of the comparison.
However I think you want any() not min and max
where grp.Any(x => x.Quantity<0) && grp.Any(x => x.Quantity >0)
This will check for any value below 0 and any value above 0. It will short circuit so it does not have traverse the entire list which should make it faster.

Or this
where grp.Min(x => x.Quantity) < 0 && grp.Max(x => x.Quantity) > 0

Related

Select only first row from each group. Entity Framework

I have a table something like this
userId
productName
transactionId
Date
6556656
apple
3534534
25.10
6556656
apple
T423423
23.10
6556656
orange
7687898
22.10
6556656
orange
5675665
27.10
6556656
orange
1231312
25.09
6556656
banana
4564545
14.09
6556656
banana
7898878
30.09
As you can see I have 7 rows where are 3 kinds of products. I needn't get all 7 rows. I need get only one of each.
In result I need only 3 rows where will be only one apple, orange and banana ordered by Date(the most late from each group)
need to write query something like this
var result = _db.Fruits.GroupBy(o => o.ProductName).Select(g => g.OrderByDescending(o => o.Date).FirstOrDefault() I try any cases but without result.
Quick answer
You want to take the first occurrence of each group.
You can use OrderBy + First over a GroupBy:
var top =
db
.Transactions
.GroupBy(
t=> t.Product
)
.Select(t=>new {
t.Key,
date=t.OrderBy(x => x.Date).Select(x=>x.Date).First() // <- Magic is Here!
})
.ToList();
That generates:
SELECT t.Product AS Key, (
SELECT t0.Date
FROM Transactions AS t0
WHERE t.Product = t0.Product
ORDER BY t0.Date
LIMIT 1) AS date
FROM Transactions AS t
GROUP BY t.Product
Note, use OrderByDescending to get the last Date (instead the first one)
More elaborate
If you want the whole Transaction model for each group:
var top =
db
.Transactions
.GroupBy(
t=> t.Product
)
.Select(t=>new {
productname = t.Key,
lasttransaction= t.OrderByDescending(x => x.Date).First()} )
.AsEnumerable() // <-- at this point you should to move sql to client
.Select(t => new {t.productname, t.lasttransaction.Date} )
.ToList();
That is translated as:
SELECT t0.Product, t1.TransactionId, t1.Date, t1.Product
FROM (
SELECT t.Product
FROM Transactions AS t
GROUP BY t.Product
) AS t0
LEFT JOIN (
SELECT t2.TransactionId, t2.Date, t2.Product
FROM (
SELECT t3.TransactionId, t3.Date, t3.Product, ROW_NUMBER() OVER(PARTITION BY t3.Product ORDER BY t3.Date DESC) AS row
FROM Transactions AS t3
) AS t2
WHERE t2.row <= 1
) AS t1 ON t0.Product = t1.Product

Complex Linq Query Update as DateTime

There are A and B tables that are related to each other. I want to create a linq query that will update the Status value in the A table if the entire row of relationship lines with the AID column in the B table is equal to or smaller than today's date in the Date field.
For example, according to the table below, the Status values of the rows with ID value 1 (AAA) and 2 (BBB) in Table A will be 1. Its Status value will not change because the line with ID value 3 (CCC) is not smaller than the current date of all the related rows in the B table.
How can I write the most stable and performance linq query?
Today : 2018-7-10
A Table
ID Name Status
1 AAA 0
2 BBB 0
3 CCC 0
B Table
ID AID Date
6 1 2018-5-3
7 2 2018-6-2
8 2 2018-6-4
9 3 2018-10-12
10 3 2018-7-7
Grouping TableB on AID
Selecting the "Max" date in each group.(Each unique AID)
Compares the selected dates with the corresponding Id in Table A.
Sets the Status value to true if the date is less or equal to the current date.
TableB.GroupBy(x => x.AId).Select(group => new { identifier = group.Key, MaxDate = group.Max(m => m.Date) }).ToList().ForEach(y =>
{
if (y.MaxDate <= DateTime.Now.Date)
{
TableA.Where(g => g.Id == y.identifier).First().Status = true;
}
});
This will select AIDs from Table B where Date is samller than now.
we select records from table A where its ID is in List from
previous step
Then we update Status value
A.Where ( a => B.Where( b => b.Date <= DateTime.Now).Select(b => b.AID).Contains(a.ID)).ForEach( a => a.Status = 1 )
/*Fetching those aS Who meet the condition. */
var aList1=(from b in dbset.Bs.Where(x=>x.Date<DateTime.Now)//TimeZone may vary
join a in dbSet.As
on b.AID equals a.ID
select a);
/*Fetching those aS Who don't meet the condition. */
var aList2=(from b in dbset.Bs.Where(x=>x.Date>=DateTime.Now)//TimeZone may vary
join a in dbSet.As
on b.AID equals a.ID
select a);
/*Removing those aS from list1 which occured in list2 */
var aFinalList=(aList1.Except(aList2)).ToList();
/*Updating status */
aFinalList.ForEach(x=>x.Status=1);
aFinalList.SaveChanges();
You can use GroupJoin extension in Lambda to Join the A and B tables then use All extension with your condition (date <= Today or any condition) then update the Status. Something like,
var lstResult = lstA.GroupJoin(lstB, a => new { a.Id }, b => new { Id = b.AId }, (a, b) => new { a, b })
.Select(x =>
{
if (x.b.All(y => y.Date <= DateTime.Now)) //Actual condition here.
{
x.a.Status = true;
return x.a;
}
else return x.a;
});
C# fiddle with sample data.

Using Ling to Sql, merge two queries into one, then get the int value of the result

I have the following Linq To Sql queries
var group = "T";
var categoryQuery = from m
in ctx.Material
where m.Group == group
select m;
var maxValue = from c
in categoryQuery
where c.Order == categoryQuery.Select(o => o.Order).Max()
select c.Order;
Where Group is a string value (ex: "T", "S", "R") and Order is an int value. What I want to achieve is to get the highest Order int value from the row in the result set filtered by a particular Group value. Ultimately, I want to use the max value for comparison elsewhere in the code, but I'm having trouble accessing the value from the maxValue IQueryable object.
So a sample from the categoryQuery set would look like this:
Id Group Order
----- ----- -----
100 T 0
101 T 1
102 T 2
103 T 3
So in the second maxValue query, I'd want the 3, because it is the max Order value out of all of the T Group values.
First question: How can I access the int value in maxValue?
Second question: How can I simplify my queries into a single query?
Thank you
It seems to as simple as ordering by Order, descending, and then take the first item:
var group = "T";
var maxValue = (from m
in ctx.Material
where m.Group == group
orderby m.Order descending
select m).FirstOrDefault();

Speed up the linq group by statement

I have a table like this
UserID Year EffectiveDate Type SpecialExpiryDate
1 2015 7/1/2014 A
1 2016 7/1/2015 B 10/1/2015
there is no ExpriyDate in the table because it is only valid for one year, so the expiry date can be calculated from the effective date by adding a year.
The result I want to get is like this (the current year's effective date and the next year's expiry date)
UserID EffectiveDate ExpiryDate
1 7/1/2014 7/1/2016
And If the user's type is B, then there will be a special expiry date, so for this person, the result will be
UserID EffectiveDate ExpiryDate
1 7/1/2014 10/1/2015
Here is the code I wrote
var result = db.Table1
.Where(x => x.Year>= 2015 && (x.Type == "A" || x.Type == "B"))
.GroupBy(y => y.UserID)
.OrderByDescending(x => x.FirstOrDefault().Year)
.Select(t => new
{
ID = t.Key,
Type = t.FirstOrDefault().Type,
EffectiveDate = t.FirstOrDefault().EffectiveDate,
ExpiryDate = t.FirstOrDefault().SpecialExpiryDate != null ? t.FirstOrDefault().SpecialExpiryDate : (t.Count() >= 2 ? NextExpiryDate : CurrentExpiryDate)
}
);
The code can get the result I need, but the problem is that in the result set there are about 10000 records which took about 5 to 6 seconds. The project is for a web search API, so I want to speed it up, is there a better way to do the query?
Edit
Sorry I made a mistake, in the select clause it should be
EffectiveDate = t.LastOrDefault().EffectiveDate
but in the Linq of C#, it didn't support this LastOrDefault function transfered to sql, and it cause the new problem, what is the easiest way to get the second item of the group?
You could generate the calculated data on the fly, using a View in your database.
Something like this (pseudocode):
Create View vwUsers AS
Select
UserID,
Year,
EffectiveDate,
EffectiveData + 1 as ExpiryDate, // <--
Type,
SpecialExpiryDate
From
tblUsers
And just connect your LINQ query to that.
Try this:
var result =
db
.Table1
.Where(x => x.Year>= 2015 && (x.Type == "A" || x.Type == "B"))
.GroupBy(y => y.UserID)
.SelectMany(y => y.Take(1), (y, z) => new
{
ID = y.Key,
z.Type,
z.EffectiveDate,
ExpiryDate = z.SpecialExpiryDate != null
? z.SpecialExpiryDate
: (t.Count() >= 2 ? NextExpiryDate : CurrentExpiryDate),
z.Year,
})
.OrderByDescending(x => x.Year);
The .SelectMany(y => y.Take(1) effectively does the .FirstOrDefault() part of your code. By doing this once rather than for many properties you may improve the speed immensely.
In a test I performed using a similarly structured query I got these sub-queries being run when using your approach:
SELECT t0.increment_id
FROM sales_flat_order AS t0
GROUP BY t0.increment_id
SELECT t0.hidden_tax_amount
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000001]
SELECT t0.customer_email
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000001]
SELECT t0.hidden_tax_amount
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000002]
SELECT t0.customer_email
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000002]
(This continued on for two sub-queries per record number.)
If I ran my approach I got this single query:
SELECT t0.increment_id, t1.hidden_tax_amount, t1.customer_email
FROM (
SELECT t2.increment_id
FROM sales_flat_order AS t2
GROUP BY t2.increment_id
) AS t0
CROSS APPLY (
SELECT t3.customer_email, t3.hidden_tax_amount
FROM sales_flat_order AS t3
WHERE ((t3.increment_id IS NULL AND t0.increment_id IS NULL) OR (t3.increment_id = t0.increment_id))
LIMIT 0, 1
) AS t1
My approach should be much faster.

How to return value from 2 tables in one linq query

please consider this table:
PK_Id Number Year Month Value
-------------------------------------------------------------------------
1 1 2000 5 100000
410 4 2000 6 10000
8888 1 2001 5 100
I Id=8888 and now I want to first select record with Id=8888 and second select previos year of that record*(I mean Id=1)*. How I can do this with linq and one query.
basically we have some queries that first it should find a value from a table (that may be not PK) and find Corresponding records in another tables. How I can do this with linq and one reference to database.
thanks
from a in Record
where a.PK_Id == 8888
from b in Record
where b.Number == a.Number && b.Year == a.Year - 1
select new { Current = a, Previous = b }
or
Record
.Where(a => a.PK_Id == 888)
.SelectMany(a =>
Record
.Where(b => b.Number == a.Number && b.Year == a.Year - 1)
.Select(b => new { Current = a, Previous = b })
If I understand your question right, then you need to filter the data of one table and join two tables.
You can join the tables and filter your data
var query = from c in Table1
join o in Table2 on c.Col1 equals o.Col2
where o.Col3 == "x"
select c;
or you can filter your data from one table and then join the tables (result will be the same)
var query = from c in Table1.Where(item => item.Col3 == "x")
join o in Table2 on c.Col1 equals o.Col2
select c;

Categories