How to get the latest number using LINQ - c#

Here lists a payment history of a customer in a db table
CustomerId PayId FeePaid
xx-yy-zz 37 0
xx-yy-zz 32 0
xx-yy-zz 31 30.00
xx-yy-zz 28 0
xx-yy-zz 26 0
xx-yy-zz 18 35.99
xx-yy-zz 17 0
xx-yy-zz 16 0
xx-yy-zz 9 12.00
xx-yy-zz 6 0
The PaymentId column is auto incremented.
How to get the last payment of this customer, i.e., the number $30.00?
My project is Asp.net API, so I need use LINQ to get the number.

If we assume that we're ignoring zeros, and that PayId is monotonically incrementing, then presumably:
as LINQ:
var val = ctx.SomeTable
.Where(x => x.CustomerId == customerId && x.FeePaid != 0)
.OrderByDescending(x => x.PayId)
.Select(x => x.FeePaid)
.First();
or as SQL:
select top 1 FeePaid
from SomeTable
where CustomerId = #customerId
and FeePaid <> 0
order by PayId desc

Try this linq expression:
var result = await (from d in _ctx.MyTable
where d.CustomerId="xx-yy-zz" && d.FreePaid > 0
orderby d.PayId descending
select d.FreePaid).FirstOrDefaultAsync();
tried to avoid negative queries
awaitable function

