Хранимая процедура с табличными параметрами, эффективный путь? - PullRequest
0 голосов
/ 11 мая 2018

Я хочу получить пользователей из таблицы пользователей, где

  1. Пользователи не удаляются
  2. Пользователи, которые имеют идентификатор пользователя в предоставленном списке идентификаторов пользователей
  3. Пользователи, у которых есть идентификатор команды в предоставленном списке идентификаторов команды
  4. Пользователи, у которых есть идентификатор роли и идентификатор команды в предоставленном списке кортежей роли и команды. Идентификаторы

Для этого я пишу хранимую процедуру с тремя табличными параметрами, как показано ниже:

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

Хотя приведенный выше код работает, у меня есть следующие вопросы:

  1. Учитывая, что я не являюсь экспертом в написании эффективных SQL-запросов, это правильный способ достижения того, чего я хочу, или есть какой-то лучший способ?
  2. Я написал версию этого запроса LINQ to Entities, используя EF, но я хочу оптимизировать ее, вызвав вместо этого эту хранимую процедуру. Итак, как мне создать табличные параметры в коде .Net?
  3. Рассмотрим сценарий, в котором предоставленные списки параметров с табличными значениями содержат тысячи значений, скажем, 100000, это, безусловно, приведет к снижению производительности. Каков предпочтительный и эффективный способ получения строк таблицы путем предоставления списка идентификаторов?

Обновление

Комментарии ниже предлагают добавить проблемный запрос, поэтому вот он

 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, полученный со значениями параметра

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
...