【问题标题】:Create a join that can (include some) or (include all except some) records创建一个可以(包括一些)或(包括除一些之外的所有)记录的连接
【发布时间】:2009-07-08 23:27:35
【问题描述】:

考虑以下两个表:

User
 - UserID
 - UserName

Group
 - GroupID
 - GroupName

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

UserGroup
 - UserID
 - GroupID

在这个教科书模式下,我可以通过在 UserGroup 表中插入新记录来轻松地将特定用户包含在特定组中。在继续之前,我想指出我认为这是数据库设计中的最佳情况。

但是,我还希望能够创建默认包含所有用户的组,除非以某种方式明确排除它们。从逻辑上讲,我将其分为两种“模式”,组必须处于其中一种:

  • 包含模式:除非特别包含,否则每个用户都被排除在外。
  • 排除模式:每个用户都包括在内,除非特别排除。

那么设计这种关系的最佳方式是什么?您可以添加列、添加表,并拥有精美的连接条件和 WHERE 约束。请不要触发或触发。

【问题讨论】:

    标签: sql-server database database-design


    【解决方案1】:

    将字段添加到组表,“模式” - 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
    

    为了使其更具动态性,只需在构建查询之前确定 Group-mode,如果 Group-mode 为“Inclusive”,则将“EXCEPT”替换为“INTERSECT”。

    【讨论】:

      【解决方案2】:

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

      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
      

      【讨论】:

        【解决方案3】:

        您是否尝试生成包含所有用户和组的表格?

        如果不是,如果您只是想知道用户在哪些组或组中的哪些用户,上述方法将是一种非常缓慢的查询方法。

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

        要查找组中的所有用户,我的第一个想法是让应用程序首先读取组记录并检查加入模式。那么如果join模式是“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 表,但后来我只有三个记录,也许有更多记录会制定不同的计划。

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            【解决方案5】:

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

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

            【讨论】:

              猜你喜欢
              • 2016-10-16
              • 1970-01-01
              • 2018-03-19
              • 1970-01-01
              • 2014-06-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多