Nested LINQ query to select 'previous' value in a list - c#

I have a list of dates. I would like to query the list and return a list of pairs where the first item is a date and the second is the date which occurs just before the first date (in the list).
I know this could easily be achieved by sorting the list and getting the respective dates by index, I am curious how this could be achieved in LINQ.
I've done this in SQL with the following query:
SELECT Date,
(SELECT MAX(Date)
FROM Table AS t2
WHERE t2.Date < t1.Date) AS PrevDate
FROM Table AS t1

It is easy as converting your current query into a LINQ query:
var result = table.Select(x =>
new
{
Date = x.Date,
PrevDate = table.Where(y => y.Date < x.Date)
.Select(y => y.Date)
.Max()
});

List<DateTime> dates = new List<DateTime>()
{
DateTime.Now.AddDays(1),
DateTime.Now.AddDays(7),
DateTime.Now.AddDays(3),
DateTime.Now.AddDays(6),
DateTime.Now.AddDays(5),
DateTime.Now.AddDays(2),
DateTime.Now.AddDays(3),
};
dates = dates.OrderByDescending(x => x).ToList();
var result = dates.Skip(1)
.Select((x, i) => new { Date = dates[i], PreviousDate = x });

Related

Merge two arrays into one and get each item's occurrence in their parent array

I have two arrays of dates. I want to merge them into one array that stores each (distinct) dates and their occurrences in their parent arrays.
For example:
var today = new DateTime (year:2022, month:01, day:01);
var firstDateArray = new DateTime[] { today.AddDays(1), today.AddDays(7), today, today.AddDays(3), today };
var secondDateArray = new DateTime[] { today.AddDays(7), today, today.AddDays(3), today.AddDays(3), today.AddDays(1) };
I want to get something like this as a result.
How do I go about this please?
Here I used a tuple (DateTime, int, int) to represent the data:
List<(DateTime, int, int)> result = firstDateArray.
Concat(secondDateArray).
Distinct().
Select(x => (
x,
firstDateArray.Where(y => y == x).Count(),
secondDateArray.Where(z => z == x).Count()
)).
ToList();
You can use .GroupBy, .Select, and .Join for this:
var today = new DateTime (year:2022, month:01, day:01);
var firstDateArray = new DateTime[] { today.AddDays(1), today.AddDays(7), today, today.AddDays(3), today };
var secondDateArray = new DateTime[] { today.AddDays(7), today, today.AddDays(3), today.AddDays(3), today.AddDays(1) };
var firstGrouped = firstDateArray
.GroupBy(d => d)
.Select(g => new { Date = g.Key, Count = g.Count() });
var secondGrouped = secondDateArray
.GroupBy(d => d)
.Select(g => new { Date = g.Key, Count = g.Count() });
var joined = firstGrouped
.Join(secondGrouped, f => f.Date, s => s.Date, (f, s) => new { Date = f.Date, FirstCount = f.Count, SecondCount = s.Count })
.OrderBy(j => j.Date)
.ToList();
GroupBy basically collects items with a common key (in this case the DateTime) and then lets you iterate through the collections.
Join takes 4 arguments: the second enumerable, the key selector for the first, the key selector for the second (it uses these to facilitate the join), and the result selector to produce the result you want.
Documentation:
GroupBy
Select
Join
OrderBy
ToList
Try it online

How can I group datetime using Linq method syntax but ignoring time section

I'm working on an ASP.NET MVC app written in C#, and I need to display sum of how many times people have viewed videos captured in a SQL database. I'm able to do it in SQL:
SELECT
CAST([Date] AS DATE),
SUM([Views])
FROM
db
WHERE
[DATE] >= '2022-01-28'
AND [DATE] <= '2022-07-28'
GROUP BY
CAST([Date] AS DATE)
But, now the problem is to translate it to Linq in my controller, this is what I have so far (where start and end date are variables):
var data = (Context.Where(w => w.Date >= startDate && w.Date <= endDate)
.GroupBy(g => new { date = g.Date })
.Select(s => new { date = s.Key, sum = s.Sum(c => c.Views) })).ToList();
This will work but will group by date and time which has more rows.
For example, what my code returns:
date = 3/3/2022 3:00:00 AM, sum = 1
date = 3/3/2022 4:00:00 AM, sum = 2
date = 3/3/2022 5:00:00 AM, sum = 3
What I want:
date = 3/3/2022, sum = 6
You can simply use EntityFunctions.TruncateTime. I had in the past tried to put in a PR to allow using DateTime.Date, not sure if it ever landed.
var data = (Context
.Where(w => w.Date >= startDate && w.Date <= endDate)
.GroupBy(g => EntityFunctions.TruncateTime(g.Date))
.Select(s => new { date = s.Key, sum = s.Sum(c => c.Views) })
).ToList()

C#- LINQ Grouping on a custom created date range

