Получение тех записей, которые соответствуют всем записям в другой таблице - PullRequest
1 голос
/ 11 апреля 2011

В рамках этой проблемы у меня есть 3 сущности:

  1. User
  2. Position
  3. License

Тогда у меня есть две реляционные (многие-ко-многим) таблицы:

  1. PositionLicense - этот соединяет Position с License т.е. какие лицензии требуются для конкретной должности
  2. UserLicense - этот соединяет User с License т.е. какие лицензии имеет конкретный пользователь. Но с дополнительной сложностью: пользовательские лицензии имеют срок действия (ValidFrom и ValidTo)

проблема

Это входные переменные:

  • UserID, который идентифицирует конкретный User
  • RangeFrom определяет нижний предел диапазона дат
  • RangeTo определяет верхний предел диапазона дат

Что мне нужно, чтобы получить ? Для конкретного пользователя (и диапазона дат) мне нужно получить список должностей, над которыми этот конкретный пользователь может работать. Проблема в том, что пользователь должен иметь как минимум все лицензии, необходимые для каждой соответствующей позиции.

У меня огромные проблемы при написании SQL-запроса для получения этого списка.

Если это вообще возможно, я хотел бы сделать это с помощью одного SQL-запроса (конечно, может иметь дополнительные CTE). Если вы можете убедить меня в том, что выполнение нескольких запросов было бы более эффективным, я готов выслушать.

Некоторые работоспособные данные

Скопируйте и запустите этот скрипт. 3 пользователя, 3 должности, 6 лицензий. Марк и Джон должны иметь матч, но не Джейн.

create table [User] (
    UserID int identity not null
        primary key,
    Name nvarchar(100) not null
)
go

create table Position (
    PositionID int identity not null
        primary key,
    Name nvarchar(100) not null
)
go

create table License (
    LicenseID int identity not null
        primary key,
    Name nvarchar(100) not null
)
go

create table UserLicense (
    UserID int not null
        references [User](UserID),
    LicenseID int not null
        references License(LicenseID),
    ValidFrom date not null,
    ValidTo date not null,
    check (ValidFrom < ValidTo),
    primary key (UserID, LicenseID)
)
go

create table PositionLicense (
    PositionID int not null
        references Position(PositionID),
    LicenseID int not null
        references License(LicenseID),
    primary key (PositionID, LicenseID)
)
go

insert [User] (Name) values ('Mark the mechanic');
insert [User] (Name) values ('John the pilot');
insert [User] (Name) values ('Jane only has arts PhD but not medical.');

insert Position (Name) values ('Mechanic');
insert Position (Name) values ('Pilot');
insert Position (Name) values ('Doctor');

insert License (Name) values ('Mecha');
insert License (Name) values ('Flying');
insert License (Name) values ('Medicine');
insert License (Name) values ('PhD');
insert License (Name) values ('Phycho');
insert License (Name) values ('Arts');

insert PositionLicense (PositionID, LicenseID) values (1, 1);
insert PositionLicense (PositionID, LicenseID) values (2, 2);
insert PositionLicense (PositionID, LicenseID) values (2, 5);
insert PositionLicense (PositionID, LicenseID) values (3, 3);
insert PositionLicense (PositionID, LicenseID) values (3, 4);

insert UserLicense (UserID, LicenseID, ValidFrom, ValidTo) values (1, 1, '20110101', '20120101');
insert UserLicense (UserID, LicenseID, ValidFrom, ValidTo) values (2, 2, '20110101', '20120101');
insert UserLicense (UserID, LicenseID, ValidFrom, ValidTo) values (2, 5, '20110101', '20120101');
insert UserLicense (UserID, LicenseID, ValidFrom, ValidTo) values (3, 4, '20110101', '20120101');
insert UserLicense (UserID, LicenseID, ValidFrom, ValidTo) values (3, 6, '20110101', '20120101');

Результирующее решение

Я настроил полученное решение на основе принятого ответа, который обеспечивает наиболее упрощенное решение этой проблемы. Если вы хотите поиграть с запросом, просто нажмите edit / clone (вне зависимости от того, вошли вы в систему или нет). Что можно изменить:

  • три переменные:
    1. две переменные для установки диапазона дат (@From и @To)
    2. идентификатор пользователя (@User)
  • вы можете переключать закомментированный код в первом CTE, чтобы переключать код между полностью перекрывающимися пользовательскими лицензиями или частично перекрывающимися.

