Hi I have some two tables,
Product:
ProductID | IssueDate | Amount
1 2017-06-01 1000
2 2017-06-01 1000
3 2017-06-02 500
and Credit:
ProductID | Amount
1 500
1 500
2 1000
3 500
if I use query like this from SQL Server:
SELECT p.IssueDate, SUM(p.Amount), SUM(p.Total)
FROM (SELECT p.IssueDate, SUM(p.Amount) AS Amount,
(SELECT TOP 1 SUM(c.Amount) FROM Credit c WHERE p.Id = c.ProductId) AS Total from Product p
GROUP BY p.IssueDate, p.Id) p
GROUP BY p.IssueDate
I get this result:
IssueDate | Amount | Total
2017-06-01 2000 2000
2017-06-02 500 500
In C# Linq I can get this data by two queries like this:
var data = from p in Products.Collection
select new
{
Product = p,
Credits = Credit.Collection.Where(c => c.ProductID == p.ID).Sum(c => c.Amount)
};
var result = from d in data
group d by new
{
IssueDate = d.Product.IssueDate
} into gr
select new
{
IssueDate = gr.Key.IssueDate,
Credits = gr.Sum(s => s.Credits),
Total = gr.Sum(s => s.Product.Amount)
};
var test = result.ToList();
Does anyone know a better (simpler) solution to get this result? Maybe in one query?
How about:
Products.Collection.GroupBy(x => x.IssueDate)
.Select(x => new
{
IssueDate = x.Key,
Amount = x.Sum(p => p.Amount),
Total = Credit.Collection.Where(c => x.Any(p => p.ProductID == c.ProductID)).Sum(c => c.Amount)
}).ToList();
Related
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
};
I'm looking to retrieve a list of the sum of property values in a list that is itself a property of another list, grouped by properties in the parent list, using LINQ.
To explain, I have a list of offers in a market with a trading date and hour of the day for a range of products, and a list of price and quantity bands within each offer. My classes are:
public class Offer
{
public DateTime TradingDate { get; set; }
public int HourOfDay { get; set; }
public string ProductName { get; set; }
public List<Band> OfferBands { get; set; }
}
public class Band
{
public decimal Price { get; set; }
public double Quantity { get; set; }
}
And what I'm looking to retrieve is the sum of Quantity for a certain Price for each TradingDate and HourOfDay, for every ProductName.
I haven't come up with a working solution, but as a start I'm trying something like (with a List<Offer> offers containing all offers, to retrieve quantities where the offer price < $10):
List<double> quantities = offers.SelectMany(o => o.Bands).Where(b => b.Price < 10).Select(b => b.Quantity)
But I don't know how to GroupBy the TradingDate and HourOfDay and retrieve the sum of Quantity. There can be multiple Offers with multiple OfferBands for different products, with various combinations of offer Prices, and I just want to get sum of Quantity for all products at a certain price grouped by date and time.
I could achieve this programmatically but I would like a LINQ solution. Thanks for your help.
Edit:
What I forgot to mention is that, where there are no Quantitys at the specified Price for a TradingDate and HourOfDay I would like to retrieve double.NaN (or 0).
With example data List<Offer> offers containing six Offers:
TradingDate | HourOfDay | ProductName | OfferBands
===================================================================
01/01/2017 | 1 | Chocolate | Price = 2, Quantity = 6
| | | Price = 5, Quantity = 10
-------------------------------------------------------------------
01/01/2017 | 2 | Chocolate | Price = 3, Quantity = 6
| | | Price = 5, Quantity = 20
-------------------------------------------------------------------
02/01/2017 | 1 | Chocolate | Price = 3, Quantity = 7
| | | Price = 6, Quantity = 9
-------------------------------------------------------------------
01/01/2017 | 1 | Cake | Price = 5, Quantity = 11
| | | Price = 8, Quantity = 3
-------------------------------------------------------------------
01/01/2017 | 2 | Cake | Price = 2, Quantity = 1
| | | Price = 8, Quantity = 4
-------------------------------------------------------------------
02/01/2017 | 1 | Cake | Price = 3, Quantity = 9
| | | Price = 5, Quantity = 13
-------------------------------------------------------------------
Selecting a sum of quantities for a given price, grouped by date and time, would give a List<double> output:
Where price >= 5
{ 24, 24, 22 }
Where price = 2
{ 6, 1, double.NaN }
Where price = 3
{ double.NaN, 6, 16 }
...where the output is the sum of quantities for all products at the specified prices for 01/01/2017 hour 1, 01/01/2017 hour 2, and 02/01/2017 hour 1.
Hopefully that is clear to follow.
First, you need to filter to get the desired Offers with the matching OfferBands.
You can create/pass-in a filter if you want to make this a function, I will just define it inline:
Func<Band, bool> filter = (Band b) => b.Price == 3;
Since you don't care about ProductName, I used an anonymous type, but you could use Offer instead. At this point, we throw out the empty slots as well:
var filteredOffers = offers.Select(o => new { TradingDate = o.TradingDate, HourOfDay = o.HourOfDay, OfferBands = o.OfferBands.Where(filter).ToList() }).Where(gb => gb.OfferBands.Count > 0);
Now, since you want to include empty slots for TradingDate+HourOfDay that are in the original data but were filtered out, group the filtered data and create a dictionary:
var mapQuantity = filteredOffers.GroupBy(o => new { o.TradingDate, o.HourOfDay })
.Select(og => new { og.Key.TradingDate, og.Key.HourOfDay, QuantitySum = og.Sum(o => o.OfferBands.Sum(ob => ob.Quantity)) })
.ToDictionary(og => new { og.TradingDate, og.HourOfDay }, og => og.QuantitySum);
Then, going back to the original offers group find all the distinct slots (TradingDate+HourOfDday) and match them up to the QuantitySum, filling empty slots with double.NaN and convert to a List:
var ans = offers.Select(o => new { o.TradingDate, o.HourOfDay }).Distinct().OrderBy(g => g.TradingDate).ThenBy(g => g.HourOfDay).Select(g => mapQuantity.TryGetValue(g, out var sumq) ? sumq : double.NaN).ToList();
After re-thinking, I realized you could simplify by preserving the slots that are empty in the filteredOffers and then set their values after grouping:
var filteredOffers = offers.Select(o => new { TradingDate = o.TradingDate, HourOfDay = o.HourOfDay, OfferBands = o.OfferBands.Where(filter).ToList() });
var ans = filteredOffers.GroupBy(o => new { o.TradingDate, o.HourOfDay })
.OrderBy(og => og.Key.TradingDate).ThenBy(og => og.Key.HourOfDay)
.Select(og => (og.Sum(o => o.OfferBands.Count) > 0 ? og.Sum(o => o.OfferBands.Sum(ob => ob.Quantity)) : double.NaN));
By using the IGrouping Key to remember the slots, you can simplify the query:
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
.OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
.Select(obg => {
var filteredOBs = obg.SelectMany(ob => ob).Where(filter).ToList();
return filteredOBs.Count > 0 ? filteredOBs.Sum(b => b.Quantity) : double.NaN;
});
If you are willing to give up the double.NaN for zero instead, you can make this even simpler:
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
.OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
.Select(obg => obg.SelectMany(ob => ob).Where(filter).Sum(b => b.Quantity));
Finally, to finish the dead horse off, some special extension methods can preserve the NaN returning property and use the simple query form:
public static class Ext {
static double ValuePreservingAdd(double a, double b) => double.IsNaN(a) ? b : double.IsNaN(b) ? a : a + b;
public static double ValuePreservingSum(this IEnumerable<double> src) => src.Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
public static double ValuePreservingSum<T>(this IEnumerable<T> src, Func<T, double> select) => src.Select(s => select(s)).Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
}
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
.OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
.Select(obg => obg.SelectMany(ob => ob).Where(filter).ValuePreservingSum(b => b.Quantity));
I believe I've been able to manage the groupings you are after, though I haven't done the summation of the (quantity)*(whatever price matches some condition), as hopefully that is something that you can customize however you need to.
To get things grouped, I had to use several nested projections and do each grouping individually (it was actually quite fun to work this out, the big sticking point is that LINQ's IGrouping isn't as straightforward to use as you might expect, so each time I grouped I did a projection with a Select):
var projected = offers.GroupBy(x => x.ProductName)
.Select(x => new
{
ProductName = x.Key,
Dates = x.GroupBy(y => y.TradingDate).ToList()
.Select(y => new
{
TradingDate = y.Key,
Times = y.GroupBy(z => z.HourOfDay).ToList()
.Select(zx => new
{
Time = zx.Key,
Items = zx.ToList()
})
})
}).ToList();
Hopefully, this will give you enough to start on for doing your summation with whatever extra checks you need for 0 items, prices not high enough, and so on.
Note that this query is probably not the most efficient if you're working directly with a database - it probably pulls more information than it really needs to at this point. I don't know enough about what you're working on to begin to optimize it, though.
var offers = new List<Offer>();
// flatten the nested list linq-style
var flat = from x in offers
from y in x.OfferBands
select new {x.TradingDate, x.HourOfDay, x.ProductName, y.Price, y.Quantity};
var grouped = from x in flat
group x by new {x.TradingDate, x.HourOfDay, x.ProductName}
into g
select new
{
g.Key.TradingDate,
g.Key.HourOfDay,
g.Key.ProductName,
OfferBands = (from y in g
group y by new {y.Price}
into q
select new {Price = q.Key, Quantity = q.Sum(_ => _.Quantity)}).ToList()
};
foreach (var item in grouped)
{
Console.WriteLine(
"TradingDate = {0}, HourOfDay = {1}, ProductName = {2}",
item.TradingDate,
item.HourOfDay,
item.ProductName);
foreach (var offer in item.OfferBands)
Console.WriteLine(" Price = {0}, Qty = {1}", offer.Price, offer.Quantity);
}
Good morning all,
I have been stuck on this all morning and feel like I've hit a wall. I'd love any advice that can be given at this point.
My table is basically as follows:
PatientName|LivingSpace
-----------|-----------
Patient 1 | Unit 1
Patient 2 | Unit 1
Patient 3 | Unit 2
Patient 4 | Unit 2
Patient 5 | Unit 3
Patient 6 | Unit 3
Patient 7 | Unit 3
Patient 8 | Unit 3
I need a LINQ to SQL query to illustrate this:
Unit|Count
----|-----
Unit 1 | 2
Unit 2 | 2
Unit 3 | 4
TOTAL | 8
My SQL query works fine, I'm just having issues with converting it to LINQ:
SELECT LivingSpace, COUNT(LivingSpace) AS LivingSpace
FROM PatientTable
WHERE Status = 'Active'
GROUP BY LivingSpace
UNION ALL
SELECT 'SUM' LivingSpace, COUNT(LivingSpace)
FROM PatientTable
var counts = from x in ctx.PatientTable
group x by x.LivingSpace into y
select new { Key = y.Key Count = y.Count() };
var total = new { Key = "Total" , Count = ctx.PatientTable.Count() };
var full = counts.ToList();
full.Add(total);
If you want to do it all in one query the following should work (adjusting for the actual names of your properties of course).
context.PatientTable.GroupBy(a => a.LivingSpace.Name, a => 1)
.Select(a => new
{
a.Key,
Total = a.Sum(q => q)
})
.Union(PatientTable.Select(a => new
{
Key = "Total",
Total = PatientTable.Count()
}))
var report = patients
.GroupBy(p => p.LivingSpace)
.Select(g => new
{
Unit = g.Key,
Count = g.Count()
})
.Union(patients
.Select(p => new
{
Unit = "Total",
Count = patients.Count
}));
Something like this should work and just run one query.
var results = db.PatientTable
.GroupBy(p => p.LivingSpace)
.Select(grp => new
{
Unit = grp.Key,
Count = grp.Count()
})
.Union(db.PatientTable
.GroupBy(p => 1)
.Select(grp => new
{
Unit = "Total",
Count = grp.Count()
}));
I see you got the answer, but for learning purposes, here is side by side conversion.
Your SQL (with some aliases added for better comparison)
SELECT P.LivingSpace, COUNT(P.*) AS Count
FROM PatientTable AS P
WHERE P.Status = 'Active'
GROUP BY P.LivingSpace
UNION ALL
SELECT 'SUM' AS LivingSpace, COUNT(P.*) AS Count
FROM PatientTable AS P
The same single query in LINQ
var query =
(
from p in db.PatientTable
where p.Status = "Active"
group p by p.LivingSpace into g
select new { LivingSpace = g.Key, Count = g.Count() }
)
.Concat
(
from p in db.PatientTable
group p by "SUM" into g
select new { LivingSpace = g.Key, Count = g.Count() }
);
I have table 4 columns.
JobId StateId Salary Expense
1 1 35,000 31,000
1 1 33,000 25,000
1 2 28,000 26,000
2 2 7,000 16,000
2 2 6,000 20,000
2 1 9,000 22,000
2 1 15,000 29,000
By using LINQ in C#, i want to group by JobId and StateId combination.For each combination i want an array of Salary and array of Expense.
I can get one column as a array by for each combination, by using this
(from r in myTable.AsEnumerable()
group r by new {
jobId = r.Field<int>("JobId"),
stateId = r.Field<int>("StateId")
}).ToDictionary(
l => Tuple.Create(l.Key.jobId, l.Key.stateId),
l=> (from i in l select i.Field<double>("Salary")).AsEnumerable()
);
How can i have Salary and Expense in two array for each group??
My goal is to find average Salary and average Expense for each combination and do some other operation. Or at least tell me how can select multiple columns as separate array.
Note: I don't want collection of anonymous objects for each combination.
To select two different columns as collections in your query you can do this:
var result =
(from r in myTable.AsEnumerable()
group r by new
{
jobId = r.Field<int>("JobId"),
stateId = r.Field<int>("StateId")
} into g
select new
{
g.Key,
Salaries = g.Select(x => x.Field<double>("Salary")),
Expenses = g.Select(x => x.Field<double>("Expense"))
})
.ToDictionary(
l => Tuple.Create(l.Key.jobId, l.Key.stateId),
l => new { l.Salaries, l.Expenses }
);
Then you can compute the averages fairly easily:
var averageSalary = result[...].Salaries.Average();
var averageExpense = result[...].Expenses.Average();
But if all you really need is the averages, this will work:
var result =
(from r in myTable.AsEnumerable()
group r by new
{
jobId = r.Field<int>("JobId"),
stateId = r.Field<int>("StateId")
} into g
select new
{
g.Key,
AverageSalary = g.Average(x => x.Field<double>("Salary")),
AverageExpense = g.Average(x => x.Field<double>("Expense"))
})
.ToDictionary(
l => Tuple.Create(l.Key.jobId, l.Key.stateId),
l => new { l.AverageSalary, l.AverageExpense }
);
Do not use LINQ for this kind of statement. If you need to count the groupings and the compute some sort of average salary/expense you could try a list:
List<myType> myList = new List<myType>();
//add stuff to myList
List<myType> JobID1 = new List<myType();
List<myType> JobID2 = new List<myType();
foreach(var item in myList)
{
if(item.JobID == 1)
JobID1.add(item);
if(item.JobID == 2)
JobID2.add(item);
}
int avgSalOne;
foreach(var item in JobID1)
{
avgSalOne += item.Salary;
}
avgSalOne = avgSaleOne / JobID2.Count;
//Note that you get Job Id 2 average salary the same way, and also the Expense by changing item. Salary to item.Expense
I'm using the following LINQ to select data from a table:
(from m in entity.Results
where m.Group == 0 ||
m.Group == 1
orderby m.Points descending
select m);
This gives me a result of all Users who are in Group 1 or 2. With that i can display the points they have. But this shows me the points they have in Group 1 and Group 2 separately.
How can i group them and display the total points they have? So instead of this (What i have now):
user1 - group1 - 10
user1 - group2 - 7
user2 - group1 - 7
user2 - group2 - 5
I want this:
user1 - total: 17
user2 - total: 12
How do i have to adjust my query to get a result set like that?
You need to group the users, then use Sum to calculate the TotalPoints:
from m in entity.Results
where m.Group == 0 || m.Group == 1
group m by m.User into g
let TotalPoints = g.Sum(m => m.Points)
orderby TotalPoints descending
select new { User = g.Key, Username = g.Key.Username, TotalPoints };
entity.Results
.Where(m => m.Group == 0 || m.Group == 1)
.GroupBy(m => m.UserID)
.Select(m => new { User = m.Key, TotalPoints = m.Sum(v => v.Points) })
.OrderByDescending(m => m.TotalPoints);
Hi Vivendi use this(Please edit according to your requirement)
var q = (from h in entity.Results
group h by new { h.UserID} into hh
select new {
hh.Key.UserID,
Score = hh.Sum(s => s.Points )
}).OrderByDescending(i => i.Points);
Output
total: 17
total: 12
Another example with more than one sum and a join
from e in _context.LearnResults
join c in _context.Country on e.CountryId equals c.CountryId
where c.DomainId.Equals("xx")
group e by e.Country.Name into newCountry
let Approved = newCountry.Sum(e => e.Approved)
let Total = newCountry.Sum(e => e.Total)
select new LearnResults() { CountryName = newCountry.Key, Approved= Approved, Total=Total };