So, I have this LINQ query:
var Result = from u in Users
group u by new {u.AccountType, u.Id,u.CreationDate} into usergroup
select new {id=usergroup.Key.Id,CreationDate=usergroup.Key.CreationDate,AccountType=usergroup.Key.AccountType};
that returns the following data set:
I am able to get the individual group count like this:
var myresult=Result.GroupBy(n=>n.AccountType).Select(n=>new {AccountType=n.Key,TotalCount=n.Count()});
which gives me:
Now suppose, I define a custom date range of Months from January-December, how can I do a group on the first data-set to give me count of AccountType based on each month based on the CreationDate column into my custom date range?
I am trying to understand what type you would like your result to be in. If you want a list of Months, each of which would have a tally of Accounts per AccountType, then you could try something like this:
var myResult2 = result.GroupBy(o => o.CreationDate.Month).Select(monthGroup => new
{
Month = System.Globalization.DateTimeFormatInfo.CurrentInfo.GetMonthName(monthGroup.Key),
Accounts = monthGroup.GroupBy(o => o.AccountType).ToDictionary(accountGroup => accountGroup.Key, accountGroup => accountGroup.Count())
});
Try this:
I think this will do your job
.GroupBy(g => new { g.CreationDate.Date.Month, g.AccountType })
.Select(t => new { t.Key.AccountType, t.Key.Month, Count = t.Count() });
If CreationDate is a string:
var group = result.GroupBy(g => new { DateTime.Parse(g.CreationDate).Date.Month, g.AccountType })
.Select(user => new { user.Key.AccountType, user.Key.Month, Count = user.Count() });
foreach (var g in group)
{
Console.WriteLine($"Users with Month[{g.Month}] : {g.Count}, AccType: {g.AccountType};");
}
I Have Try Below Code:
var data = Users.GroupBy(x => new {x.column1 , x.column2 ,...})
.Select(y=> new className() { column1 = y.Key.column1 , column2 = y.Key.column2}).ToList<className>();

Add missing dates to list

I have written a solution which basically adds missing date and sets the sales property for that date in my collection to 0 where it's missing like this:
int range = Convert.ToInt32(drange);
var groupedByDate = tr.Union(Enumerable.Range(1, Convert.ToInt32(range))
.Select(offset => new MyClassObject
{
Date = DateTime.Now.AddDays(-(range)).AddDays(offset),
Sales = 0
})).GroupBy(x => x.Date)
.Select(item => new MyClassObject
{
Sales = item.Sum(x => x.Sales),
Date = item.Key
})
.OrderBy(x => x.Date)
.ToList();
The first solution where the dates from DB were grouped by and they were missing looked like this:
var groupedByDate = tr
.GroupBy(x => x.TransactionDate.Date)
.Select(item => new MyClassObject
{
Sales = item.Sum(x => x.QuantityPurchased),
Date = item.Key.ToString("yyyy-MM-dd")
})
.OrderBy(x => x.Date)
.ToList();
I don't really like the way I did it in first solution, the code looks very messy and I honestly believe it can be written in a better manner..
Can someone help me out with this?
P.S. The first solution above that I've shown works just fine, but I would like to write something better which is more prettier to the eyes, and it looks quite messy (the first solution I wrote)...
How about generate the date range and then left join that with the result from your original query. And than set Sales to 0 when there is no match.
int range = 2;
var startDate = DateTime.Now;
var dates = Enumerable.Range(1, range)
.Select(offset => startDate.AddDays(-offset).Date);
var groupedByDate = from date in dates
join tmp in groupedByDate on date equals tmp.Date into g
from gr in g.DefaultIfEmpty()
select new MyClassObject
{
Sales = gr == null ? 0 : gr.Sales,
Date = date
};
Here is the easy way to do this:
var lookup = tr.ToLookup(x => x.TransactionDate.Date, x => x.QuantityPurchased);
var quantity = lookup[new DateTime(2017, 6, 29)].Sum();
If you want a range of dates then it's just this:
var startDate = new DateTime(2017, 6, 1)
var query =
from n in Enumerable.Range(0, 30)
let TransactionDate = startDate.AddDays(n)
select new
{
TransactionDate,
QuantityPurchases = lookup[TransactionDate].Sum(),
};
Simple.

linq groupby Months and add any missing months to the grouped data

I have created a linq statement which seems to be working ok. I may or maynot have written it correctly however its returning my expected results.
var grouped = RewardTransctions.GroupBy(t => new
{
t.PurchaseDate.Value.Month
}).Select(g => new TransactionDetail()
{
Month =
g.Where(w=>w.EntryType==1).Select(
(n =>
n.PurchaseDate.Value.Month))
.First(),
TransactionAmount = g.Count()
});
Now the results are returning 5 values grouped by months. Is it possible to add the 7 other missing months with a TransactionAmount = 0 to them?
The reason for my madness is I am trying to bind these values to a chart and having my x axis based on months. Currently its only showing the 5 months of records. If my data doesnt return any value for a month I some how want to add in the 0 value.
Any suggestions?
It's very simple if you use .ToLookup(...).
var lookup =
(from w in RewardTransctions
where w.EntryType == 1
select w).ToLookup(w => w.PurchaseDate.Value.Month);
var grouped =
from m in Enumerable.Range(1, 12)
select new TransactionDetail()
{
Month = m,
TransactionAmount = lookup[m].Count(),
};
How's that for a couple of simple LINQ queries?
When you're using LINQ to Objects, this query should do the trick:
var grouped =
from month in Enumerable.Range(1, 12)
select new TransactionDetail()
{
Month = month,
TransactionAmount = RewardTransactions
.Where(t => t.PurchaseDate.Value.Month == month).Count()
};
When RewardTransactions however is an IQueryable, you should first call AsEnumerable() on it.
Why not do it just like this:
var grouped =
RewardTransctions.GroupBy(t => t.PurchaseDate.Value.Month).Select(
g => new TransactionDetail { Month = g.Key, TransactionAmount = g.Count() }).ToList();
for (var i = 1; i <= 12; ++i)
{
if (grouped.Count(x => x.Month == i) == 0)
{
grouped.Add(new TransactionDetail { Month = i, TransactionAmount = 0 });
}
}
It's not entirely LINQ, but straight forward. I also simplified your LINQ query a bit ;-)
I guess If you do not use an anonymoustype(var), but create a custom type and do a .ToList() on your query that you can use .Add() on your list and bind the chart to the list.

Categories