桥接表的 SQL 查询帮助

Posted

技术标签:

【中文标题】桥接表的 SQL 查询帮助【英文标题】:SQL query help with bridge table 【发布时间】:2008-11-18 17:13:03 【问题描述】:

我正在使用现有数据库并尝试编写一个 sql 查询来获取所有帐户信息,包括权限级别。这是为了安全审计。我们希望以一种可读的方式转储所有这些信息,以便于比较。我的问题是权限有一个桥/链接表,因此每个用户有多个记录。我想在一行上获得一个用户的所有权限的结果。这是一个例子:

Table_User:
UserId   UserName
1        John
2        Joe
3        James

Table_UserPermissions:
UserId   PermissionId   Rights
1        10             1
1        11             2
1        12             3
2        11             2
2        12             3
3        10             2

PermissionID 链接到带有权限名称及其作用的表。 Right 就像 1 = 查看,2 = 修改等等。

我从用户 1 的基本查询中得到的是:

UserId UserName PermissionId Rights
1      John     10           1
1      John     11           2
1      John     12           3

我想要这样的东西:

UserId UserName Permission1 Rights1 Permission2 Right2 Permission3 Right3
1      John     10          1       11          2      12          3

理想情况下,我希望所有用户都能使用此功能。 我发现最接近的是 SQL Server 2005 中的 Pivot 函数。 http://geekswithblogs.net/lorint/archive/2006/08/04/87166.aspx 据我所知,这个问题是我需要为每个用户命名每一列,我不确定如何获得权限级别。使用真实数据,我有大约 130 个用户和 40 种不同的权限。

是否有另一种仅使用 sql 的方法可以做到这一点?

【问题讨论】:

知道我在这个网站上几乎完全看到过这个问题至少两次。但我找不到它。 【参考方案1】:

你可以这样做:

select userid, username
,      max(case when permissionid=10 then rights end) as permission10_rights
,      max(case when permissionid=11 then rights end) as permission11_rights
,      max(case when permissionid=12 then rights end) as permission12_rights
from   userpermissions
group by userid, username;

您必须为每个权限 ID 显式添加类似的 max(...) 列。

【讨论】:

另见 sql server 2005 及更高版本的 PIVOT 关键字【参考方案2】:

如果你使用 mysql,我建议你使用group_concat(),如下所示。

select UserId, UserName, 
       group_concat(PermissionId) as PermIdList,
       group_concat(Rights SEPARATOR ',') as RightsList
from Table_user join Table_UserPermissions on 
     Table_User.UserId = Table_UserPermissions.UserId=
GROUP BY Table_User.UserId

这会返回

UserId UserName PermIdList  RightsList
1      John     10,11,12    1,2,3

在 google 上快速搜索“mssql group_concat”,发现有几个不同的 MSSQL 存储过程 (I)、(II) 可以实现相同的行为。

【讨论】:

【参考方案3】:

简答:

没有。

您不能在查询中动态添加列。

请记住,SQL 是一种基于集合的语言。您查询集合并将集合连接在一起。

您要挖掘的是一个递归列表,并要求列表水平而不是垂直地串在一起。

您可以使用一组自连接来伪造它,但为了做到这一点,您必须在编写查询之前了解所有可能的权限......这是其他建议所提出的。

您还可以将记录集拉回另一种语言,然后对其进行迭代以生成正确的列。

类似:

SELECT Table_User.userID, userName, permissionid, rights
FROM Table_User
        LEFT JOIN Table_UserPermissions ON Table_User.userID =Table_UserPermissions.userID
ORDER BY userName

然后使用类似(Python)的方式显示每个用户的所有权限:

userID = recordset[0][0]
userName = recordset[0][1]
for row in recordset:
   if userID != row[0]:
       printUserPermissions(username, user_permissions)
       user_permissions = []
       username = row[1]
       userID = row[0]

    user_permissions.append((row[2], row[3]))

printUserPermissions(username, user_permissions)

【讨论】:

谢谢。我认为它也不适用于 sql。我没有像大多数数据透视示例显示的那样将字段的值加在一起,并且有太多权限字段供我编写列。管理员拥有超过 120 个权限。我想我必须用 C# 编写代码。【参考方案4】:

您可以创建一个临时 table_flatuserpermissions 的:

用户ID
权限 ID1
权利1
权限 ID2
权利2
...等您需要的尽可能多的权限/权利组合

从 Table_user 将记录插入此表,所有权限和权限字段均为空。

从 table_userpermissions 更新此表上的记录 - 第一条记录插入并设置 PermissionID1 和 Rights1,第二条记录用于用户更新 PermissionsID2 和 Rights2,等等。

然后您查询此表以生成您的报告。

就个人而言,我会坚持使用您现在拥有的 UserId、UserName、PermissionID、Rights 列。

可以在某些文本中替换 PermissionID 和 Rights,而不是数值。

可能按 PermissionID、User 而不是 User、PermissionID 对表进行排序,以便审核员可以检查每个权限类型的用户。

【讨论】:

【参考方案5】:

