I have two IEnumerables. One contains dates, the other contains data.
DateTime start = DateTime.Today.AddDays(-21);
var dates = Enumerable.Range(0, 21).Select(n => start.AddDays(n)).ToArray();
var data = MyClass.Data.Select(x => new { Date = x.Date, Views = x.Count });
I'm trying to build a table which shows the Views on a given day. However, data contains some gaps. How do I write a linq query which joins the two sets, and returns the Views number when present or 0 when there is no matching object in data?
I can do this the old fashioned way with foreach statements but I'd like to know how to do it in Linq.
Not sure i fully understood your question.If you want to generate a list of days which has at least one views,then this will get the job done.
DateTime start = DateTime.Today.AddDays(-21);
//Sample view data
var viewsData = new[] {new {id = "id", date = new DateTime(2013, 4, 12), views = 25}};
var dates = Enumerable.Range(0, 21)
.Select(d => start.AddDays(d))
.Select(n => new
{
Day = n,
views =viewsData.Any(x => x.date == n)
? viewsData.FirstOrDefault(v => v.date == n).views
: 0
});
Zero is populated for days having no views
This should work:
var data = from day in Enumerable.Range(0, 21).Select(n => start.AddDays(n))
join d in MyClass.Data.Select(x => new { Date = x.Date, Views = x.Count })
on day equals d.Date into gj
from dd in gj.DefaultIfEmpty()
select new { Date = day, Views = dd == null ? 0 : dd.Views };
This returns the views-number when there is one at the given day and 0 otherwise.
How to: Perform Left Outer Joins
Have a look at this MSDN article: http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx
It explains how to do a left outer join which is what you need in this case.
Related
I am trying to return number of records per day for a week. My code returns me only records for the day that has records, I want to return 0 if no records were inserted that day. Following is my code.
//Desired Result Sun to Sat = [0,3,5,1,0,18,0] , currently I only get [3,5,1,18]
// DateTime.Now.FirstDayOfWeek() is extension method that return first day of the week. Thank you for help.
var records = logs.Where(x=>x.date> DateTime.Now.FirstDayOfWeek())
.GroupBy(x => x.date.Day)
.Select(x =>
x.Any()?x.Count():0
).ToArray();
Using another extension method to create a range of dates:
public static IEnumerable<DateTime> Range(this DateTime startDate, int numberOfDays) => Enumerable.Range(0, numberOfDays).Select(e => startDate.AddDays(e));
You can GroupJoin with the week of dates and create the array:
var recordgroups = logs.Where(x => x.date > DateTime.Now.FirstDayOfWeek())
.GroupBy(x => x.date.Day);
var records = DateTime.Now.FirstDayOfWeek().Range(7)
.GroupJoin(recordgroups, wd => wd.Day, lg => lg.Key, (_, lg) => lg.Any() ? lg.First().Count() : 0).ToArray();
I am not sure this solution will work, but just give it a try, simply you can do left outer join with static list, below is just snippet (pseudo code) how to do it.
List<String> weekdays = new List<String>();
weekdays.Add("Monday");
weekdays.Add("Tuesday");
...
...
...
//Left outer join to display forcibly all weekdays and join them with your log's data and continue to grouping.
var result = from w in weekdays
join l in logs into joined
from j in joined.DefaultIfEmpty()
If it did not work, please leave a comment as I've another solution for you to make it workable :)
If it did not work, check this, after returning the results, left join your static data (weekdays) with the results (records) which means you've returned only the records that their counts have data (>0) but for only those that don't have data at all, it should be coming as zero after the join.
var result = from w in weekdays
join l in records into joined
from j in joined.DefaultIfEmpty()
You can't join with a List and DbSet because Linq to Entities can't translate a List to SQL language. So, It seems that there is no option except to fill the empty days in a loop;
var records = logs
.GroupBy(x => SqlFunctions.DatePart("dw", x.date))
.Select(x =>
new WeekCounts
{
Day = x.Key,
Count = x.Count()
}).ToList();
for (int i = 0; i < 7; i++)
{
if (records.All(x => x.Day != i))
{
records.Add(new WeekCounts { Day = i, Count = 0 } );
}
}
public class WeekCounts
{
public int? Day;
public int Count;
}
I am working on an application in which I have to store play history of a song in the data table. I have a table named PlayHistory which has four columns.
Id | SoundRecordingId(FK) | UserId(FK) | DateTime
Now i have to implement a query that will return the songs that are in trending phase i.e. being mostly played. I have written the following query in sql server that returns me data somehow closer to what I want.
select COUNT(*) as High,SoundRecordingId
from PlayHistory
where DateTime >= GETDATE()-30
group by SoundRecordingId
Having COUNT(*) > 1
order by SoundRecordingId desc
It returned me following data:
High SoundRecordingId
2 5
2 3
Which means Song with Ids 5 and 3 were played the most number of times i.e.2
How can I implement this through Linq in c#.
I have done this so far:
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId)
.Take(20)
.ToList();
It returns me list of whole table with the count of SoundRecording objects but i want just count of the most repeated records.
Thanks
There is an overload of the .GroupBy method which will solve your problem.
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId, (key,values) => new {SoundRecordingID=key, High=values.count()})
.Take(20)
.ToList();
I have simply added the result selector to the GroupBy method call here which does the same transformation you have written in your SQL.
The method overload in question is documented here
To go further into your problem, you will probably want to do another OrderByDescending to get your results in popularity order. To match the SQL statement you also have to filter for only counts > 1.
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId, (key,values) => new {SoundRecordingID=key, High=values.count()})
.Where(x=>x.High>1)
.OrderByDescending(x=>x.High)
.ToList();
I like the 'linq' syntax it's similar to SQL
var query = from history in _db.PlayHistories
where history.DateTime >= monthBefore
group history by history.SoundRecordingId into historyGroup
where historyGroup.Count() > 1
orderby historyGroup.Key
select new { High = historyGroup.Count(), SoundRecordingId = historyGroup.Key };
var data = query.Take(20).ToList();
You´re allmost done. Just order your list by the count and take the first:
var max =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId)
.OrderByDescending(x => x.Count())
.First();
This gives you a single key-value-pair where the Key is your SoundRecordingId and the value is the number of its occurences in your input-list.
EDIT: To get all records with that amount chose this instead:
var grouped =
_db.PlayHistories
.OrderByDescending(x => x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x => x.SoundRecordingId)
.Select(x => new { Id = x.Key, Count = x.Count() }
.OrderByDescending(x => x.Count)
.ToList();
var maxCount = grouped.First().Count;
var result = grouped.Where(x => x.Count == maxCount);
This solves the problem by giving you what you asked for. Your query in LINQ, returning just the play counts.
var list = _db.PlayHistories.Where(x => x.DateTimeProp > (DateTime.Now).AddMonths(-1))
.OrderByDescending(y => y.SoundRecordingId.Count())
.ThenBy(z => z.SoundRecordingId)
.Select(xx => xx.SoundRecordingId).Take(20).ToList();
Sorry to open another post.
.SelectMany with C#
I asked in the previous post but I can't get the solution for my problem.
`
var departments = stops
.Where(stop => stop.InitDate != null)
.SelectMany(stop => new[] { Month = stop.InitDate.Month, Year = stop.InitDate.Year, Duration = stop.Duration })
.GroupBy(dt => new { dt.Month, dt.Year })
.OrderBy(g => g.Key.Month)
.ThenBy(g => g.Key.Year)
.Select(g => new
{
Key = g.Key.Month,
Año = g.Key.Year,
Duration = g.Sum(v => v.Duration),
Count = g.Count()
});
`
This is de final solution to my problem, but, when I use this in my code, I have some problems.
If I don't declare the variables "Month, Year, Duration", I get an error in:
.SelectMany(stop => new[] { Month = stop.InitDate.Month, Year = stop.InitDate.Year, Duration = stop.Duration })
But I do not know what kind of data they are month and year because if I declare it how integer, I get an error in .GroupBy(dt => new { dt.Month, dt.Year }), because the compiler recognizes dt as integer.
I tried to declare Month and Year as integer and put in the .GroupBy this:
.GroupBy(dt => new { Month, Year }) but it is not correct...
Thank you in advance
Raúl
Apparently you have a sequence named Stops which is a sequence of stop objects. Each stop object may or may have not an InitDate. If it has an InitDate, this InitDate has at least properties Month, Year and Duration, which are all int.
What you want is from your original Stops, only those stop objects that have an InitDate. From every stop object you select, you want to create a new object, with a Key property which contains the Month and the Year and a Duration property which contains the Duration.
You were almost there. Your problem was that you used SelectMany instead of a simple Select.
Normally you use SelectMany if you have a sequence of sequences that you want to concatenate into one sequence. However your Stops does not have a sequence of sequences. Every stop object should produce one "new object with Year, Month and Duration".
Or in simple words: whenever you have a collection of "thingies" and you want to convert every "thingy" into exactly one "other thingy", you should use Select, not SelectMany:
In your case the Select will be:
var stopData = stops
.Where(stop => stop.InitDate != null)
.Select(stop => new
{
GroupKey = new
{
Month = stop.InitDate.Month,
Year = stop.InitDate.Year,
},
Duration = stop.Duration
});
I put the Year and the Month alreay in a property GroupKey, because that makes the grouping easier:
var groupsOfSameMonth = stopData
.GroupBy( item => item.Key, item => item.Duration);
Now every group, contains a key, which is the {Month, Year}. The elements of the group are all Durations with this {month, year}. So now all you have to do is from every group, take all elements from the group and Sum() and Count() them:
var durationsOfSameMonth = groupsOfSameMonth
.Select(group => new
{
Month = group.Key.Month, // I assumed your Key was an error
Año = group.Key.Year,
Duration = group.Sum(),
Count = group.Count()
});
All you have to do is some ordering and you are finished.
I'm trying to group a list of records by hour and store the number of record for each hour. Here is my code :
DateTime firstTimeStamp = myRecords.DataBaseRecords.First().TimeStamp;
Statistics = myRecords.DataBaseRecords
.GroupBy(x => x.TimeStamp.Hour)
.Select(group => new GraphModel() { Date =firstTimeStamp.AddHours(group.Key), Value = group.Count() })
.ToList();
The problem is that when I'm on the select fuction, I cannot acces to the DateTime anymore so the field group.key contains a value between 0 and 24. I just need to group all the records by hour and foreach hour, I need to have the number of records in the Value parameter.
You have to group the data by absolute hours as of the first timestamp, i.e. the differences in hours calculated for each TimeStamp value:
Statistics = myRecords.DataBaseRecords
.GroupBy(x => DbFunctions.DiffHours(firstTimeStamp, x.TimeStamp) into g
.Select(g => new GraphModel
{
Date = g.FirstOrDefault().TimeStamp,
Value = g.Count()
};
If this is plain LINQ to objects (not Entity Framework) you can replace ...
DbFunctions.DiffHours(firstTimeStamp, x.TimeStamp)
... by
(x.TimeStamp - firstTimeStamp).TotalHours
If it's LINQ to SQL, use
SqlMethods.DateDiffHour(firstTimeStamp, x.TimeStamp)
Perhaps something like this may work out for you:
DateTime myDateTime = new DateTime(DateTime.Parse(firstTimeStamp).AddHours(group.Key).Ticks);
Question specific to answer above:
...Date = new DateTime(DateTime.Parse(firstTimeStamp).AddHours(group.Key))...
I have a List which contains dates from June 1, 2009 to June 1, 2014. How would I query it in C# LINQ to select only the first date of each month?
Your question is slightly ambiguous. If you want the first item appearing in the list in each month, you can use:
var result = list.GroupBy(x => new { x.Year, x.Month })
.Select(x => x.Min());
The simplest way I think is to filter by the Day property of your dates:
var firstDays = dates.Where(d=> d.Day == 1);
Like this:
dates.Where(d => d.Day == 1);
Or, using query comprehension,
from d in dates where d.Day == 1 select d;
Try the following
public static bool IsFirstDayOfMonth(this DateTime t) {
var other = new DateTime(t.Year,t.Month,1);
return other == t.Date;
}
var allDates = GetTheDates();
var filter = allDates.Where(x => x.IsFirstDayOfMonth());