【问题标题】:Help with Pivot / Unpivot帮助枢轴/取消枢轴
【发布时间】:2010-03-05 00:34:24
【问题描述】:

我有一张如下表

Name    Priority    Date    
-------------------------
A         2          d1
B         3          d2

如何编写查询来实现以下输出

ColumnNames   d1      d2
--------------------------
 Name         A       B
 Priority     2       3

谢谢

【问题讨论】:

  • 这看起来很眼熟……这和你前几天问的问题一样吗?

标签: sql sql-server tsql sql-server-2008 pivot


【解决方案1】:

这是我承诺的解决方案:

编辑:我修改了答案以回答 OP 提出的其他问题。

需要注意的几点:

  • STUFF 函数:用于将 XML 字符串转换为常规字符串(并删除第一个逗号)
  • Group By(我从 OMG Ponies 偷来的):您需要这样做以确保您没有任何重复的日期
  • 在运行之前确保表格中没有太多的日期。列过多可能是个问题
  • NVARCHAR:我使用它而不是 VARCHAR 作为 @sql 变量,因为 sp_ExecuteSQL 需要它。
  • CONVERT(VARCHAR, DateColumn, 101):我这样做是因为除非您将日期转换为字符串,否则这是行不通的。 101 结果:mm/dd/yyyy,但你可以使用任何你需要的东西(确保它在这个脚本中使用的 2 次匹配)
  • 为了使这适用于多列,您必须首先使用 UNPIVOT 并将所有列转换为相同的数据类型(代码下方有更多信息)
  • 重要的是要注意,为了连接字符串,它们必须是相同的数据类型、相同的大小(在我的例子中,它们都是 NVARCHAR(MAX))

阅读this page 了解有关将日期转换为字符串的更多信息。

话虽如此,代码如下:

-- table with multiple columns
CREATE TABLE #TBL ( 
    NameColumn VARCHAR(10), 
    PriorityColumn INT,
    AnotherColumn FLOAT,
    DateColumn DATETIME 
)


-- Insert the test data
INSERT INTO #TBL VALUES ('a', 1, 7.2, '1/1/2000')
INSERT INTO #TBL VALUES ('a', 2, 8.9, '1/2/2000')
INSERT INTO #TBL VALUES ('a', 2, 53.2, '1/3/2000')
INSERT INTO #TBL VALUES ('a', 3, 9.12, '1/4/2000')
INSERT INTO #TBL VALUES ('b', 2, 1.26, '1/1/2001')


DECLARE
    @sql NVARCHAR(max),
    @dates NVARCHAR(max)


-- I separated this to make the code easier to read
SET @dates = STUFF(
    (
        SELECT N',[' + CONVERT(VARCHAR, DateColumn, 101) + ']' AS [text()]
        FROM #TBL
        GROUP BY DateColumn
        ORDER BY DateColumn
        FOR XML PATH('')
    ), 1, 1, N''
)


-- I will break this part of the code up below
SET @sql = N'SELECT
    *
FROM (
    SELECT
        ColumnName,
        ColumnValue,
        CONVERT(VARCHAR, DateColumn, 101) AS DateString
    FROM (
        SELECT
            CAST(NameColumn AS VARCHAR(100)) AS NameColumn,
            CAST(PriorityColumn AS VARCHAR(100)) AS PriorityColumn,
            CAST(AnotherColumn AS VARCHAR(100)) AS AnotherColumn,
            DateColumn
        FROM #TBL
    ) P
    UNPIVOT (
        ColumnValue
        FOR ColumnName IN (NameColumn, PriorityColumn, AnotherColumn)
    ) UNPIV
) P2
PIVOT (
    MAX(ColumnValue)
    FOR DateString IN (' + @dates + N')
) PIV'

EXECUTE dbo.sp_ExecuteSQL @sql


DROP TABLE #TBL

让我们稍微了解一下

-- I first do an UNPIVOT on all of the columns I want to pivot on, at the same time, converting them to the same datatype
SELECT
    ColumnName,
    ColumnValue,
    CONVERT(VARCHAR, DateColumn, 101) AS DateString
