I want to retrieve users form User table where
- Users are not deleted
- Users who have user id in provided list of user ids
- Users who have team id in provided list of team ids
- Users who have role id and team id in provided tuple list of role and team ids
For that, I'm writing stored procedure with three table-valued parameters like below:
create type RoleTeamTableType as table
(
RoleId bigint,
TeamId bigint
);
go
create type UserTableType as table
(
UserId bigint
);
go
create type TeamTableType as table
(
TeamId bigint
);
go
create proc spCurrentUsersInRolesAndTeams
@roleTeamTableType RoleTeamTableType readonly,
@userTableType UserTableType readonly,
@teamTableType TeamTableType readonly
as
set nocount on
select * from Users as U where U.Deleted<>1 and
(
exists
(select * from @roleTeamTableType as V
where (
(V.TeamId <> 0 and V.TeamId = U.TeamId and V.RoleId = U.RoleId) or
(V.TeamId = 0 and V.RoleId = U.RoleId)
)) /*if team id is 0 then only check for role id match else match both*/
or exists
(select * from @userTableType as W
where ( W.UserId = U.Id))
or exists
(select * from @teamTableType as X
where ( X.TeamId = U.TeamId))
)
return
go
/*Usage*/
declare @roleTeamTableType as RoleTeamTableType;
insert into @roleTeamTableType (RoleId, TeamId)
values (1,0), (1,1), (2,1)
declare @userTableType as UserTableType
insert into @userTableType (UserId) values (1),(2),(3)
declare @teamTableType as TeamTableType
insert into @teamTableType (TeamId) values (1),(2),(3)
exec spCurrentUsersInRolesAndTeams @roleTeamTableType, @userTableType, @teamTableType
While the above code works, I have following questions:
- Considering, I'm not an expert in writing efficient SQL queries, is this the right way of achieving what I want, or is there any better way?
- I've written LINQ to Entities version of this query using EF but I want to optimize that by calling this stored procedure instead. So, how do I create table valued parameters in .Net code?
- Consider the scenario where the provided table valued parameter lists contain thousands of values lets say 100000, it will surely degrade the performance. What is the preferred and efficient way to get table rows by providing list of ids?
Update
Comments below suggest to add problematic query, so here it is
public IEnumerable<long> GetCurrentUserIds(IEnumerable<long> userIds, IEnumerable<Tuple<long, long>> roleTeamIds, IEnumerable<long> teamIds)
{
//setting timeout to avoid timeout exceptions
Context.Database.CommandTimeout = 180;
var users = Context.Users.AsNoTracking().Where(user => !user.Deleted);
//following loop is only for testing with userIds1
//working at i = 30,000
var userIds1 = new List<long>();
for (long i = 1; i < 30000; i++)
userIds1.Add(i);
//at i = 35,000
//Internal error: An expression services limit has been reached. Please look for potentially complex expressions in your query, and try to simplify them.
//at i = 40,000
//"The query processor ran out of internal resources and could not produce a query plan.
//This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions.
//Please simplify the query. If you believe you have received this message in error, contact Customer Support Services for more information."
var result = users.Where(user => (userIds1.Contains(user.Id) ||
(user.TeamId.HasValue && teamIds.Contains(user.TeamId.Value)))).Select(u=>u.Id);
foreach (var roleTeamId in roleTeamIds)
{
var filtered = users.Where(user => (roleTeamId.Item2 != 0 && roleTeamId.Item2 == user.TeamId && roleTeamId.Item1 == user.RoleId) ||
(roleTeamId.Item2 == 0 && roleTeamId.Item1 == user.RoleId)).Select(u => u.Id);
result = result.Concat(filtered);
}
return result.Distinct().ToList();
}
SQL Profiler query resulted with parameter values of
userIds: 6,7
roleTeamIds: (4,1), (6,1)
teamIds: 0
exec sp_executesql N'SELECT
[Distinct1].[C1] AS [C1]
FROM ( SELECT DISTINCT
[UnionAll2].[Id] AS [C1]
FROM (SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Users] AS [Extent1]
WHERE ([Extent1].[Deleted] <> 1) AND ([Extent1].[Id] IN (cast(6 as bigint), cast(11 as bigint)))
UNION ALL
SELECT
[Extent2].[Id] AS [Id]
FROM [dbo].[Users] AS [Extent2]
WHERE ([Extent2].[Deleted] <> 1) AND (((0 <> @p__linq__0) AND (@p__linq__1 = [Extent2].[TeamId]) AND (@p__linq__2 = [Extent2].[RoleId])) OR ((0 = @p__linq__3) AND (@p__linq__4 = [Extent2].[RoleId])))
UNION ALL
SELECT
[Extent3].[Id] AS [Id]
FROM [dbo].[Users] AS [Extent3]
WHERE ([Extent3].[Deleted] <> 1) AND (((0 <> @p__linq__5) AND (@p__linq__6 = [Extent3].[TeamId]) AND (@p__linq__7 = [Extent3].[RoleId])) OR ((0 = @p__linq__8) AND (@p__linq__9 = [Extent3].[RoleId])))) AS [UnionAll2]
) AS [Distinct1]',N'@p__linq__0 bigint,@p__linq__1 bigint,@p__linq__2 bigint,@p__linq__3 bigint,@p__linq__4 bigint,@p__linq__5 bigint,@p__linq__6 bigint,@p__linq__7 bigint,@p__linq__8 bigint,@p__linq__9 bigint',@p__linq__0=1,@p__linq__1=1,@p__linq__2=4,@p__linq__3=1,@p__linq__4=4,@p__linq__5=1,@p__linq__6=1,@p__linq__7=6,@p__linq__8=1,@p__linq__9=6