【问题标题】:Transposing a table in SQL server在 SQL Server 中转置表
【发布时间】:2011-12-08 10:23:25
【问题描述】:

我正在为我的应用程序使用 SQL Server 2005。 我的存储过程中有一个表,它有两列,C1 和 C2。我想转置此表,使 C1 列的值成为列。 转置前(表一):

C1  C2
M1  U1
M1  U2
M1  U3
M2  U4
M2  U5

转置后(表 2):

M1  M2
U1  U4
U2  U5
U3  NULL

在表 1 中,不同值(M1、M2)的数量可能会有所不同。因此,Table2 中的列不固定。

请提供一个解决方案来达到同样的效果。

【问题讨论】:

标签: sql sql-server pivot transpose


【解决方案1】:

对于这种类型的数据转换,您需要使用 SQL Server 2005+ 中提供的PIVOT 函数。有两种方法可以应用 pivot 函数。

如果您提前知道这些值,则可以对查询中的值进行硬编码。类似这样:

select M1, M2
from
(
  select c1, c2,
    row_number() over(partition by c1 order by c1, c2) rn
  from yourtable
) src
pivot
(
  max(c2)
  for c1 in (M1, M2)
) piv

见SQL Fiddle with Demo。

但是,如果您有未知数量的值要转置到列中,则可以使用动态 SQL 在运行时创建查询。

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(C1) 
                    from yourtable
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT ' + @cols + ' from 
             (
                select C1, C2,
                  row_number() over(partition by c1 order by c1, c2) rn
                from yourtable
            ) x
            pivot 
            (
                max(C2)
                for C1 in (' + @cols + ')
            ) p '

execute(@query)

见SQL Fiddle with Demo。

两者都会给出相同的结果,不同的是动态版本是灵活的,如果值会改变:

| M1 |     M2 |
---------------
| U1 |     U4 |
| U2 |     U5 |
| U3 | (null) |

【讨论】:

  • 不错的解决方案,但您能否详细说明row_number() over(partition by c1 order by c1, c2) rn 在这种情况下的作用?
  • @JeremyS。当您使用字符串值时,您的聚合选项仅限于max 或min。因此,如果您不使用row_number,您将只得到一行。 row_number 和 partition 生成一个不同的值,该值在 group by 期间保持分隔的值。请参阅this demo 以了解没有row_number 会发生什么,您只会得到一行。
  • 感谢这个例子,我看到了,但仍然不明白 MSSQL 如何知道将row_number 视为一个不同的值,它不包含在 group by 子句中,因为没有, MSSQL 怎么会知道呢?
  • @JeremyS。 PIVOT 正在执行聚合,而 group by 未明确说明它正在使用中。我建议阅读 MSDN 上的PIVOT docs。或者,如果您有特定问题,请发布新问题。 :)
  • 你好,你的transponation对我很有用,但我不能在mysql server 5.1上使用它。我在开始时收到关于“声明”的错误。我该怎么做才能让这个运行?
【解决方案2】:

这可能是动态 sql 是你朋友的情况。假设您的基表名称是“转置”:

--Get a Unique List of values from C1 --> These will be our column headers
DECLARE @Unique TABLE (C1 VARCHAR(25), fUsed BIT DEFAULT 0);
INSERT INTO @Unique (C1) SELECT DISTINCT C1 FROM Transpose;

DECLARE @TransSQL NVARCHAR(4000);
DECLARE @ColID NVARCHAR(25);

SET @TransSQL = N'SELECT ' 

--Loop over unique C1 values and construct the select statement
SELECT @ColID = (SELECT TOP 1 C1 FROM @Unique WHERE fUSed = 0);
WHILE @@ROWCOUNT <> 0 AND @ColID IS NOT NULL
BEGIN
    SET @TransSQL = @TransSQL + 'CASE C1 WHEN ' + '''' + @ColID + '''' + ' THEN C2 ELSE NULL END AS ' + @ColID + ', '

    --Update flag in table so we don't use this field again
    UPDATE u SET fUsed = 1 FROM @Unique u WHERE C1 = @ColID;
    SELECT @ColID = (SELECT TOP 1 C1 FROM @Unique WHERE fUSed = 0);
END

--Remove Trailing comma and add FROM clause
DECLARE @SQL NVARCHAR(4000)
SET @SQL = LEFT(@TransSQL,LEN(@TransSQL) -1) + ' FROM Transpose'

--For debugging purposes
PRINT @SQL;

--Execute the dynamic sql
EXEC sp_executesql @SQL;

这不会实现您所描述的问题的确切布局,因为它们不是对结果进行分组的关键字段。相反,输出将如下所示:

M1     M2      M3
U1     NULL    NULL
U2     NULL    NULL
U3     NULL    NULL
NULL   U4      NULL
NULL   U5      NULL
NULL   NULL    U6

如果您的基表有一个关键字段,则可以稍微修改查询以按关键字段对结果进行分组,并且可能更接近您对结果数据的既定目标。

【讨论】:

  • 嘿,谢谢您的回复....有没有办法只对 SQL 中的一列进行排序,这样所有其他列都不会因此而排序。在excel中,我们可以做到这一点。不了解 SQL Server。如果有任何方法可以做到这一点,请提供帮助。提前致谢。
【解决方案3】:

这不是你想要的确切结果,但你可以试试这个

;WITH TAB1 AS
( SELECT  [ResponsibleID] C1,[ActionID] C2,1 ORD
  FROM [TEST].[dbo].[yourtable]
  WHERE 1=1
  )


 SELECT 
    CASE WHEN M1=1 THEN ActionID ELSE NULL END M1,
    CASE WHEN M2=1 THEN ActionID ELSE NULL END M2 
 FROM TAB1
 PIVOT(AVG(ORD) FOR RESPONSIBLEID IN ([M1],[M2])) AS ABC

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-05
    • 1970-01-01
    相关资源
    最近更新 更多