FROM (
    SELECT
        CAST(NameColumn AS VARCHAR(100)) AS NameColumn,
        CAST(PriorityColumn AS VARCHAR(100)) AS PriorityColumn,
        CAST(AnotherColumn AS VARCHAR(100)) AS AnotherColumn,
        DateColumn
    FROM #TBL
) P
UNPIVOT (
    ColumnValue
    FOR ColumnName IN (NameColumn, PriorityColumn, AnotherColumn)
) UNPIV

执行此操作后,数据将如下所示:

ColumnName  ColumnValue DateString
----------------------------------
NameColumn      a       01/01/2000
PriorityColumn  1       01/01/2000
AnotherColumn   7.2     01/01/2000
NameColumn      a       01/02/2000
PriorityColumn  2       01/02/2000
AnotherColumn   8.9     01/02/2000
NameColumn      a       01/03/2000
PriorityColumn  2       01/03/2000
AnotherColumn   53.2     01/03/2000
NameColumn      a       01/04/2000
PriorityColumn  3       01/04/2000
AnotherColumn   9.12     01/04/2000
NameColumn      b       01/01/2001
PriorityColumn  2       01/01/2001
AnotherColumn   1.26     01/01/2001

然后我们可以像这样使用 PIVOT 来获取我们需要的所有列:

PIVOT (
    MAX(ColumnValue)
    FOR DateString IN (' + @dates + N')
) PIV

【讨论】:

  • 困惑。我明白了这个概念,但无法将其应用于我的问题以获得所需的输出。
  • 是日期列 DATETIME 的数据类型?如果是这样,您将需要编写一些动态 SQL。我回家后可以告诉你更多细节。现在,我无法访问我的代码。
  • 好的。当我今晚回到家时,我将使用动态 sql 编辑我的答案(除非有人击败我)。
  • 好的。我用我承诺的解决方案修改了我的答案。
  • @Gabriel:谢谢。那行得通。我已经编辑了我的问题并更新了输出。是否可以在输出中也显示列名?
【解决方案2】:

这是你要找的吗:

create table NameAndDate (NameCol varchar(200), DateCol datetime);

insert into NameAndDate (NameCol, DateCol) values ('A', '2010-03-04');
insert into NameAndDate (NameCol, DateCol) values ('B', '2010-03-05');

select * from NameAndDate;

select * from NameAndDate
pivot (
max(NameCol) 
for DateCol 
in ([2010-03-04], [2010-03-05])) 
as PivotResults;

这给了我以下结果:

   NameCol     DateCol
---------------------------------------
1  A           2010-03-04 00:00:00.0000
2  B           2010-03-05 00:00:00.0000


   2010-03-04  2010-03-05
-------------------------
1  A           B

请注意,这要求您在编写查询时提前知道日期(除非您使用 Gabriel 提到的动态 SQL)。

编辑:

我尝试使用 OMG Ponies 的方法,但不得不像这样修改它:

declare @Dates nvarchar(max)
set @Dates = 
    (select '['+ convert(varchar, NameAndDate.DateCol) + '],' 
    from NameAndDate
    group by NameAndDate.DateCol
    order by NameAndDate.DateCol
    for xml path(''))
set @Dates = left(@Dates, len(@Dates) - 1)

declare @SQL nvarchar(4000)
set @SQL = 
    'select *  
    from NameAndDate
    pivot (
    max(NameCol) 
    for DateCol in (' + @Dates + ')) 
    as PivotResults'

exec sp_executesql @SQL

这给了我这些结果:

   Mar 4 2010 12:00AM   Mar 5 2010 12:00AM
------------------------------------------
1  A                    B

【讨论】:

    【解决方案3】:

    用途:

    DECLARE @SQL NVARCHAR(4000)
    DECLARE @dates NVARCHAR(max)
    
    SET @dates = SELECT '['+ t.date +"],"
                   FROM TABLE t
               GROUP BY t.date
               ORDER BY t.date
                FOR XML PATH('')
    
    @SQL = 'SELECT * 
              FROM TABLE 
             PIVOT(MAX(name) FOR date IN (@dates)) AS pvt'
    
    BEGIN 
    
      EXEC sp_executesql @SQL, N'@dates NVARCHAR(max)', @dates
    
    END
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 2010-12-01
      相关资源
      最近更新 更多