Полностью совпадающие наборы записей двух таблиц «многие ко многим» - PullRequest
4 голосов
/ 17 июля 2011

У меня Пользователи , Позиции и Лицензии .

Отношения:

  • пользователи могут иметь много лицензий
  • должности могут потребовать много лицензий

Таким образом, я могу легко получить требования к лицензии для каждой позиции (ий), а также действующие лицензии для пользователя (ей).

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

Я хотел бы получить результаты с пользователями и подходящими позициями.

PersonID PositionID
1        1          -> user 1 is eligible to work on position 1
1        2          -> user 1 is eligible to work on position 2
2        1          -> user 2 is eligible to work on position 1
3        2          -> user 3 is eligible to work on position 2
4        ...

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


Здесь на самом деле 5 таблиц:

create table Person ( PersonID, ...)
create table Position (PositionID, ...)
create table License (LicenseID, ...)

и отношения

create table PersonLicense (PersonID, LicenseID, ...)
create table PositionLicense (PositionID, LicenseID, ...)

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

Как сопоставить несколько записей одной реляционной таблицы с несколькими записями другой. Это также можно описать как inner join для набора записей, а не для одной записи, как это обычно делается в TSQL.

Я думаю о языковых конструкциях TSQL:

  • наборы строк, но я никогда не использовал их раньше и все равно не знаю, как их использовать
  • intersect операторов, может быть, хотя они, вероятно, работают только над целыми наборами, а не группами

Ответы [ 3 ]

4 голосов
/ 18 июля 2011

Окончательное решение (для дальнейшего использования)

Между тем, пока вы, коллеги-разработчики, ответили на мой вопрос, я пришел к этому вопросу и использую CTE и разбиение на разделы, которые, конечно же, можно использовать в SQL Server 2008 R2.,Я никогда раньше не использовал разбиение результатов, поэтому мне пришлось изучать что-то новое (что является плюсом в целом).Вот код:

with CTEPositionLicense as (
    select
        PositionID,
        LicenseID,
        checksum_agg(LicenseID) over (partition by PositionID) as RequiredHash
    from PositionLicense
)
select per.PersonID, pos.PositionID
from CTEPositionLicense pos
    join PersonLicense per
    on (per.LicenseID = pos.LicenseID)
group by pos.PositionID, pos.RequiredHash, per.PersonID
having pos.RequiredHash = checksum_agg(per.LicenseID)
order by per.PersonID, pos.PositionID;

Итак, я провел сравнение между этими тремя техниками, которые я назвал:

  1. Перекрестное соединение (от Андрея М)
  2. Переменная таблицы (Петр Иванов)
  3. Контрольная сумма - здесь (Роберт Коритник, я)

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

Результирующий примерный план выполнения

  1. Контрольная сумма: 7%
  2. Переменная таблицы: 2% (создание таблицы) + 9% (выполнение) = 11%
  3. Перекрестное соединение: 82%

Я также изменил ТаблицаВерсия переменной в версии CTE (вместо табличной переменной использовалась CTE), в конце удалила order by и сравнила свои предполагаемые планы выполнения.Просто для справки CTE версия 43%, тогда как в оригинальной версии было 53% (10% + 43%).

2 голосов
/ 18 июля 2011

Один из способов эффективно написать это - объединить PositionLicences с PersonLicences в licenceId.Затем подсчитайте ненулевые значения, сгруппированные по должности и лицу, и сравните с количеством всех лицензий на должность - если оно равно значению, указанному для этого лица:

DECLARE @tmp TABLE(PositionId INT, LicenseCount INT)

INSERT INTO @tmp
SELECT  PositionId as PositionId
        COUNT(1) as LicenseCount
FROM PositionLicense
GROUP BY PositionId

SELECT  per.PersonID, pos.PositionId
FROM    PositionLicense as pos
INNER JOIN PersonLicense as per ON (pos.LicenseId = per.LicenseId)
GROUP BY t.PositionID, t.PersonId
HAVING COUNT(1) = (
    SELECT LicenceCount FROM @tmp WHERE PositionId = t.PositionID
)
1 голос
/ 18 июля 2011

Я бы подошел к проблеме так:

  1. Получить всех (отдельных) пользователей из PersonLicense.

  2. Соедините их крестом с PositionLicense.

  3. Соедините слева в результирующий набор с помощью PersonLicense, используя PersonID и LicenseID.

  4. Сгруппируйте результаты по PersonID и PositionID.

  5. Отфильтруйте те пары (PersonID, PositionID), в которых количество лицензий в PositionLicense не совпадает с количеством лицензий в PersonLicense.

А вот и моя реализация:

SELECT
  u.PersonID,
  pl.PositionID
FROM (SELECT DISTINCT PersonID FROM PersonLicense) u
  CROSS JOIN PositionLicense pl
  LEFT JOIN PersonLicense ul ON u.PersonID = ul.PersonID
                            AND pl.LicenseID = ul.LicenseID
GROUP BY
  u.PersonID,
  pl.PositionID
HAVING COUNT(pl.LicenseID) = COUNT(ul.LicenseID)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...