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

时间:2017-09-11 17:28:14

标签: sql-server

SQL Server 2012。

直截了当,这就是我想要做的事情:

用户表

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

简而言之,我想将flags表中的每个标志转换为一个列,其中name字段用作列标题。然后我想为每个用户添加一行,每列中都有一个布尔值,表示它们是否具有该特定标志。

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

我更喜欢在视图中执行此操作,但我可以使用表值函数。

我之前没有做过这样的事情,所以我甚至不知道从哪里开始 - 我可以在桌面上进行完全连接,最后每个用户每个标志一行,但我接着希望将每个用户的所有内容折叠成一行。

编辑其中一个关键点是能够适应" - 最佳方案是在构建响应时自动从flags表中提取所有当前定义的标志的查询。必须编辑查询并不一定是坏事,但考虑允许管理员向系统添加新标志的实例。插入新标志很容易,自动编辑存储的查询以反映这一点很难。如果这根本不可能做到,那么解释为什么会有所帮助。谢谢!

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:2)

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

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

答案 2 :(得分:0)

您可以使用case语句和子查询或使用pivot来执行此操作。它们基本上都做同样的事情。这包括我为测试而制作的表格的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