Convert multiple columns row values into multiple columns

冷暖自知 提交于 2019-12-24 11:36:02

问题


I've tried all possibilities around Pivot, Unpivot crosstab - you name it.

Struggle is that i've over worked myself with this. I am hoping it wi going to be a simple solution just that I cant see it.

I'm so sorry for not providing in DDL.

But essentially I have a table that looks like this.

DeptName    Metric1_Name        Metric1_Value    Metric2_Name        Metric2_Value    Metric3_Name        Metric3_Value    Metric4_Name        Metric4_Value

ABC         Sales Per Hour      200              Wins Per Hour       10               Leads per Hour      2                Losses per Hour     1 
ABC         Sales per Minute    20               Wins per Minute     1                Leads per minute    1                Losses per Minute   1
XYZ         Sales Per Hour      5000             Wins Per Hour       300              Leads per Hour      20               Losses per Hour     10 
XYZ         Sales per Minute    2000             Wins per Minute     100              Leads per minute    10               Losses per Minute   10

I hope to get a result like this

DeptName    Sales per Hour    Sales per minute    Wins Per Hour    Wins per Minute    Leads per Hour    Leads per minute    Losses per Hour    Losses per Minute

ABC         200               20                  10               1                  2                 1                   1                   1          
XYZ         5000              2000                300              100                20                10                  10                  10         

I'm using SQL-Server 2012. But i guess solutons can work on 2008 R2

Catches - 1)the number of columns named Metric1_name etc are unknown. Someone can add metric_36 and I will not know it for 13 days.

2) There are more than 1 field like DeptName which i believe is ok to leave out for this example. Once someone gives me direction how to solve for it I'll add them in.

I really appreciate you taking time to help me out.

Cheers


回答1:


First things first, I highly suggest that you redesign your current structure. Your current structure is not normalized and will be incredibly difficult to maintain especially if you allow users to add new columns to your table. Before I explain how to get the result with your current structure, I will demonstrate how much easier this would be if you redesign your tables.

New Table Design: This is a suggestion on how you can rewrite your table into a more flexible working model. If you have the following tables and sample data:

-- contains the names of each metric you need to track
CREATE TABLE metric
(
  [id] int, 
  [name] varchar(17)
);

INSERT INTO metric ([id], [name])
VALUES (1, 'Sales per Hour'), (2, 'Sales per Minute'),
    (3, 'Wins per Hour'), (4, 'Wins per Minute'),
    (5, 'Leads per Hour'), (6, 'Leads per Minute'),
    (7, 'Losses per Hour'), (8, 'Losses per Minute');

-- contains the details of your departments
CREATE TABLE Departments
(
  [id] int, 
  [name] varchar(3)
);

INSERT INTO Departments ([id], [name])
VALUES (1, 'ABC'), (2, 'XYZ');

-- associates the dept to each metric and the value
CREATE TABLE details
(
  [deptid] int, 
  [metricid] int, 
  [value] int
);

INSERT INTO details ([deptid], [metricid], [value])
VALUES
    (1, 1, 200), (1, 2, 20), (1, 3, 10),
    (1, 4, 1), (1, 5, 2), (1, 6, 1),
    (1, 7, 1), (1, 8, 1), (2, 1, 5000),
    (2, 2, 2000), (2, 3, 300), (2, 4, 100),
    (2, 5, 20), (2, 6, 10), (2, 7, 10),
    (2, 8, 10);

This design is more flexible because you can easily add new metrics to track without having to add a new column to a table. this can even be expanded to add a date/time column for each day the values are captured. You can easily join them using:

select d.name deptname, m.name, dt.value
from departments d
inner join details dt
  on d.id = dt.deptid
inner join metric m
  on dt.metricid = m.id;

See SQL Fiddle with Demo. This will give you all of the metrics for each department and the associated value which can then be converted to columns using pivot:

select deptname,
  [Sales per hour], [Sales per minute],
  [Wins per hour], [Wins per minute],
  [Leads per hour], [Leads per minute],
  [Losses per hour], [Losses per minute]
from
(
  select d.name deptname, m.name, dt.value
  from departments d
  inner join details dt
    on d.id = dt.deptid
  inner join metric m
    on dt.metricid = m.id
) src
pivot
(
  max(value)
  for name in ([Sales per hour], [Sales per minute],
               [Wins per hour], [Wins per minute],
               [Leads per hour], [Leads per minute],
               [Losses per hour], [Losses per minute])
) piv;

See SQL Fiddle with Demo. The above query could easily be converted to dynamic SQL if you have unknown metrics types.

