【问题标题】:TSQL creating a dynamic report from two tables, one table is holds the headers, other one, dataTSQL 从两张表创建动态报告,一张表保存标题,另一张保存数据
【发布时间】:2013-01-29 06:10:46
【问题描述】:

假设我想从[FormValues] 获取动态报告作为数据,基于[ReportItems][Title] 列作为标题。

我真的很困惑如何做到这一点并尝试了很多方法,但没有一个能正常工作。

我应该可以给一个过程一个 [ReportID] 并得到结果。

[FormID][FieldID] 是两个表之间的关系键。

我们将不胜感激。

CREATE TABLE #ReportItems(
    ReportItemID [uniqueidentifier] NOT NULL primary key,
    ReportID [uniqueidentifier] NOT NULL,
    FormID [uniqueidentifier] NOT NULL,
    FieldID [uniqueidentifier] NOT NULL,
    Title nvarchar(100) NOT NULL
) 
GO

insert into #ReportItems
select '5674d274-b146-4251-be0d-a15000e7cefa', '597d37c0-563b-42f0-99be-a15000dc7a65', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'First Name'
insert into #ReportItems
select '5674d274-b146-4252-be0d-a15000e7cefa', '597d37c0-563b-42f0-99be-a15000dc7a65', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7b', 'Last Name'
insert into #ReportItems
select '5674d274-b146-4253-be0d-a15000e7cefa', '597d37c0-563b-42f0-99be-a15000dc7a65', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'Age'
GO

CREATE TABLE #FormValues(
    ValueID uniqueidentifier NOT NULL primary key,
    FormID uniqueidentifier NULL,
    FieldID uniqueidentifier NOT NULL,
    UserName nvarchar(100) NOT NULL,
    Value nvarchar(max) null
)
GO

insert into #FormValues
select 'af6dc400-3972-49ff-9711-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'user 1', 'Mike'
insert into #FormValues
select 'af6dc400-3972-49ff-9721-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7b', 'user 1', 'Oscar'
insert into #FormValues
select 'af6dc400-3972-49ff-9731-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'user 1', '20'

insert into #FormValues
select 'af6dc400-3972-49ff-9741-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'user 2', 'Merry'
insert into #FormValues
select 'af6dc400-3972-49ff-9761-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'user 2', '23'

insert into #FormValues
select 'af6dc400-3972-49ff-9771-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'user 3', 'Alen'
insert into #FormValues
select 'af6dc400-3972-49ff-9781-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7b', 'user 3', 'Escott'
insert into #FormValues
select 'af6dc400-3972-49ff-9791-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'user 3', '28'
GO

Select * from #ReportItems
Select * from #FormValues
GO

我想要这样的报告作为结果:

User Name   |   First Name  |   Last Name   |   Age
User 1      |   Mike        |   Oscar       |   20
User 2      |   Merry       |               |   23
User 3      |   Alen        |   Escott      |   28
User n      |   ...         |   ...         |   ...



drop table #ReportItems
drop table #FormValues

