2017-09-11 38 views
0

SQL Server 2012的T-SQL:跨表将查询转换为列的查询?

单刀直入,这就是我想要做的事:

用户表

id username fullname 
---- -------- ---------------------- 
1 test0001 Test User #1 
2 test0002 Test User #2 
3 test0003 Test User #3 
4 test0004 Test User #4 

旗表

id name  description 
--- ---------- ------------------------------------------- 
1 isActive true if user is currently active 
2 isAdmin true if user can do Admin things 
3 canEdit true if user can can edit 

UserFlags表

user flag 
---- ---- 
1 1 
1 2 
1 3 
2 1 
2 3 
3 1 

(user = FK to user.id, flag = FK to flag.id) 

期望结果

userId username isActive isAdmin canEdit 
------ -------- -------- ------- ------- 
1  test0001 1  1  1 
2  test0002 1  0  1 
3  test0003 1  0  0 
4  test0004 0  0  0 

总之欲每个标志转换在标志表到柱与用作列标题中的name字段。然后我想为每个用户设置一行,每列中都有一个布尔值,表示他们是否拥有该特定标志。

这需要能够适应 - 例如,如果添加了另一个标志,则查询结果应该包含另一个标志名称作为其标题的列。

我宁愿做一个观点,但我很确定用表值函数。

我没有做过这样的事之前,所以我甚至不知道从哪里开始 - 我可以做一个完整的加盟在桌子上,并与每位用户的标志行结束了,但后来我想折叠所有这些都是按每个用户划分为一行。

编辑其中一个关键点是“能适应” - 最好的情形是查询建立响应时,会自动提取从标志表中的所有当前定义的标志。不必编辑查询不一定是坏事,但考虑以下情况:管理员允许一个新的标志添加到系统中的实例。插入新标志很容易,自动编辑存储的查询以反映这一点更加困难。如果那根本无法做到,那么解释为什么会有帮助。谢谢!

+1

您将需要PIVOT最后一个表。你试过什么了? – Eli

+0

我现在正在做的就是调用一个UDF,我写到询问“用户X是否有标记Y”。然后,我为每个用户反复调用该函数,每个标志基本上最后都会有一个表,每个用户每个标志用一个布尔值表示一行。正如我在下面的评论中所说的,这里的所有解决方案都有效,但需要修改查询本身以处理更多标志。我正在寻找PIVOT,看看它是否有可能用它来完成。 – fdmillion

+0

有两个答案可以帮助您:Kannan提供了PIVOT答案,而lostmylogin提供了更简单的非PIVOT答案。两者都应该解决你的问题。 – Eli

回答

2

可以使用旋转如下:如下

Select * from (
    Select u.id, u.username, f.[name] from #user u 
    left join #userflags uf 
    on uf.[user] = u.id 
    left join #flags f 
    on uf.flag = f.id 
) a 
pivot (count([name]) for [name] in ([isActive],[isAdmin],[canEdit])) p 

输出:

+----+----------+----------+---------+---------+ 
| id | username | isActive | isAdmin | canEdit | 
+----+----------+----------+---------+---------+ 
| 1 | test0001 |  1 |  1 |  1 | 
| 2 | test0002 |  1 |  0 |  1 | 
| 3 | test0003 |  1 |  0 |  0 | 
| 4 | test0004 |  0 |  0 |  0 | 
+----+----------+----------+---------+---------+ 

Demo

更新我的查询,如果你有如下标志的动态列表:

Declare @cols1 varchar(max) 
Declare @query nvarchar(max) 

Select @cols1 = stuff((select distinct ','+QuoteName([name]) from #flags for xml path('')),1,1,'') 

Select @query = ' Select * from (
     Select u.id, u.username, f.[name] from #user u 
     left join #userflags uf 
     on uf.[user] = u.id 
     left join #flags f 
     on uf.flag = f.id 
    ) a 
    pivot (count([name]) for [name] in (' + @cols1 + ')) p ' 

Exec sp_executesql @query 
+0

您的回应是最简洁的,但如果添加新标志,则此处的所有回复都需要编辑查询。是否无法自动从标志表中拉入标志? – fdmillion

+0

然后,您需要添加动态查询以添加该标志。相应地更新我的查询 –

+0

这看起来很有希望。我还发现一篇文章http://sqlmag.com/t-sql/pivoting-dynamic-way,这似乎解释了这种事情。 @Eli让我和PIVOT走在了正确的道路上。将尝试你的解决方案! – fdmillion

0

您可以用case语句和子查询或与枢轴做到这一点。他们都基本上做同样的事情。这包括我为测试而制作的表的DDL。

declare @user_tbl table (
id int, 
username nvarchar(max) 
) 

INSERT @user_tbl VALUES 
(1,'test0001'), 
(2,'test0002'), 
(3,'test0003'), 
(4,'test0004') 


declare @userflags_tbl table(
userid int, 
flag int 
) 

INSERT @userflags_tbl VALUES 
(1,1), 
(1,2), 
(1,3), 
(2,1), 
(2,3), 
(3,1) 

declare @flags_tbl table(
id int 
) 

INSERT @flags_tbl VALUES 
(1), 
(2), 
(3) 

SELECT 
    userid, 
    username, 
    MAX(isActive) AS isActive, 
    MAX(isAdmin) AS isAdmin, 
    MAX(canEdit) AS canEdit 
FROM (
    SELECT 
     user_tbl.id AS userid, 
     user_tbl.username, 
     CASE 
      WHEN userflags_tbl.flag = 1 THEN 1 
      ELSE 0 
      END AS isActive, 
     CASE 
      WHEN userflags_tbl.flag = 2 THEN 1 
      ELSE 0 
      END AS isAdmin, 
     CASE 
      WHEN userflags_tbl.flag = 3 THEN 1 
      ELSE 0 
      END AS canEdit 
    FROM @user_tbl user_tbl 
    LEFT JOIN @userflags_tbl userflags_tbl ON 
     user_tbl.id = userflags_tbl.userid 
    LEFT JOIN @flags_tbl flags_tbl ON 
     userflags_tbl.flag = flags_tbl.id 
    )tbl 
GROUP BY 
    userid, 
    username 


SELECT 
    userid, 
    username, 
    ISNULL([1],0) AS isActive, 
    REPLACE(ISNULL([2],0),2,1) AS isAdmin, 
    REPLACE(ISNULL([3],0),3,1) AS canEdit 
FROM (
    SELECT 
     user_tbl.id AS userid, 
     user_tbl.username, 
     userflags_tbl.flag 
    FROM @user_tbl user_tbl 
    JOIN @userflags_tbl userflags_tbl ON 
     user_tbl.id = userflags_tbl.userid 
    JOIN @flags_tbl flags_tbl ON 
     userflags_tbl.flag = flags_tbl.id 
    ) tbl 
PIVOT (
    MAX(flag) 
    FOR flag IN ([1],[2],[3]) 
    ) AS PivotTable 
2

如果你不喜欢pivot像我这样的功能;您可以使用SUMIIF方法这样

SELECT u.id 
    , username 
    , SUM(IIF(flag = 1, 1, 0)) AS isActive 
    , SUM(IIF(flag = 2, 1, 0)) AS isAdmin 
    , SUM(IIF(flag = 3, 1, 0)) AS canEdit 
FROM User u 
LEFT JOIN UserFlags uf ON uf.[user] = u.id 
GROUP BY u.id 
    , username