With Existing Table: You can get the result by first unpivoting your columns and then applying the PIVOT function. I would suggest using CROSS APPLY to unpivot the data so you can convert the multiple columns into rows in pairs. The syntax to unpivot using CROSS APPLY is:

select deptname, name, value
from yourtable
cross apply
(
  values
    (Metric1_Name, Metric1_Value),
    (Metric2_Name, Metric2_Value),
    (Metric3_Name, Metric3_Value),
    (Metric4_Name, Metric4_Value)
) c (name, value)

See SQL Fiddle with Demo. This gets your data into the format:

| DEPTNAME |              NAME | VALUE |
|----------|-------------------|-------|
|      ABC |    Sales Per Hour |   200 |
|      ABC |     Wins Per Hour |    10 |
|      ABC |    Leads per Hour |     2 |
|      ABC |   Losses per Hour |     1 |
|      ABC |  Sales per Minute |    20 |
|      ABC |   Wins per Minute |     1 |
|      ABC |  Leads per minute |     1 |
|      ABC | Losses per Minute |     1 |

Once the data is in this format, you can easily apply the PIVOT function. The following will work if you have a limited number of values:

select deptname, 
  [Sales per hour], [Sales per minute],
  [Wins per hour], [Wins per minute],
  [Leads per hour], [Leads per minute],
  [Losses per hour], [Losses per minute]
from
(
  select deptname, name, value
  from yourtable
  cross apply
  (
    values
      (Metric1_Name, Metric1_Value),
      (Metric2_Name, Metric2_Value),
      (Metric3_Name, Metric3_Value),
      (Metric4_Name, Metric4_Value)
  ) c (name, value)
) d
pivot
(
  max(value)
  for name in ([Sales per hour], [Sales per minute],
               [Wins per hour], [Wins per minute],
               [Leads per hour], [Leads per minute],
               [Losses per hour], [Losses per minute])
) piv
order by deptname;

See SQL Fiddle with Demo.

Because of your current table structure this becomes much more complicated if you have unknown values but the following dynamic SQL script should get you the result that you need:

DECLARE @colsUnpivotList AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX),
    @colsPivot as  NVARCHAR(MAX),
    @q nvarchar(max)

declare @temp table
(
    name varchar(50),
    pos int
) ;

-- create the list of columns for the cross apply
select @colsUnpivotList 
  = stuff((select ', ('+quotename('Metric'+CAST(seq as varchar(2))+nm) 
                + ', '+quotename('Metric'+CAST(seq as varchar(2))+vl) +')'
            from
            (
                select distinct substring(C.COLUMN_NAME, 7, CHARINDEX('_', c.column_name)-7) seq
                from INFORMATION_SCHEMA.columns as C
                where C.TABLE_NAME = 'yourtable'
                     and C.COLUMN_NAME not in ('DeptName')
            ) s
            cross join
            (
                select '_Name', '_Value'
            ) c (nm, vl)
           for xml path('')), 1, 1, '')

-- create a sql string to get the list of values to be pivoted 
select @q = stuff((select 'union select '+c.COLUMN_NAME + ' nm, '+ cast(c.ordinal_position as varchar(10))+' pos from yourtable ' 
                    from INFORMATION_SCHEMA.columns as C
                    where C.TABLE_NAME = 'yourtable'
                        and C.COLUMN_NAME not in ('DeptName')
                        and C.COLUMN_NAME like ('%_Name')
                    for xml path('')), 1, 6, '')

insert into @temp execute(@q );

-- use the @temp table to get the list of values to pivot
select @colsPivot = STUFF((SELECT  ',' + quotename(name)
                    from @temp
                   group by name, pos
                   order by pos
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT deptname, ' + @colsPivot + ' 
            from 
            (
                select deptname, name, value
                from yourtable
                cross apply
                (
                  values
                  '+@colsUnpivotList +'
                ) c (name, value)
            ) x
            pivot 
            (
                max(value)
                for name in (' + @colsPivot + ')
            ) p '

execute sp_executesql @query;

See SQL Fiddle with Demo. All versions get the result:

| DEPTNAME | SALES PER HOUR | SALES PER MINUTE | WINS PER HOUR | WINS PER MINUTE | LEADS PER HOUR | LEADS PER MINUTE | LOSSES PER HOUR | LOSSES PER MINUTE |
|----------|----------------|------------------|---------------|-----------------|----------------|------------------|-----------------|-------------------|
|      ABC |            200 |               20 |            10 |               1 |              2 |                1 |               1 |                 1 |
|      XYZ |           5000 |             2000 |           300 |             100 |             20 |               10 |              10 |                10 |


来源:https://stackoverflow.com/questions/21554504/convert-multiple-columns-row-values-into-multiple-columns

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!