I have the following table that records when a particular room in a hotel (designated by a three character code [dlx, sup, jac, etc..]) is sold out on a particular DATETIME.
CREATE TABLE [dbo].[RoomSoldOut](
[SoldOutID] [int] IDENTITY(1,1) NOT NULL,
[RoomType] [nchar](3) NOT NULL,
[SoldOutDate] [datetime] NOT NULL,
CONSTRAINT [PK_RoomSoldOut5] PRIMARY KEY CLUSTERED
I need to find out when a particular date is sold out in the entire hotel. There are 8 room types and if all 8 are sold out then the hotel is booked solid for that night.
the LINQ statement to count the roomtypes sold for a given night works like this.
var solds = from r in RoomSoldOuts
group r by r.SoldOutDate into s
select new
{
Date = s.Key,
RoomTypeSoldOut = s.Count()
};
from this LINQ statement I can get a list of all the sold out DATETIME's with a COUNT of the number of rooms that are sold out.
I need to filter this list to only those DATETIME's where the COUNT = 8, because then the hotel is sold out for that day.
This should be simple but I can not figure out how to do it in LINQ
I think that you need to add the following to the query: where s.Count()==8
You can also try
var solds = (from r in RoomSoldOuts
group r by r.SoldOutDate into s
select new
{
Date = s.Key,
RoomTypeSoldOut = s.Count()
}).Where(x => x.RoomTypeSoldOut == 8);
You could then also have shortened it to only select the dates
var solds = from r in RoomSoldOuts
group r by r.SoldOutDate into s
where s.Count() == 8
select s.Key;
Related
The example given in the blog has the following
from e in s.StudentCourseEnrollments where courseIDs.Contains(e.Course.CourseID) select e
The contains logic will not work when we are looking for an exact match. If a student has enrolled for 6 courses (ex : 1,2,3,4,5,6) and the requested list contains 5 (ex: 1,2,3,4,5) the query will return a match when it should not. The other way works well when the student has enrolled in a subset of the requested list.
Below solution works but need help to convert the below sql to LINQ (EF Core 3.0) ?
Create TABLE dbo.Enrollments (StudentId INT NOT NULL, CourseId INT NOT NULL)
insert into dbo.Enrollments values (1,1)
insert into dbo.Enrollments values (1,2)
insert into dbo.Enrollments values (1,3)
insert into dbo.Enrollments values (1,4)
insert into dbo.Enrollments values (1,5)
insert into dbo.Enrollments values (1,6)
DECLARE #TempCourses TABLE
(
CourseId INT
);
INSERT INTO #TempCourses (CourseId) VALUES (1), (2), (3),(4),(5);
SELECT t.StudentId
FROM
(
SELECT StudentId, cnt=COUNT(*)
FROM dbo.Enrollments
GROUP BY StudentId
) kc
INNER JOIN
(
SELECT cnt=COUNT(*)
FROM #TempCourses
) nc ON nc.cnt = kc.cnt
JOIN dbo.Enrollments t ON t.StudentId = kc.StudentId
JOIN #TempCourses n ON n.CourseId = t.CourseId
GROUP BY t.StudentId
HAVING COUNT(*) = MIN(nc.cnt);
drop table dbo.Enrollments
db<>Fiddle
I don't know about the SQL query, but the EF Core 3.0 LINQ query for the same task is something like this:
var matchIds = new[] { 1, 2, 3, 4, 5 }.AsEnumerable();
var query = dbContext.Students
.Where(s => s.Enrollments.All(e => matchIds.Contains(e.CourseId))
&& s.Enrollments.Count() == matchIds.Count());
The main matching job is done with All subquery. Unfortunately that's not enough for the case when related link records are more than the matching ids, so additional counts comparison solves that.
You can achieve it with a simple way like this, live demo here
Let's say that you've got the list of enrollments by this way
var enrollments = from s in dc.Students
from c in s.Courses
select new { StudentID = s.StudentID, CourseID = c.CourseID };
Then get the result by this way
var groupedEnrollment = enrollments.GroupBy(p => p.StudentId)
.Select(g => new
{
StudentId = g.Key,
Courses = g.Select(p => p.CourseId).ToArray()
});
var result = groupedEnrollment.Where(g =>
g.Courses.Length == courses.Length &&
g.Courses.Intersect(courses).Count() == courses.Length);
Currently I have SQL query like
select tt.userId, count(tt.userId) from (SELECT userId,COUNT(userId) as cou
FROM [dbo].[users]
where createdTime> DATEADD(wk,-1,GETDATE())
group by userId,DATEPART(minute,createdTime)/5) tt group by tt.userId
Now I have the Data in the Data Table, I need to convert the above the query to LINQ and execute against the data table. I am unable to do so , can anybody help me out.
This is what query does, It groups the users into 5 minutes time slots and then counts the number of timeslots per user.
Note : I am not able to use Linqer to create the Linq queries because this table does not exist in the database, it's a virtual one created dynamically.
Bit complex query, giving my best to make it work.
var result = table.AsEnumerable().Where(u=> u.Field<DateTime>("createdTime") > DateTime.Now.AddDays(-7)) //subtract a week
.GroupBy(g=> new { userid = g.Field<string>("userId") , span = g.Field<DateTime>("createdTime").Minute })
.Select(g=> new { userid = g.Key.userid, count = g.Count()})
.GroupBy(g=> g.userid ).Select(s=> new {userid = s.Key, count = s.Count()});
Working Demo
This SQL can be rewritten like this
SELECT
COUNT(U.UserId),
U.[createdTime]
FROM USERS U WHERE createdTime> DATEADD(wk,-1,GETDATE())
GROUP BY U.UserId,
DATEPART(MONTH, U.[createdTime]),
DATEPART(DAY, U.[createdTime]),
DATEPART(HOUR, U.[createdTime]),
(DATEPART(MINUTE, U.[createdTime]) / 5)
And its corresponding Linq for DataTable would be
var users = myDataTable.AsEnumerable()
.Select(r=> new {
UserId = r.Field<int>("UserId"),
CreatedTime = r.Field<DateTime>("createdTime")
}).ToList();
var groupedUsersResult = from user in users where user.CreatedTime > user.CreatedTime.AddDays(-7) group user by
new {user.CreatedTime.Year,user.CreatedTime.Month,user.CreatedTime.Day,Minute=(user.CreatedTime.Minute/5),user.UserId}
into groupedUsers select groupedUsers;
Fiddle is here
I will suggest to use LINQPad4. It would be easy to do that and that will help you a lot in writing LINQ queries.
https://www.linqpad.net/
I have
var result = (from rev in Revisions
join usr in Users on rev.UserID equals usr.ID
join clc in ChangedLinesCounts on rev.Revision equals clc.Revision
select new {rev.Revision,
rev.Date, usr.UserName, usr.ID, clc.LinesCount}).Take(6);
I make a couple of joins on different tables, not relevant for this question what keys are, but at the end of this query my result "table" contains
{Revision, Date, UserName, ID, LinesCount}
Now I execute e GroupBy in order to calculate a total lines count per user.
So..
from row in result group row by row.ID into g {1}
select new {
g.Key,
totalCount = g.Sum(count=>count.LinesCount)
};
So I get a Key=ID, and totalCount=Sum, but
Confusion
I would like to have also other fields in final result.
In my understanding "table" after {1} grouping query consist of
{Revision, Date, UserName, ID, LinesCount, TotalCount}
If my assumption is correct, why I can not do something like this:
from row in result group row by row.ID into g {1}
select new {
g.Key,
g.Revision //Revision doesn't exist ! Why ??
totalCount = g.Sum(count=>count.LinesCount)
};
but
from row in result group row by row.ID into g {1}
select new {
g.Key,
Revision = g.Select(x=>x.Revision), //Works !
totalCount = g.Sum(count=>count.LinesCount)
};
Works !, but imo, sucks, cause I execute another Select.
Infact looking on LinqPad SQL output I get 2 SQL queries.
Question
Is there any elegant and optimal way to do this, or I always need to run Select
on groupped data, in order to be able to access the fields, that exists ?
The problem is, that you only group by ID - if you'd do that in SQL, you couldn't access the other fields either...
To have the other fields as well, you have to include them in you group clause:
from row in result group row by new { row.ID, row.Revision } into g
select new {
g.Key.ID,
g.Key.Revision
totalCount = g.Sum(count=>count.LinesCount)
};
The problem here is your output logically looks something like this:
Key = 1
Id = 1, Revision = 3587, UserName = Bob, LinesCount = 34, TotalCount = 45
Id = 1, Revision = 3588, UserName = Joe, LinesCount = 64, TotalCount = 54
Id = 1, Revision = 3589, UserName = Jim, LinesCount = 37, TotalCount = 26
Key = 2
Id = 2, Revision = 3587, UserName = Bob, LinesCount = 34, TotalCount = 45
Id = 2, Revision = 3588, UserName = Joe, LinesCount = 64, TotalCount = 54
Id = 2, Revision = 3589, UserName = Jim, LinesCount = 37, TotalCount = 26
Much like if you were to perform a an SQL GROUP BY, an value is either part of the key and thus unique per group, or is in the details and thus is repeated multiple times and possibly different for each row.
Now, logically, it might be that Revision and UserName are unique for each Id but Linq has no way to know that (the same as SQL has no way to know that).
To solve this you'll need to some how specify which revision you want. For instance:
Revision = g.FirstOrDefault(x => x.Revision)
To avoid the multiple SQL problem you would need to use an aggregate function that can be translated in to SQL since most SQL dialects do not have a first operator (the result set is considered unordered so technically no item is "first").
Revision = g.Min(x => x.Revision)
Revision = g.Max(x => x.Revision)
Unfortunately Linq does not have a min/max operator for strings, so although the SQL might support this, Linq does not.
In this case you can produce an intermediate result set for the Id and totals, then join this back to the original set to get the details, eg:
from d in items
join t in (
from t in items
group by t.Id into g
select new { Id = g.Key, Total = g.Sum(x => x.LineCount) }
) on d.Id equals t.Id
select new { Id = d.Id, Revision = d.Revision, Total = t.Total }
Revision doesn't exist in your second example because it's not a member of IGrouping<T>, in IGrouping<T> you have a Key property, and it's also an IEnumerable<T> for all the rows grouped together. Thus each of those rows has a Revision, but there is no Revision for the grouping itself.
If the Revision will be the same for all rows with the same ID, you could use FirstOrDefault() so that the select nets at most one answer:
from row in result group row by row.ID into g {1}
select new {
g.Key,
Revision = g.Select(x=>x.Revision).FirstOrDefault(),
totalCount = g.Sum(count=>count.LinesCount)
};
If the Revision is not unique per ID, though, you'd want to use an anonymous type as #Tobias suggests for the grouping, then you will get a grouping based on ID and Revision.
I've found several posts detailing how to perform a weighted average based on a foreign key, but I have yet to find a solution that deals with my situation. Here it is:
I have two tables, table A and a table B many-to-many table linking them; nothing complicated:
TableA
{
A_ID,
Other stuff
}
TableB
{
B_ID,
Date
Other stuff
}
LinkAtoB
{
A_ID,
B_ID
}
Now here comes the math part. I'm more or less trying to weight result from TableA based on the number of recent associations in Table B.
So if TableA has 4 associations in table with the following dates:
{10/23/2010, //3 days ago
10/19/2010, //5 days ago
10/18/2010, //6 days ago
9/13/2010} //40ish days ago
So here is how I'd like to rank them:
I'd like to provide a recency threshold in days, I'll use 7 days as an example:
So using the above data I would assign the following values:
{10/23/2010, //7-3 = 4
10/19/2010, //7-5 = 2
10/18/2010, //7-6 = 1
9/13/2010} //40ish days ago
So the value of the weighted average for that particular TableA entry is then 7 / 3 = 2.33333.
Here is more or less what I have so far:
var k = from a in TableA
group a by a.Select(x=>x.LinkAtoB.TableB)
.Where(x=>x.Date.CompareTo(DateTime.Now.AddDays(-7)) >= 0)
into g
select g.Sum(x => DateTime.Now.Subtract(x.Date).Days) /
g.Sum(x => x.Length);
I think I'm close but I know I have the group part wrong. I think the other stuff should work. How do I fix my code to accomplish what I want?
Here you go! :)
var k = (from b in TableB
join bb in LinkAtoB on b.B_ID equals bb.B_ID into b_join
from ab in b_join.DefaultIfEmpty()
where b.B_DATE.CompareTo(DateTime.Now.AddDays(-7)) > 0
select new {ab.A_ID, DaysAgo = (DateTime.Now - b.B_DATE).Days} into xx
group xx by xx.A_ID into yy
select new {yy.Key, Weighted = yy.Sum(x=> 7 - x.DaysAgo) / yy.Count()} into zz
join a in TableA on zz.Key equals a.A_ID
select new {a.A_ID, a.A_Other_Stuff, zz.Weighted}).ToList();
How can this query be transform to linq
SELECT materialId, SUM(totalAmount) as quantity FROM Inventory
It's the sum part that I don't know how...
query = from inv in context.Inventory
select new MaterialQuantity()
{
MaterialId = inv.materialId,
Quantity = ??
};
EDIT
Trying to sum the value of totalAmount.
It's a view that is
materialId totalSum and other fields
1 5
1 10
1 20
So I want my linq to return me
MaterialId = 1, Quantity = 35
I'm going to give a complete guess here... assuming your inventory has multiple rows with the same materialId and you want to sum in those groups, you could use:
var query = from inv in content.Inventory
group inv.totalAmount by inv.materialId into g
select new { MaterialId = g.Key, Quantity = g.Sum() };
If you're not trying to group though, you'll need to clarify your question. Sample data and expected output would help.