将行转换为列

时间:2016-03-04 16:11:44

标签: sql-server database

我有一个包含'Name','Amount'和'ReasonId'列的查询。我想总结金额并将原因放在一行,以便将每个名字保持为一行。大约有50个不同的ReasonId,所以我不想将列命名为ReasonId的名称。相反,我想将列命名为“Reason1”,“Reason2”,“Reason3”和“Reason4”。一个名称最多可以有4个不同的原因。

我有这个:

Name   Amount   ReasonId
-------------------------
Bob    $5       7
Bob    $8       6
John   $2       8
John   $5       9
John   $3       9
John   $8       4

我想制作以下内容:

Name   Amount   Reason1   Reason2   Reason3   Reason4
-----------------------------------------------------
Bob    $13      7         6         NULL      NULL
John   $18      8         9         4         NULL

3 个答案:

答案 0 :(得分:0)

一种方法是使用dense_rank窗口函数对行进行编号,然后使用条件聚合将原因放在正确的列中。

我无法看到任何可以提供原因列的特定顺序的内容,但是可能有一些列缺少提供订单?

with cte as (
    select 
       name, 
       reasonid,
       amount, 
       dense_rank() over (partition by name order by reasonid) rn
    from your_table
)

select 
    name, 
    sum(amount) amount, 
    max(case when rn = 1 then reasonid end) reason1, 
    max(case when rn = 2 then reasonid end) reason2, 
    max(case when rn = 3 then reasonid end) reason3, 
    max(case when rn = 4 then reasonid end) reason4 
from cte 
group by name

如果您有一些列提供了您想要的订单,请更改order by函数中使用的dense_rank子句。

Sample SQL Fiddle(使用PG作为MSSQL似乎处于离线状态。)

上述查询的输出为:

| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
|  Bob |     13 |       6 |       7 |  (null) |  (null) |
| John |     18 |       4 |       8 |       9 |  (null) |

答案 1 :(得分:0)

你也可以用一个支点来达到这个目的;如果你知道列可以在脚本中输入它们,但如果没有,你可以使用动态sql(有理由你可能想要避免使用动态解决方案)。

此路由的优点是您可以在表中输入列列表,然后对该表的更改将导致输出更改并更改所涉及的脚本。缺点是与动态SQL相关的所有缺点。

为了变体,这里是一个使用临时表来保存数据的动态SQL解决方案,因为提供了不同的可能性:

-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))

INSERT #AllPossibleReasons
VALUES
     (1,'Reason1')
    ,(2,'Reason2')
    ,(3,'Reason3')
    ,(4,'Reason4')
    ,(5,'Reason5')
    ,(6,'Reason6')
    ,(7,'Reason7')
    ,(8,'Reason8')
    ,(9,'Reason9')

INSERT #MyTab
VALUES
     ('Bob',7,7)
    ,('Bob',8,6)
    ,('John',2,8)
    ,('John',5,9)
    ,('John',3,9)
    ,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE @ReasonList VARCHAR(MAX) = ''
DECLARE @SQL VARCHAR(MAX)

SELECT @ReasonList = @ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET @ReasonList = SUBSTRING(@ReasonList,2,LEN(@ReasonList))

SET @SQL = 
'SELECT Name,Value,' + @ReasonList + ' FROM
    (SELECT 
        M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
     FROM
            #MyTab                              AS M
     INNER JOIN #AllPossibleReasons             AS Reason   ON M.ReasonId = Reason.Id
     INNER JOIN(SELECT T.Name, SUM(Amount)Value
                FROM #MyTab T GROUP BY T.Name)      AS Total    ON M.Name = Total.Name
     GROUP BY M.Name, Reason.Label) AS Up
    PIVOT (SUM(THis) FOR Label IN (' + @ReasonList + ')) AS Pvt'

EXEC (@SQL)

DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab

答案 2 :(得分:0)

根据ListAGG in SQLSERVER中的信息,我想出了一个有点丑陋的例子:

with tbl1 as (
  -- Set up initial data set
  select 'Bob' name,        5 amount, 7 ReasonId 
  union all select 'Bob' ,  3,        4
  union all select 'Bob',   2,        1
  union all select 'Brian', 8,        2
  union all select 'Bob',   6,        4
  union all select 'Brian', 1,        3
  union all select 'Tim',   2,        2)
, TBL2 AS ( -- Add a blank to separate the concatenation
  SELECT NAME
       , AMOUNT
       , CAST(ReasonId as varchar) + ' ' ReasonId   from tbl1
)
select ta.name
     , Total
     , ReasonIds from ( 
       (select distinct name, stuff((select distinct '' + t2.ReasonId  from tbl2 t2
where t1.name = t2.name 
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;

这会将TBL1转换为以下内容:

name   Total ReasonIds
Bob    16    1 4 7 
Brian  9     2 3 
Tim    2     2