创建可以(包括一些)或(包括除一些除外)记录的联接

时间:2009-07-08 23:27:36

标签: sql-server database database-design

考虑以下两个表:

User
 - UserID
 - UserName

Group
 - GroupID
 - GroupName

明显的关联是用户将在群组中。这本身就是一个简单的多对多连接情况,所以让我们添加第三个表:

UserGroup
 - UserID
 - GroupID

在此教科书架构下,我可以通过将新记录插入UserGroup表,轻松地在特定组中包含特定用户。在进一步讨论之前,我想指出我认为这是数据库设计的最佳情况。

但是,我希望能够默认创建包含所有用户的组,除非它们以某种方式被特别排除。从逻辑上讲,我已将其分解为两个“模式”,其中一个组必须在一个或另一个中:

  • 包含模式:除非明确包含,否则将排除每个用户。
  • 排除模式:除非明确排除,否则将包含每个用户。

那么设计这种关系的最佳方式是什么?您可以添加列,添加表,并具有花哨的连接条件和WHERE约束。请不要触发器或s'procs。

5 个答案:

答案 0 :(得分:1)

将字段添加到组表,'模式' - 0 =包含,1 =排除

然后,在进行查询时,“独占”组链接表中的用户将被置于“NOT IN()”列表或“EXCEPT”子句中;而“包容性”群体将被正常查询。

有关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”。

答案 1 :(得分:1)

这个让我思考一下如何轻松编写最终查询以获取特定用户所在的组列表,但现在就是这样。无论哪种方式,在组表中设置属性是确定该组是“包含”还是“排除”组的最佳方法。这包括用于测试它的所有create和insert语句。

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

答案 2 :(得分:1)

您是否尝试制作包含所有用户和群组的表格?

如果没有,如果您只想知道用户所在的群组或群组中的哪些用户,则上述查询到达目的地的速度非常慢。

哦,问题是这一切是否必须通过一个查询完成,或者是否可以混合使用某些应用程序代码。

要查找组中的所有用户,我首先想到的是让应用程序首先读取组记录并检查连接模式。然后,如果连接模式为“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表,但之后我只有三条记录,可能有更多的记录,它会做出不同的计划。

答案 3 :(得分:0)

由于all-inclusiveness和all-exclusiveness是group的一个属性,最明显的方法似乎是在Group表中添加一个列,指定它是否应包括所有用户,排除所有用户或具有默认行为(两者都不是)。

答案 4 :(得分:0)

这是我最终使用的解决方案。我将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

如果您发现此方法有任何问题或者您有更好的方法,请告诉我。