You wrote:
The PaymentId column is auto incremented
My advice would be to group the PaymentHistories per user, so by common value of CustomerId.
Then for each group, keep the PaymentHistory that has the highest value of PaymentId. After all: PaymentId is auto-increments, so the PaymentHistory in the group of PaymentHistories of Customer X is the one with the highest PaymentId
For this I used the overload of Queryable.GroupBy that has a parameter resultSelector, so I can precisely specify what I want in my result.
IQueryable<PaymentHistory> paymentHistories = ...
var lastCustomerPayments = paymentHistories.GroupBy(
// parameter keySelector: make groups with same CustomerId
paymentHistory => paymentHistory.CustomerId,
// parameter resultSelector: for every CustomerId and all PaymentHistories
// that have this value for CustomerId, make one new:
(customerId, paymentHistoriesWithThisCustomerId) => new
{
CustomerId = customerId,
// get the feePaid of the PaymentHistory with the largest PaymentId
FeePaid = paymentHistoriesWithThisCustomerId
.OrderByDescending(paymentHistory => paymentHistory.PaymentId)
.Select(paymentHistory => paymentHistory.FeePaid)
.FirstOrDefault(),
}
If you don't want FeePaid, but also the PaymentId, use the following resultSelector:
(customerId, paymentHistoriesWithThisCustomerId) => new
{
CustomerId = customerId,
LastPayment = paymentHistoriesWithThisCustomerId
.OrderByDescending(paymentHistory => paymentHistory.PaymentId)
.Select(paymentHistory => new
{
PaymentId = paymentHistory.PaymentId,
FeePaid = paymentHistory.FeePaid,
})
.FirstOrDefault();
}

Related

Order by descending group by Max date, then order by single row in the group by date ascending using linq Method sintax

I have a table like this
Id Date GroupId Text Column1 Column2 ...
1 2020-02-02 1 .... .... ...
2 2020-02-04 1 .... .... ...
3 2020-02-03 1 .... .... ...
4 2020-02-02 2 .... .... ...
5 2020-02-05 2 .... .... ...
I need to obtain this result:
Id Date GroupId Text Column1 Column2 ...
5 2020-02-05 2 .... .... ...
4 2020-02-02 2 .... .... ...
1 2020-02-02 1 .... .... ...
3 2020-02-03 1 .... .... ...
2 2020-02-04 1 .... .... ...
I explain I need to get before all rows of the group 2 because the max date is in the group 2... I need to order the group by date descending but every single group should be ordered by date ascending.
I find difficult to write it in sql too. Can anyone help me please?
Thank you
EDIT:
Here what I have tried to do:
var result = messages.Select(m => new MyViewModel()
{
Id = m.Id,
Date = m.Date,
Text = m.Text,
GroupId = m.GroupId
}).GroupBy(d => d.GroupId)
.SelectMany(g => g)
.OrderByDescending(x => x.GroupId)
.ThenBy(x => x.Date);
But it does not work
I suggest creating an intermediate result having a GroupDate property. In my example I am using C# 7.0 Tuples. You could use anonymous types as well, if you are using an older C# version.
var result = messages
.GroupBy(m => m.GroupId) // Create Tuple ...
.Select(g => (Group: g, GroupDate: g.Max(m => m.Date))) // ... with group and group date.
.SelectMany(a => // Expand the group
a.Group.Select(m => // Create new tuple with group date and model.
(a.GroupDate,
Model: new MyViewModel() {
Id = m.Id,
Date = m.Date,
Text = m.Text,
GroupId = m.GroupId
})))
.OrderByDescending(x => x.GroupDate)
.ThenBy(x => x.Model.Date)
.Select(x => x.Model); // Extract the view model from the tuple.
Result:
Id = 2, Date = 2020-02-02, GroupId = 2
Id = 2, Date = 2020-02-05, GroupId = 2
Id = 1, Date = 2020-02-02, GroupId = 1
Id = 1, Date = 2020-02-03, GroupId = 1
Id = 1, Date = 2020-02-04, GroupId = 1
Example for tuple:
var t = (X: 15, Name: "axis");
Print($"X = {t.X}, Name = {t.Name}");
The name of a tuple property can also be inferred like in (a.GroupDate, Model: ...). The first property will automatically be called GroupDate. The second is named explicitly Model.
In SQL, you can use window functions:
order by
max(date) over(partition by groupId),
case when max(date) over(partition by groupId) = max(date) over() then date end desc,
date
You want to order the groups by their max date. Then within each group, order by the date descending.
I would recommend these keys for the order by:
maximum date for each group (desc)
groupId to keep each group together (order doesn't matter)
flag to put the overall maximum date first
date for all other rows (asc)
So:
order by max(date) over (partition by groupId) desc,
groupId, -- put each group together
(case when date = max(date) over() then 1 else 2 end),
date

join table and get the record which has minimum value?

I have the following collection Model: Hotel
public class Hotel {
int HotelId {get;set;}
decimal Price {get;set;}
int vendorId {get;set;}
int vendorHotelId {get;set;}
}
The records will be like this
HotelId Price VendorId VendorHotelId
1 100 1 0
2 200 2 0
3 300 1 0
4 400 2 1
If the VendorHotelId is equal to HotelId then I need to select the record which has the cheapest price in LINQ.
I want the result like this
HotelId Price VendorId VendorHotelId
1 100 1 0
2 200 2 0
3 300 1 0
Can anyone help me to solve this query?
You can use a conditional expression to group based on your condition, then get the minimum price from each group, which will be the one hotel if no matching vendorHotelId exists.
var ans = (from h in hotels
let hasVendor = h.vendorHotelId > 0
group h by hasVendor ? h.vendorHotelId : h.HotelId into hg
let hmin = hg.OrderBy(h => h.Price).First()
select new {
HotelId = hmin.HotelId,
Price = hmin.Price,
vendorId = hmin.vendorId
})
.ToList();
Update: Since you seem to be using fluent syntax based on your comment, here is a translation:
var ans2 = hotels.Select(h => new { h, hasVendor = h.vendorHotelId > 0 })
.GroupBy(hv => hv.hasVendor ? hv.h.vendorHotelId : hv.h.HotelId, hv => hv.h)
.Select(hg => hg.OrderBy(h => h.Price).First())
.Select(hmin => new {
HotelId = hmin.HotelId,
Price = hmin.Price,
vendorId = hmin.vendorId
})
.ToList();
NB: Somewhere someone should write an article on the advantages of conditional GroupBy expressions for unusual groupings.

LINQ - Group By with Having?

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

EF Sum between 3 tables

Say we got a Database design like this.
Customer
Id Name
1 John
2 Jack
Order
Id CustomerId
1 1
2 1
3 2
OrderLine
Id OrderId ProductId Quantity
1 1 1 10
2 1 2 20
3 2 1 30
4 3 1 10
How would I create an entity framework query to calculate the total Quantity a given Customer has ordered of a given Product?
Input => CustomerId = 1 & ProductId = 1
Output => 40
This is what I got so far, through its not complete and still missing the Sum.
var db = new ShopTestEntities();
var orders = db.Orders;
var details = db.OrderDetails;
var query = orders.GroupJoin(details,
order => order.CustomerId,
detail => detail.ProductId,
(order, orderGroup) => new
{
CustomerID = order.CustomerId,
OrderCount = orderGroup.Count()
});
I find it's easier to use the special Linq syntax as opposed to the extension method style when I'm doing joins and groupings, so I hope you don't mind if I write it in that style.
This is the first approach that comes to mind for me:
int customerId = 1;
int productId = 1;
var query = from orderLine in db.OrderLines
join order in db.Orders on orderLine.OrderId equals order.Id
where order.CustomerId == customerId && orderLine.ProductId == productId
group orderLine by new { order.CustomerId, orderLine.ProductId } into grouped
select grouped.Sum(g => g.Quantity);
// The result will be null if there are no entries for the given product/customer.
int? quantitySum = query.SingleOrDefault();
I can't check what kind of SQL this will generate at the moment, but I think it should be something pretty reasonable. I did check that it gave the right result when using Linq To Objects.

linq group by, order by

I have the following list
ID Counter SrvID FirstName
-- ------ ----- ---------
1 34 66M James
5 34 66M Keith
3 55 45Q Jason
2 45 75W Mike
4 33 77U Will
What I like to do is to order by ID by ascending and then
get the first value of Counter, SrvID which are identical (if any).
So the output would be something like:
ID Counter SrvID FirstName
-- ------ ----- ---------
1 34 66M James
2 45 75W Mike
3 55 45Q Jason
4 33 77U Will
Note how ID of 5 is removed from the list as Counter and SrvID was identical to what I had for ID 1 but as ID 1 came first
I removed 5.
This is what I would do but not working
var result = (from ls in list1
group ts by new {ls.Counter, ls.SrvID}
order by ls.ID
select new{
ls.ID,
ls.Counter.FirstOrDefault(),
ls.SrvID.First,
ls.FirstName}).ToList()
list1.GroupBy(item => new { Counter = item.Counter, SrvID = item.SrvID })
.Select(group => new {
ID = group.First().ID,
Counter = group.Key.Counter,
SrvID = group.Key.SrvID,
FirstName = group.First().FirstName})
.OrderBy(item => item.ID);
Group the records up, and pick a winner from each group.
var query =
from record in list1
group record by new {record.Counter, record.SrvID } into g
let winner =
(
from groupedItem in g
order by groupedItem.ID
select groupedItem
).First()
select winner;
var otherQuery = list1
.GroupBy(record => new {record.Counter, record.SrvID })
.Select(g => g.OrderBy(record => record.ID).First());

Categories