【问题讨论】:

    标签: sql sql-server tsql pivot


    【解决方案1】:

    要获得您想要的结果,您需要使用PIVOT 函数。

    如果提前知道所有值 (title),那么您可以硬编码静态查询:

    select *
    from
    (
        select r.Title, f.UserName, f.Value
        from ReportItems r
        left join FormValues f
            on r.FormID = f.FormID
            and r.FieldID = f.FieldID
    ) src
    pivot
    (
        max(value)
        for title in ([First Name], [Last Name], Age)
    ) piv;
    

    SQL Fiddle with Demo

    但听起来您将有未知数量的titles,您想将其转换为列。如果是这种情况,那么您将需要使用动态 sql:

    DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)
    
    select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Title) 
                        from ReportItems
                FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')
    
    set @query = 'SELECT UserName,' + @cols + ' from 
                 (
                    select r.Title, f.UserName, f.Value
                    from ReportItems r
                    left join FormValues f
                        on r.FormID = f.FormID
                        and r.FieldID = f.FieldID
                ) x
                pivot 
                (
                    max(value)
                    for Title in (' + @cols + ')
                ) p '
    
    execute(@query)
    

    SQL Fiddle with Demo

    两者的结果都是:

    | USERNAME | FIRST NAME | LAST NAME | AGE |
    -------------------------------------------
    |   user 1 |       Mike |     Oscar |  20 |
    |   user 2 |      Merry |    (null) |  23 |
    |   user 3 |       Alen |    Escott |  28 |
    

    如果您有一个特定的SortOrder 需要并将其存储在一个表中,那么当您获取列列表时,您将使用以下内容,它会以正确的顺序返回列:

    select @cols = STUFF((SELECT ',' + QUOTENAME(Title) 
                        from ReportItems
                        group by Title, sortorder
                        order by sortorder
                FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')
    

    SQL Fiddle with Demo

    【讨论】:

    • 非常感谢您的完美回答。如果您不介意我需要另外两个选项:1. 如何通过 [ReportID] 请求结果 2. 如何通过 [ReportItems] 中名为 [SortOrder] 的列对标题进行排序?
    • @Kardo 你能用所需的额外数据编辑 sql fiddle 吗?编辑时,请在此处发布链接。或者使用其他详细信息创建一个新问题。
    • 事实上你的答案是完美的并且工作正常。我想对要按 ReportItems 中名为 [SortOrder] 的列排序的标题(例如:USERNAME | FIRST NAME | LAST NAME | AGE)进行排序。
    • @Kardo 查看我的编辑,了解如何使用SortOrder 保持列的正确顺序。
    • with DISTINCT in select 我得到了这个错误:select @ cols = STUFF((SELECT distinct ',' + QUOTENAME(Title) ------- 并通过删除 DISTINCT 它工作正常。希望删除 DISTINCT 以后不会造成任何问题。
    【解决方案2】:
    DECLARE @SQL NVARCHAR(MAX)
    
    SET @SQL='SELECT F.UserName'
    SELECT @SQL = @SQL+', MAX(CASE WHEN FieldID='''+CONVERT(VARCHAR(50), FieldID)+''' THEN F.Value END) AS ['+Title+']
    '
    FROM #ReportItems
    SET @SQL = @SQL+' FROM #FormValues F GROUP BY F.UserName ORDER BY 1'
    --select @sql
    EXEC sp_ExecuteSQL @SQL
    

    稍后编辑:基于报告 ID 和排序列的报告过程

    CREATE PROCEDURE spReport
    @ReportID uniqueidentifier,
    @SortColumns NVARCHAR(MAX) --shoud be a comma separated list of ReportItems.Title
    AS 
    BEGIN
        DECLARE @SQL NVARCHAR(MAX)
        SET @SQL='SELECT F.UserName'
        SELECT @SQL = @SQL+', MAX(CASE WHEN F.FieldID='''+CONVERT(VARCHAR(50), FieldID)+''' THEN F.Value END) AS ['+Title+']
        '
        FROM ReportItems
        WHERE ReportID=@ReportID --create the dynamic sql only for the items in your report 
    
        SET @SQL = @SQL+' FROM FormValues F 
        JOIN ReportItems R ON F.FormID=R.FormID
        WHERE R.ReportID = @ReportID
        GROUP BY F.UserName '
        IF @SortColumns<>''
            SET @SQL = @SQL + 'ORDER BY '+@SortColumns -- beware of SQL injection. 
        select @sql
        EXEC sp_ExecuteSQL @SQL, N'@ReportID uniqueidentifier', @ReportID=@ReportID
    END
    

    但是,我怎么强调都不为过,你必须特别注意 @SortColumns 参数,因为你正在向 SQL 注入开放。

    【讨论】:

    • 嗨@Daniel,你的回答也是正确的。现在我很困惑哪个是更好的解决方案?再次感谢:)
    • 嗯,Phil Factor 有一篇文章(我认为是在 simple-talk.com),认为 CASE 解决方案的性能优于 PIVOT 解决方案。无论如何,这是你的电话。
    • 其实这篇文章是Jeff Moden写在SQLServerCentral sqlservercentral.com/articles/T-SQL/63681
    • 好的,非常感谢。还有一个问题:我想通过 [ReportID] (在 sp 中)获得结果,并通过 [ReportItems] 表中的 [SortOrder] 列对标题进行排序。如果您让我知道如何计算,我将不胜感激?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-16
    • 1970-01-01
    • 2015-01-05
    相关资源
    最近更新 更多