Ответы [ 3 ]

3 голосов
/ 11 апреля 2011
Select ...
From User As U
    Cross Join Position As P
Where Exists    (
                Select 1
                From PositionLicense As PL1
                    Join UserLicense As UL1
                        On UL1.LicenseId = PL1.LicenseId
                            And UL1.ValidFrom <= @RangeTo
                            And UL1.ValidTo >= @RangeFrom
                Where PL1.PositionId = P.Id
                    And UL1.UserId = U.Id
                Except
                Select 1
                From PositionLicense As PL2
                    Left Join UserLicense As UL2
                        On UL2.LicenseId = PL2.LicenseId
                            And UL2.ValidFrom <= @RangeTo
                            And UL2.ValidTo >= @RangeFrom
                            And UL2.UserId = U.Id
                Where PL2.PositionId = P.Id
                    And UL2.UserId Is Null
                )

Если требуется, чтобы пользователи и позиции действовали во всем диапазоне, это сложнее:

With Calendar As 
    (
    Select @RangeFrom As [Date]
    Union All
    Select DateAdd(d, 1, [Date])
    From Calendar
    Where [Date] <= @RangeTo
    )
Select ...
From User As U
    Cross Join Position As P
Where Exists    (
                Select 1
                From UserLicense As UL1
                    Join PositionLicense As PL1
                        On PL1.LicenseId = UL1.LicenseId
                Where UL1.UserId = U.Id
                    And PL1.PositionId = P.Id
                    And UL1.ValidFrom <= @RangeTo
                    And UL1.ValidTo >= @RangeFrom
                Except
                Select 1
                From Calendar As C1
                    Cross Join User As U1
                    Cross Join PositionLicense As PL1
                Where U1.Id = U.Id
                    And PL1.PositionId = P.Id
                    And Not Exists  (
                                    Select 1
                                    From UserLicense As UL2
                                    Where UL2.LicenseId = PL1.LicenseId
                                        And UL1.UserId = U1.Id
                                        And C1.Date Between UL2.ValidFrom And UL2.ValidTo
                                    )
                )
Option ( MaxRecursion 0 );  
3 голосов
/ 11 апреля 2011

Это делает ряд допущений (игнорирует наличие времени в столбцах datetime, предполагает довольно очевидные первичные ключи) и пропускает объединения, чтобы получить имя пользователя, сведения о позиции и т.п. (И вы подразумевали, что пользователь должен был владеть всеми лицензиями в течение всего указанного периода, верно?)

SELECT pl.PositionId
 from PositionLicense pl
  left outer join (--  All licenses user has for the entirety (sp?) of the specified date range
                   select LicenseId
                    from UserLicense
                    where UserId = @UserId
                     and @RangeFrom <= ValidFrom
                     and @RangeTo >= ValidTo) li
   on li.LicenseId = pl.LicenseId
 group by pl.PositionId 
 --  Where all licenses required by position are held by user
 having count(pl.LicenseId) = count(li.LicenseId)

Нет данных, поэтому я не могу отладить или протестировать их, но это или что-то очень близкое к этому должно помочь.

3 голосов
/ 11 апреля 2011

готовая версия здесь

WITH PositionRequirements AS (
    SELECT p.PositionID, COUNT(*) AS LicenseCt
    FROM #Position AS p
    INNER JOIN #PositionLicense AS posl
        ON posl.PositionID = p.PositionID
    GROUP BY p.PositionID
)
,Satisfied AS (
    SELECT u.UserID, posl.PositionID, COUNT(*) AS LicenseCt
    FROM #User AS u
    INNER JOIN #UserLicense AS perl
        ON perl.UserID = u.UserID
        -- AND @Date BETWEEN perl.ValidFrom AND perl.ValidTo
        AND '20110101' BETWEEN perl.ValidFrom AND perl.ValidTo
    INNER JOIN #PositionLicense AS posl
        ON posl.LicenseID = perl.LicenseID
    -- WHERE u.UserID = @UserID -- Not strictly necessary, we can go over all people
    GROUP BY u.UserID, posl.PositionID
)
SELECT PositionRequirements.PositionID, Satisfied.UserID
FROM PositionRequirements
INNER JOIN Satisfied
    ON Satisfied.PositionID = PositionRequirements.PositionID
    AND PositionRequirements.LicenseCt = Satisfied.LicenseCt

Возможно, вы могли бы превратить это в встроенную табличную функцию, параметризованную на дату вступления в силу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...