Создать объединение, которое может (включать некоторые) или (включать все, кроме некоторых) записей - PullRequest
1 голос
/ 09 июля 2009

Рассмотрим следующие две таблицы:

User
 - UserID
 - UserName

Group
 - GroupID
 - GroupName

Очевидная связь заключается в том, что пользователи будут в группах. Само по себе это простая ситуация соединения «многие ко многим», поэтому давайте добавим третью таблицу:

UserGroup
 - UserID
 - GroupID

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

Однако я хочу также иметь возможность создавать группы, которые включают всех пользователей по умолчанию, если только они не были специально исключены. Логически, я разбил это на два "режима", для которых группа должна быть в одном или другом:

  • Режим включения : исключается каждый пользователь, если не указано иное.
  • Режим исключения : каждый пользователь включен, если не исключен специально.

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

Ответы [ 5 ]

1 голос
/ 11 июля 2009

Вы пытаетесь создать таблицу всех пользователей и групп?

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

Да, и возникает вопрос, нужно ли все это делать одним запросом, или вы можете смешать какой-то код приложения.

Чтобы найти всех пользователей в группе, моей первой мыслью было бы, чтобы приложение сначала прочитало запись группы и проверило режим объединения. Затем, если режим соединения «in», выполните обычный запрос:

select userName
from userGroup ug
join user u using (userId)
where ug.groupId=?

Если режим соединения «ex», запустите:

select userName
from user u
where not exists (select * from userGroup ug where ug.groupId=? and ug.userId=u.userId)

Если по какой-то причине это нужно сделать одним запросом, возможно:

select userName
from group g
left join user u on joinMode='in'
  and exists (select * from userGroup ug where ug.groupid=g.groupid and ug.userid=u.userid)
or joinMode='ex'
  and not exists (select * from userGroup ug where ug.groupid=g.groupid and ug.userid=u.userid)
where g.groupid=?

Это работает, но, честно говоря, я не уверен, на что будет похожа производительность. Я разработал план объяснения в MySQL, и он решил прочитать всю таблицу User, но тогда у меня было только три записи, может быть, с большим количеством записей он мог бы составить другой план.

1 голос
/ 09 июля 2009

Добавить поле в таблицу групп, «Режим» - 0 = Включить, 1 = Исключить

Затем, пользователи в связующей таблице для «Эксклюзивной» группы будут помещены в список «NOT IN ()» или «EXCEPT» при выполнении ваших запросов; тогда как «включающие» группы будут запрашиваться в обычном режиме.

См. http://msdn.microsoft.com/en-us/library/ms188055.aspx для ИСКЛЮЧЕНИЯ синтаксиса / использования.

РЕДАКТИРОВАТЬ: о, кто-то опубликовал до меня ... надеюсь, это все еще полезно!

Запрос SELECT для «Эксклюзивной» группы будет выглядеть примерно так:

SELECT UserID
FROM Users
EXCEPT
SELECT UserID
FROM UserGroup
WHERE GroupID = X

Чтобы сделать его более динамичным, просто определите групповой режим перед построением запроса, затем замените «EXCEPT» на «INTERSECT», если групповой режим «Inclusive».

1 голос
/ 09 июля 2009

Это заставило меня задуматься над тем, как легко написать окончательный запрос, чтобы получить список групп, в которых находится конкретный пользователь, но здесь он есть. В любом случае, установка атрибута в таблице групп - лучший способ определить, является ли группа группой «Включить» или «Исключить». Это включает в себя все операторы создания и вставки, использованные для его тестирования.

CREATE TABLE MUser (UserID INT, UserName VARCHAR(64))
GO
CREATE TABLE MGroup (GroupID INT, GroupName VARCHAR(64), GroupTypeID INT)
GO
CREATE TABLE MGroupType (GroupTypeID INT, GroupTypeName VARCHAR(64))
GO
CREATE TABLE MUserGroup (UserID INT, GroupID INT)
GO

INSERT INTO MGroupType VALUES(1, 'Include')
INSERT INTO MGroupType VALUES(2, 'Exclude')

INSERT INTO MGroup VALUES(1, 'Group 1a', 1)
INSERT INTO MGroup VALUES(2, 'Group 1b', 1)
INSERT INTO MGroup VALUES(3, 'Group 2a', 2)
INSERT INTO MGroup VALUES(4, 'Group 2b', 2)

