首先,我强烈建议您重新设计当前的结构。您当前的结构没有标准化,维护起来非常困难,特别是如果您允许用户向您的表中添加新列。在我解释如何使用当前结构获得结果之前,我将演示如果您重新设计表格,这将变得多么容易。
新表格设计:这是关于如何将表格重写为更灵活的工作模型的建议。如果您有以下表格和样本数据:
-- 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);
这种设计更加灵活,因为您可以轻松添加新指标进行跟踪,而无需向表格添加新列。这甚至可以扩展为为捕获值的每一天添加一个日期/时间列。您可以使用以下方法轻松加入他们:
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;
见SQL Fiddle with Demo。这将为您提供每个部门的所有指标和相关值,然后可以使用数据透视表将其转换为列:
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;
见SQL Fiddle with Demo。如果您有未知的指标类型,上述查询可以轻松转换为动态 SQL。
使用现有表:您可以通过首先取消透视列然后应用 PIVOT 函数来获得结果。我建议使用CROSS APPLY 取消透视数据,以便您可以将多列成对转换为行。使用CROSS APPLY 进行反透视的语法是:
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)
见SQL Fiddle with Demo。这会将您的数据转换为以下格式:
| 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 |
一旦数据采用这种格式,您就可以轻松应用 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 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;
见SQL Fiddle with Demo。
由于您当前的表结构,如果您有未知值,这会变得更加复杂,但以下动态 SQL 脚本应该可以为您提供所需的结果:
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;
见SQL Fiddle with Demo。所有版本都得到结果:
| 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 |