SQL query converted to LINQ - sql-server

How do I write this SQL query using LINQ?
I tried a few ideas but can't figure it out :
SELECT id , Email
FROM Customer
WHERE Customer.Id IN
(SELECT CustPeople.PeopleID
FROM CustPeople WHERE MemID = 1)
Table Customer:
Id Email
--------------------------
1 johnDoe#aol.com
12 billGates#yahoo.com
11 charlieParker#aol.com
Table CustPeople:
Id MemID PeopleID
----------------------
1 1 11
2 1 12
3 4 163
Result :
11 charlieParker#aol.com
12 billGates#yahoo.com
Do I use joins? Is there some way of creating a sub query in linq?
var people = from c in _custRepository.Table
join p in _custPeopleRepository.Table on c.Id equals p.MemID
???

Contains call on a collection will be translated into IN clause:
var people = from c in _custRepository.Table
where _custPeopleRepository.Table
.Where(x => x.MemID == 1)
.Select(x => x.PeopleID)
.Contains(c.Id)
select new { c.Id, c.Email };

Related

Gettings grouped sums from related tables into linq query columns

I would like to query a table (Accounts) and also as part of the query get totals (Total) from another table (AccountPositions). How can I rewrite this sql query as a linq query? Is it as easy as adding a group by statement... but the group by usage is confusing me.
select
(select sum(ap.Quantity * ap.LastPrice) from AccountPositions ap
where ap.AccountId = a.Accountid and ap.IsOpen = 0) as Total,
a.AccountId, a.Username, a.FirstName, a.LastName, a.AccountNumber
from Accounts a
where a.IsTrading = 1
Something like???
var query = (from a in _context.Accounts
join ap in _context.AccountPositions on ap.AccountId = a.AccountId
where a.IsTrading == true && and ap.IsOpen == true
select new {
Total = ap.Sum(r => r.Quantity * r.LastPrice),
AccountId = a.AccountId,
Username = a.Username,
FirstName = a.FirstName,
LastName = a.LastName,
AccountNumber = a.AccountNumber
});
Desired Result:
Total AccountId Username FirstName LastName AccountNumber
2500 496 brice Brian Rice U399445
160 508 jdoe John Doe U555322
Tables:
Accounts
AccountId Username FirstName LastName AccountNumber IsTrading
496 brice Brian Rice U399445 1
498 bmarley Bob Marley U443992 0
508 jdoe John Doe U555332 1
AccountPositions
AccountPositionId AccountId Quantity LastPrice IsOpen
23 496 10 200 1
24 496 15 48 0
25 508 8 20 1
26 498 18 35 1
27 496 5 100 1
How can I rewrite this sql query as a linq query? Is it as easy as adding a group by statement...
It's even easier than that, because the SQL query in question uses single aggregate returning correlated subquery in the select clause, so the translation to LINQ is literally one to one - just use the corresponding C# operators and remember that in LINQ select goes last. And aggregate methods like Sum are outside the LINQ query syntax:
var query =
from a in _context.Accounts
where a.IsTrading
select new
{
Total = (from ap in _context.AccountPositions
where ap.AccountId == a.AccountId && ap.IsOpen
select ap.Quantity * ap.LastPrice).Sum(),
a.AccountId,
a.Username,
a.FirstName,
a.LastName,
a.AccountNumber
};
But LINQ allows you to mix query syntax with method syntax, so the Sum part can be written more naturally using the later like this:
Total = _context.AccountPositions
.Where(ap => ap.AccountId == a.AccountId && ap.IsOpen)
.Sum(ap => ap.Quantity * ap.LastPrice),
Finally, if you are using Entity Framework, the relationship between the Account and AccountPosition will be expressed by something like
public ICollection<AccountPosition> AccountPositions { get; set; }
navigation property inside Account class. Which allows to forget about the join (correlation) conditions like ap.AccountId == a.AccountId - they will be applied automatically, and concentrate just on your query logic (see Don’t use Linq’s Join. Navigate!), e.g.
Total = a.AccountPositions.Where(ap => ap.IsOpen).Sum(ap => ap.Quantity * ap.LastPrice),
Inside LINQ query, the EF collection navigation property represents the so called grouped join - an unique LINQ feature (at least doesn't have direct equivalent in SQL). In LINQ query syntax it's expressed as join + into clauses. Let say you need more than one aggregate. Grouped join will help you to achieve that w/o repeating the correlation part.
So with your model, the query using grouped join will start like this:
from a in _context.Accounts
join ap in _context.AccountPositions on a.AccountId equals ap.AccountId into accountPositions
From here you have 2 variables - a representing an Account and accountPositions representing the set of the AccountPosition related to that Account.
But you are interested only in open positions. In order to avoid repeating that condition, you can use another LINQ query syntax construct - the let clause. So the query continues with both filters:
where a.IsTrading
let openPositions = accountPositions.Where(ap => ap.IsOpen)
Now you have all the information to produce the final result, for instance:
select new
{
// Aggregates
TotalPrice = openPositions.Sum(ap => ap.Quantity * ap.LastPrice),
TotalQuantity = openPositions.Sum(ap => ap.Quantity),
OpenPositions = openPositions.Count(),
// ...
// Account info
a.AccountId,
a.Username,
a.FirstName,
a.LastName,
a.AccountNumber
};

SQL Server : get results from one table based on search another result set

I'm trying to create a search page that will allow the user to return a list of employee names based on the skills they have been assigned so the user can select the best qualified technician for new jobs.
We have an mpEmployee table with basic name and contact information:
EmployeeID FirstName LastName
1 Nathan G
3 Jay A
6 Dawn H
17 Dawn S
The various skills have an ID and are assigned to each employee in the mpAssignedSkills table in an individual record. Each employee can have multiple skills assigned.
AssignedSkillID EmployeeID SkillID
11 17 16
18 17 14
12 17 29
13 17 25
14 1 16
15 1 29
16 1 25
The user needs to be able to select a set of skillIDs and it should return a listing of employees that have all those skills currently assigned.
Ive tried a number of queries using INNER JOIN, HAVING, IN and GROUP BY with no luck. The problem seems to be in the results from the mpAssignedSkills table. How do I return a set of distinct employeeIDs for employees that have multiple SKillIDs assigned (i.e 16, 29, 25) ?
Thanks
Depending on how the Ids are passed to the query, you could populate a table variable of the selected ids.
DECLARE #SelectedSkillIds TABLE
(
SkillId INT
)
Then to find employees with matching skills, you can join to the table variable:
SELECT e.EmployeeId
,e.FirstName
,e.LastName
FROM mpEmployee e
WHERE EXISTS (SELECT AssignedSkillId
FROM mpAssignedSkills as
INNER JOIN #SelectedSkillIds ssid ON ssid.SkillId = as.SkillId
WHERE as.EmployeeId = e.EmployeeId)
If there is a finite number of skills, you could try this instead:
SELECT e.EmployeeId
,e.FirstName
,e.LastName
FROM mpEmployee e
WHERE EXISTS (SELECT AssignedSkillId
FROM mpAssignedSkills as
WHERE as.EmployeeId = e.EmployeeId
AND as.SkillId IN (#SkillId1, #SkillId2, #SkillId3 /* etc.. /*
)
EDIT: If you know the number of skills provided, an easy way would be to count the number of assigned skills that matched the search:
DECLARE #SkillId1 INT = 16
DECLARE #SkillId2 INT = 14
DECLARE #SkillId3 INT = 29
DECLARE #TotalSkills INT = 3
SELECT DISTINCT eas.EmployeeId
FROM mpEmployee e
INNER JOIN mpAssignedSkills eas ON eas.EmployeeId = e.EmployeeId
WHERE eas.SkillId IN (#SkillId1, #SkillId2, #SkillId3)
GROUP BY eas.EmployeeId
HAVING COUNT(eas.SkillId) = #TotalSkills
Working example: here

Querying tables with count from other tables

I have three tables in database that are "UserGroup" which has a unique group for user. The second table is "Users" that has users but multiple users can belong to same usergroup.
Similarly I have a third table "Roles" that contains roles and multiple roles
can belong to same "usergroup".
Therefore parent table is UserGroup the id of this table is foreign key in two other tables.
A related question has been already asked here but if you can answer with my scenario , it would be better
Select from one table with count from two other tables
I am trying to query these tables such that I need count of "user Group" from
each of the two tables i.e from "users" and roles". If you can help with the Linq query, it would be appreciated.
For example:
"User Group Table"
Id GroupName
1 Admin
2 IT
3 Helpdesk
"Users Table"
Id USerGroupId UserName
1 1 Tom
2 1 Mickey
3 2 John
4 3 Sandy
"Roles Table"
Id USerGroupId Role
1 2 Supervisor
2 2 Superidetendent
3 3 etc
4 3 etc
Output should show:
GroupName USerCount RolesCount
Admin 2 0
IT 1 2
Helpdesk 1 2
SELECT GroupName
,(SELECT COUNT*) FROM Users U WHERE U.USerGroupId=G.Id) [USerCount]
,(SELECT COUNT*) FROM Roles r WHERE R.USerGroupId=G.Id) [RolesCount]
FROM Group g
I think something like that should work:
UsersTable.GroupBy(u => u.USerGroupId).Select(group => new {
USerGroupId = group.Key,
Count = group.Count()
}).OrderBy(x => x.USerGroupId);
RolesTable.GroupBy(u => u.USerGroupId).Select(group => new {
USerGroupId = group.Key,
Count = group.Count()
}).OrderBy(x => x.USerGroupId);
This is the linq to Entity in c# (if you use Entity Framework to connect to DB)
var query =
from h in (
(from b in db.UserTable
group b.UserGroup by new {
b.UserGroup.GroupName
} into g
select new {
g.Key.GroupName,
UserCount = g.Count(p => p.GroupName != null)
}))
join k in (
(from d in db.Roles
group d.UserGroup by new {
d.UserGroup.GroupName
} into g
select new {
g.Key.GroupName,
RolesCount = g.Count(p => p.GroupName != null)
})) on h.GroupName equals k.GroupName into k_join
from k in k_join.DefaultIfEmpty()
select new {
h.GroupName,
h.UserCount,
RolesCount = k.RolesCount
};
this is linq to SQL in c# (if you use DBML)
var query =
from h in (
(from a in db.UserGroup
join b in db.UserTable on new { Id = a.Id } equals new { Id = b.UserGroupId } into b_join
from b in b_join.DefaultIfEmpty()
group a by new {
a.GroupName
} into g
select new {
g.Key.GroupName,
UserCount = g.Count(p => p.GroupName != null)
}))
join k in (
(from d in db.Roles
group d.UserGroup by new {
d.UserGroup.GroupName
} into g
select new {
g.Key.GroupName,
RolesCount = g.Count(p => p.GroupName != null)
})) on h.GroupName equals k.GroupName into k_join
from k in k_join.DefaultIfEmpty()
select new {
h.GroupName,
h.UserCount,
RolesCount = k.RolesCount
};
this is the source SQL
SELECT h.GroupName, h.UserCount, k.RolesCount
FROM
(SELECT a.GroupName, count(a.GroupName) as UserCount
from UserGroup a
LEFT JOIN UserTable b on a.id = b.UserGroupId
group by a.GroupName) h
LEFT JOIN (
SELECT a.GroupName, count(a.GroupName) as RolesCount
from UserGroup a
INNER JOIN Roles d on a.id = d.UserGroupId
group by a.GroupName) k on h.GroupName = k.GroupName

Sql query to create teams

I need a query to assign teams to a series of users. Data looks like this:
UserId Category Team
1 A null
2 A null
3 B null
4 B null
5 A null
6 B null
8 A null
9 B null
11 B null
Teams should be created by sorting by userid and the first userid becomes the team number and the consecutive A's are part of that team as are the B's that follow. The first A after the Bs starts a new team. There will always be at least one A and one B. So after the update, that data should look like this:
UserId Category Team
1 A 1
2 A 1
3 B 1
4 B 1
5 A 5
6 B 5
8 A 8
9 B 8
11 B 8
EDIT:
Need to add that the user id's will not always increment by 1. I edited the example data to show what I mean. Also, the team ID doesn't strictly have to be the id of the first user, as long as they end up grouped properly. For example, users 1 - 4 could all be on team '1', users 5 and 6 on team '2' and users 8,9 and 11 on team '3'
First you could label each row with an increasing number. Then you can use a left join to find the previous user. If the previous user has category 'B', and the current one category 'A', that means the start of a new team. The team number is then the last UserId that started a new team before the current UserId.
Using SQL Server 2008 syntax:
; with numbered as
(
select row_number() over (order by UserId) rn
, *
from Table1
)
, changes as
(
select cur.UserId
, case
when prev.Category = 'B' and cur.Category = 'A' then cur.UserId
when prev.Category is null then cur.UserId
end as Team
from numbered cur
left join
numbered prev
on cur.rn = prev.rn + 1
)
update t1
set Team = team.Team
from Table1 t1
outer apply
(
select top 1 c.Team
from changes c
where c.UserId <= t1.UserId
and c.Team is not null
order by
c.UserId desc
) as team;
Example at SQL Fiddle.
You can do this with a recursive CTE:
with userCTE as
(
select UserId
, Category
, Team = UserId
from users where UserId = 1
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
from userCTE
inner join users on users.UserId = userCTE.UserId + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
You can update the CTE to get this to go:
with userOrder as
(
select *
, userRank = row_number() over (order by userId)
from users
)
, userCTE as
(
select UserId
, Category
, Team = UserId
, userRank
from userOrder where UserId = (select min(UserId) from users)
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
, users.userRank
from userCTE
inner join userOrder users on users.userRank = userCTE.userRank + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
For larger datasets you'll need to add the maxrecursion query hint; I've edited the previous queries to show this. From Books Online:
Specifies the maximum number of recursions allowed for this query.
number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied.
In this case I've set it to 0, i.e. not limit on recursion.
Query Hints.
I actually ended up going with the following. It finished on all 3 million+ rows in a half an hour.
declare #userid int
declare #team int
declare #category char(1)
declare #lastcategory char(1)
set #userid = 1
set #lastcategory='B'
set #team=0
while #userid is not null
begin
select #category = category from users where userid = #userid
if #category = 'A' and #lastcategory = 'B'
begin
set #team = #userid
end
update users set team = #team where userid = #userid
set #lastcategory = #category
select #userid = MIN(userid) from users where userid > #userid
End

Grouping by ShopName Count Different type of Customer(membership)

We have a 2 tables
Table 1
Shop where we have shop details
ShopNo, ShopName
Table 2
Customer where we have all customer details
CustomerId, CustomerName, MembershipType, ShopId
what we are trying to query is
ShopName SilverMember GoldMember LifeMember
ABC 45 110 1
XYZ 90 0 10
DEF 10 10 7
Is there a way to make it possible
if in Linq well and good else only sql query will do thanks in advance
Regards
Moksha
Something like this should do the trick:
select s.ShopName,
count(c1.CustomerId) as 'SilverMember',
count(c2.CustomerId) as 'GoldMember',
count(c3.CustomerId) as 'LifeMember'
from Shop s
left outer join Customer c1 on c1.ShopId = s.ShopNo and c1.MembershipType = 'SilverMember'
left outer join Customer c2 on c2.ShopId = s.ShopNo and c2.MembershipType = 'GoldMember'
left outer join Customer c3 on c3.ShopId = s.ShopNo and c3.MembershipType = 'LifeMember'
group by s.ShopName
Assuming MembershipType contains the actual VARCHAR values SilverMember, GoldMember, and LifeMember, this should work in T-SQL:
SELECT
[ShopName], COUNT([SilverMember]) AS [SilverMember], COUNT([GoldMember]) AS [GoldMember], COUNT([LifeMember]) AS [LifeMember]
FROM
[Table1]
INNER JOIN [Table2]
ON [Table1].[ShopNo] = [Table2].[ShopId]
PIVOT
(
MAX([MembershipType])
FOR [MembershipType] IN ([SilverMember], [GoldMember], [LifeMember])
) AS [P]
GROUP BY
[ShopName]
Example on SQL Fiddle
If you want to use linq:
var query = from s in context.Shops
join c in context.Customers on s.ShopNo equals c.ShopId
group c by s.ShopName into g
select new
{
ShopName = g.Key,
SilverMember = g.Count(c => c.MembershipType == "SilverMember"),
GoldMember= g.Count(c => c.MembershipType == "GoldMember"),
LifeMember= g.Count(c => c.MembershipType == "LifeMember"),
};

Resources