如果可以接受,我用于设计和/或实施的策略是将查询转储到 Excel 或 Access 中。两者都具有更友好的数据透视用户界面,并且更多人在这种环境中感到舒适。

一旦有了自己喜欢的设计,就更容易考虑如何在 TSQL 中复制它。

【讨论】:

【参考方案6】:

看起来,pivot 函数是为您可以在其中一个字段上使用聚合函数的情况而设计的。就像我想知道每个销售人员为 x 公司赚了多少收入一样。我可以总结销售表中的价格字段。然后我会得到销售人​​员以及他们有多少销售收入。对于权限,尽管对permissionId 字段或Rights 字段进行求和/计数/等是没有意义的。

【讨论】:

【参考方案7】:

您可能希望查看以下有关在 SQL 中创建交叉表查询的示例:

http://www.databasejournal.com/features/mssql/article.php/3521101/Cross-Tab-reports-in-SQL-Server-2005.htm

SQL Server 2005 中似乎包含了一些新操作,称为 PIVOT 和 UNPIVOT

【讨论】:

【参考方案8】:

对于这种类型的数据转换,您需要对数据执行UNPIVOTPIVOT。如果您知道要转换的值,则可以使用静态数据透视对查询进行硬编码,否则可以使用动态 sql。

创建表格:

CREATE TABLE Table_User
    ([UserId] int, [UserName] varchar(5))
;

INSERT INTO Table_User
    ([UserId], [UserName])
VALUES
    (1, 'John'),
    (2, 'Joe'),
    (3, 'James')
;

CREATE TABLE Table_UserPermissions
    ([UserId] int, [PermissionId] int, [Rights] int)
;

INSERT INTO Table_UserPermissions
    ([UserId], [PermissionId], [Rights])
VALUES
    (1, 10, 1),
    (1, 11, 2),
    (1, 12, 3),
    (2, 11, 2),
    (2, 12, 3),
    (3, 10, 2)
;

静态枢轴:

select *
from
(
  select userid,
    username,
    value,
    col + '_'+ cast(rn as varchar(10)) col
  from
  (
    select u.userid,
      u.username,
      p.permissionid,
      p.rights,
      row_number() over(partition by u.userid 
                        order by p.permissionid, p.rights) rn
    from table_user u
    left join Table_UserPermissions p
      on u.userid = p.userid
  ) src
  unpivot
  (
    value
    for col in (permissionid, rights)
  ) unpiv
) src
pivot
(
  max(value)
  for col in (permissionid_1, rights_1, 
              permissionid_2, rights_2, 
              permissionid_3, rights_3)
) piv
order by userid

见SQL Fiddle with Demo

动态枢轴:

如果permissionids和rights的数量未知,那么可以使用动态sql:

DECLARE 
    @query  AS NVARCHAR(MAX),
    @colsPivot as  NVARCHAR(MAX)

select @colsPivot = STUFF((SELECT  ',' 
                      + quotename(c.name +'_'+ cast(t.rn as varchar(10)))
                    from
                    (
                      select row_number() over(partition by u.userid 
                                order by p.permissionid, p.rights) rn
                      from table_user u
                      left join Table_UserPermissions p
                        on u.userid = p.userid
                    ) t
                    cross apply sys.columns as C
                   where C.object_id = object_id('Table_UserPermissions') and
                         C.name not in ('UserId')
                   group by c.name, t.rn
                   order by t.rn
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
  = 'select *
     from
     (
       select userid,
         username,
         value,
         col + ''_''+ cast(rn as varchar(10)) col
       from
       (
         select u.userid,
           u.username,
           p.permissionid,
           p.rights,
           row_number() over(partition by u.userid 
                             order by p.permissionid, p.rights) rn
         from table_user u
         left join Table_UserPermissions p
           on u.userid = p.userid
       ) src
       unpivot
       (
         value
         for col in (permissionid, rights)
       ) unpiv
     ) x1
     pivot
     (
       max(value)
       for col in ('+ @colspivot +')
     ) p
     order by userid'

exec(@query)

见SQL Fiddle with demo

两者的结果都是:

| USERID | USERNAME | PERMISSIONID_1 | RIGHTS_1 | PERMISSIONID_2 | RIGHTS_2 | PERMISSIONID_3 | RIGHTS_3 |
---------------------------------------------------------------------------------------------------------
|      1 |     John |             10 |        1 |             11 |        2 |             12 |        3 |
|      2 |      Joe |             11 |        2 |             12 |        3 |         (null) |   (null) |
|      3 |    James |             10 |        2 |         (null) |   (null) |         (null) |   (null) |

【讨论】:

以上是关于桥接表的 SQL 查询帮助的主要内容,如果未能解决你的问题,请参考以下文章

Excel 和使用桥接表的多值维度

mysql - 来自桥接表的多对多查询[关闭]

sql连接需要进一步帮助

是否可以使用 JDBC 到 ODBC 桥接进行 SQL 连接?

sql 调用从特定dnis桥接扩展 - 此特定查询为cal调用dnis,ani,扩展名,call_begin_datetime和音频

邮政编码/城市多对多桥接表的最佳设计