为另一列的每个值添加更多列?

时间:2013-03-21 15:56:35

标签: sql sql-server-2008 pivot unpivot

我有这种表:

Name   Date      Value
-----------------------
Test 1/1/2001   10
Test 2/1/2001   17
Test 3/1/2001   52
Foo  5/4/2011   15
Foo  6/4/2011   321
My   15/5/2005  36
My   25/7/2005  75

我想像这样显示结果:

Name   Date      Value  Name   Date      Value  Name   Date      Value
---------------------------------------------------------------------
Test 1/1/2001    10      Foo  5/4/2011   15      My   15/5/2005  36
Test 2/1/2001    17      Foo  6/4/2011   321     My   25/7/2005  75
Test 3/1/2001    52

我需要显示与我的名称列中的内容一样多的列

我怎么能在Sql中执行此操作?

2 个答案:

答案 0 :(得分:6)

为了获得您想要的结果,您将不得不取消忽略表格中的列并应用 pivot 功能。

unpivot 可以使用UNPIVOT功能完成,也可以CROSS APPLY使用VALUES

<强> UNPIVOT:

select rn, 
  col +'_'+cast(dr as varchar(10)) col, 
  new_values
from
(
  select name, 
    convert(varchar(10), date, 101) date, 
    cast(value as varchar(10)) value,
    dense_rank() over(order by name) dr,
    row_number() over(partition by name order by date) rn
  from yourtable
) d
unpivot
(
  new_values
  for col in (name, date, value)
) un;

交叉申请:

select rn, 
  col +'_'+cast(dr as varchar(10)) col, 
  c.value
from
(
  select name, 
    convert(varchar(10), date, 101) date, 
    cast(value as varchar(10)) value,
    dense_rank() over(order by name) dr,
    row_number() over(partition by name order by date) rn
  from yourtable
) d
cross apply
(
  values
    ('Name', name), ('Date', date), ('Value', Value)
) c (col, value);

请参阅两个版本的SQL Fiddle with Demo。这给出了结果:

| RN |     COL | NEW_VALUES |
-----------------------------
|  1 |  name_1 |        Foo |
|  1 |  date_1 | 04/05/2011 |
|  1 | value_1 |         15 |
|  2 |  name_1 |        Foo |
|  2 |  date_1 | 04/06/2011 |
|  2 | value_1 |        321 |
|  1 |  name_2 |         My |
|  1 |  date_2 | 05/15/2005 |
|  1 | value_2 |         36 |

这些查询会获取您现有的列值并将其转换为行。一旦它们成行,您可以使用窗口函数dense_rank创建新的列名。

将数据转换为行后,您可以使用新的列名(使用dense_rank值创建)并应用PIVOT函数。

PIVOT with UNPIVOT:

select name_1, date_1, value_1,
  name_2, date_2, value_2,
  name_3, date_3, value_3
from
(
  select rn, 
    col +'_'+cast(dr as varchar(10)) col, 
    new_values
  from
  (
    select name, 
      convert(varchar(10), date, 101) date, 
      cast(value as varchar(10)) value,
      dense_rank() over(order by name) dr,
      row_number() over(partition by name order by date) rn
    from yourtable
  ) d
  unpivot
  (
    new_values
    for col in (name, date, value)
  ) un
) src
pivot
(
  max(new_values)
  for col in (name_1, date_1, value_1,
              name_2, date_2, value_2,
              name_3, date_3, value_3)
) piv;

请参阅SQL Fiddle with Demo

与交叉申请PIVOT:

select name_1, date_1, value_1,
  name_2, date_2, value_2,
  name_3, date_3, value_3
from
(
  select rn, 
    col +'_'+cast(dr as varchar(10)) col, 
    c.value
  from
  (
    select name, 
      convert(varchar(10), date, 101) date, 
      cast(value as varchar(10)) value,
      dense_rank() over(order by name) dr,
      row_number() over(partition by name order by date) rn
    from yourtable
  ) d
  cross apply
  (
    values
      ('Name', name), ('Date', date), ('Value', Value)
  ) c (col, value)
) src
pivot
(
  max(value)
  for col in (name_1, date_1, value_1,
              name_2, date_2, value_2,
              name_3, date_3, value_3)
) piv;

请参阅SQL Fiddle with Demo

Dyanmic PIVOT:

如果您的列数有限或已知,上述版本将会很有用,如果没有,那么您将需要使用动态SQL:

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

select @cols = STUFF((SELECT ',' + QUOTENAME(col +'_'+cast(dr as varchar(10)))
                    from 
                    (
                      select dense_rank() over(order by name) dr
                      from yourtable
                    ) t
                    cross apply
                    (
                      values(1, 'Name'), (2, 'Date'), (3, 'Value')
                    ) c (sort, col)
                    group by col, dr, sort
                    order by dr, sort
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT ' + @cols + ' 
              from 
             (
                select rn, 
                  col +''_''+cast(dr as varchar(10)) col, 
                  c.value
                from
                (
                  select name, 
                    convert(varchar(10), date, 101) date, 
                    cast(value as varchar(10)) value,
                    dense_rank() over(order by name) dr,
                    row_number() over(partition by name order by date) rn
                  from yourtable
                ) d
                cross apply
                (
                  values
                    (''Name'', name), (''Date'', date), (''Value'', Value)
                ) c (col, value)
            ) x
            pivot 
            (
                max(value)
                for col in (' + @cols + ')
            ) p'

execute(@query)

请参阅SQL Fiddle with Demo

每个查询的结果是:

| NAME_1 |     DATE_1 | VALUE_1 | NAME_2 |     DATE_2 | VALUE_2 | NAME_3 |     DATE_3 | VALUE_3 |
-------------------------------------------------------------------------------------------------
|    Foo | 04/05/2011 |      15 |     My | 05/15/2005 |      36 |   Test | 01/01/2001 |      10 |
|    Foo | 04/06/2011 |     321 |     My | 07/25/2005 |      75 |   Test | 01/02/2001 |      17 |
| (null) |     (null) |  (null) | (null) |     (null) |  (null) |   Test | 01/03/2001 |      52 |

答案 1 :(得分:0)

手工或程序。大多数人习惯于以线性方式显示输出(默认)。如果有人要求您这样做,您可以告诉他们这不是应用程序的工作方式。您可以将结果集导出到csv,然后将其导入Excel之类的内容并手动重新格式化,或使用ASP.net或PHP等服务器端语言将结果格式化为表格。

解析输出时,可以根据当前值检查最后一个var Name。如果它们不同则添加一列。编写脚本仍然很棘手,因为它们很可能会按顺序从数据库中出来。所以你会有一个像test,test,test,foo,foo这样的序列,这意味着你需要创建一个多维数组来组织数据以获得列数。然后根据具有计数行名称的计数器设置表格,然后计算下面的数据。

我不确定您熟悉哪些应用程序,因此在PHP中,输出在多维数组中看起来像这样。

row [1]['name']=test
row [1][test][1]['date'] = 1/1/2001

这更像是一种视觉输出。数据库旨在保存数据并以直观的方式返回。