INSERT INTO MUser VALUES (1, 'User1')
--included in 1a, 1b; excluded from 2a, 2b
INSERT INTO MUserGroup VALUES(1, 1)
INSERT INTO MUserGroup VALUES(1, 2)
INSERT INTO MUserGroup VALUES(1, 3)
INSERT INTO MUserGroup VALUES(1, 4)

INSERT INTO MUser VALUES (2, 'User2')
--included in 1a; implicitly included in 2b
INSERT INTO MUserGroup VALUES(2, 1)
INSERT INTO MUserGroup VALUES(2, 3)

INSERT INTO MUser VALUES (3, 'User3')
--implicitly included in 2b
INSERT INTO MUserGroup VALUES(3, 3)

--SELECT ALL GROUPS FOR A PARTICULAR USER
DECLARE @UserID INT
SET @UserID = 3

SELECT g.GroupName
FROM MGroup g WITH(NOLOCK)
LEFT JOIN (
            SELECT *
            FROM MUserGroup WITH(NOLOCK)
            WHERE UserID = @UserID
        ) ug ON g.GroupID = ug.GroupID
WHERE (g.GroupTypeID = 1 AND ug.UserID IS NOT NULL)
OR (g.GroupTypeID = 2 AND ug.UserID IS NULL)

--SELECT ALL USERS FOR A PARTICULAR GRUP
DECLARE @GroupID INT
SET @GroupID = 4

SELECT u.UserName
FROM MUser u WITH(NOLOCK)
JOIN (SELECT * FROM MGroup WITH(NOLOCK) WHERE GroupID = @GroupID AND GroupTypeID = 2) g ON NOT EXISTS (SELECT * FROM MUserGroup ug WITH(NOLOCK) WHERE u.UserID = ug.UserID AND g.GroupID = ug.GroupID)
UNION
SELECT u.UserName
FROM MUser u WITH(NOLOCK)
JOIN MUserGroup ug WITH(NOLOCK) ON u.UserID = ug.UserID
JOIN MGroup g WITH(NOLOCK) ON ug.GroupID = g.GroupID
WHERE g.GroupID = @GroupID
    AND g.GroupTypeID = 1
0 голосов
/ 11 июля 2009

Это решение, которое я использовал в итоге. Я добавил JoinMode в таблицу Group, которая имеет допустимые значения «IN» и «EX» для включающих и исключающих соответственно. Казалось, все согласны с тем, что это способ сделать это. Я думаю, что это делает содержимое таблицы объединения довольно запутанным, но, надеюсь, это наименее плохое решение.

В запросе я выполняю CROSS JOIN, чтобы создать все возможные отношения, а затем отбрасываю ненужные в ограничениях WHERE в зависимости от того, существует значение таблицы объединения или нет.

DECLARE @User TABLE
    (UserID   int
    ,UserName varchar(16))
INSERT INTO @User VALUES (1, 'John')
INSERT INTO @User VALUES (2, 'Jim')
INSERT INTO @User VALUES (3, 'Bob')
INSERT INTO @User VALUES (4, 'George')

DECLARE @Group TABLE
    (GroupID   int
    ,GroupName varchar(16)
    ,JoinMode  char(2))
INSERT INTO @Group VALUES (1, 'Nobody',    'IN')
INSERT INTO @Group VALUES (2, 'Only John', 'IN')
INSERT INTO @Group VALUES (3, 'Jim & Bob', 'IN')
INSERT INTO @Group VALUES (4, 'Not John',  'EX')
INSERT INTO @Group VALUES (5, 'Everybody', 'EX')

DECLARE @UserGroup TABLE
    (UserID   int
    ,GroupID   int)
INSERT INTO @UserGroup VALUES (1, 2) -- Only John
INSERT INTO @UserGroup VALUES (2, 3) -- Jim & Bob
INSERT INTO @UserGroup VALUES (3, 3) -- Jim & Bob
INSERT INTO @UserGroup VALUES (1, 4) -- Not John

SELECT
    g.GroupName
    ,u.UserName
FROM
    (
        @User u
        CROSS JOIN
        @Group g
    )
    LEFT OUTER JOIN
    @UserGroup ug ON (
        u.UserID = ug.UserID
        AND g.GroupID = ug.GroupID
    )
WHERE
    (
        g.JoinMode = 'IN'
        AND ug.UserID IS NOT NULL
    )
    OR (
        g.JoinMode = 'EX'
        AND ug.UserID IS NULL
    )
ORDER BY
    g.GroupID
    ,u.UserID

Дайте мне знать, если вы видите какие-либо проблемы с этим подходом или если у вас есть лучший способ.

0 голосов
/ 09 июля 2009

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

...