我想动态生成状态列。
有三张表,资产、资产类型、资产状态
表:资产 资产 ID 整数 资产标签 varchar(25) 资产类型整数 资产状态整数 表:资产类型 id 整数 typename varchar(20)(例如:台式机、笔记本电脑、服务器等) 表:资产状态 id 整数 statusname varchar(20)(例如:已部署、库存、已发货等)
期望的结果:
AssetType Total Deployed Inventory Shipped ...
-----------------------------------------------------------
Desktop 100 75 20 5 ...
Laptop 75 56 19 1 ...
Server 60 50 10 0 ...
一些数据:
资产表: 1,hol1234,1,1 2,hol1233,1,2 3,hol3421,2,3 4,svr1234,3,1 资产类型表: 1、台式机 2、笔记本电脑 3、服务器 资产状态表: 1、已部署 2、库存 3、发货
这种类型的转换称为枢轴。您没有指定您使用的数据库,所以我将提供 SQL Server 和 MySQL 的答案。
SQL Server: 如果您使用的是 SQL Server 2005+,您可以实现
PIVOT
功能。
如果您想要将已知数量的值转换为列,那么您可以对查询进行硬编码。
select typename, total, Deployed, Inventory, shipped
from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
pivot
(
count(statusname)
for statusname in (Deployed, Inventory, shipped)
) piv;
请参阅 SQL Fiddle 演示。
但是,如果您有未知数量的
status
值,那么您将需要使用动态 sql 在运行时生成列列表。
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT distinct ',' + QUOTENAME(statusname)
from assetstatus
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'SELECT typename, total,' + @cols + ' from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) x
pivot
(
count(statusname)
for statusname in (' + @cols + ')
) p '
execute(@query)
这也可以使用带有 case 表达式的聚合函数来编写:
select typename,
total,
sum(case when statusname ='Deployed' then 1 else 0 end) Deployed,
sum(case when statusname ='Inventory' then 1 else 0 end) Inventory,
sum(case when statusname ='Shipped' then 1 else 0 end) Shipped
from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total
MySQL: 该数据库没有 pivot 函数,因此您必须使用聚合函数和
CASE
表达式。它也没有窗口函数,因此您必须将查询稍微更改为以下内容:
select typename,
total,
sum(case when statusname ='Deployed' then 1 else 0 end) Deployed,
sum(case when statusname ='Inventory' then 1 else 0 end) Inventory,
sum(case when statusname ='Shipped' then 1 else 0 end) Shipped
from
(
select t.typename,
(select count(*)
from assets a1
where a1.assettype = t.id
group by a1.assettype) total,
s.statusname
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total;
那么如果你需要MySQL中的动态解决方案,你将不得不使用准备好的语句来生成要执行的sql字符串:
SET @sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN statusname = ''',
statusname,
''' THEN 1 else 0 END) AS `',
statusname, '`'
)
) INTO @sql
FROM assetstatus;
SET @sql
= CONCAT('SELECT typename,
total, ', @sql, '
from
(
select t.typename,
(select count(*)
from assets a1
where a1.assettype = t.id
group by a1.assettype) total,
s.statusname
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
请参阅 SQL Fiddle 演示。
两个数据库中的所有查询的结果都是相同的:
| TYPENAME | TOTAL | DEPLOYED | INVENTORY | SHIPPED |
-----------------------------------------------------
| Desktop | 2 | 1 | 1 | 0 |
| Laptop | 1 | 0 | 0 | 1 |
| Server | 1 | 1 | 0 | 0 |
使用不兼容数据透视表的 DBMS(绝对数据库),使用此 SQL 交叉表等效语句更加成功:
SELECT
sub.TypeName
, SUM(sub.[Count]) AS "Total"
, SUM(CASE WHEN AssetStatus='1' THEN sub.[Count] ELSE 0 END) AS "Deployed"
, SUM(CASE WHEN AssetStatus='2' THEN sub.[Count] ELSE 0 END) AS "Inventory"
, SUM(CASE WHEN AssetStatus='3' THEN sub.[Count] ELSE 0 END) AS "Shipped"
FROM
(
SELECT
t.TypeName
, AssetStatus
, COUNT(AssetID) AS "Count"
FROM
Assets
JOIN AssetTypes t ON t.ID = AssetType
JOIN AssetStatus s ON s.ID = AssetStatus
GROUP BY t.TypeName, AssetStatus, s.StatusName
) sub
GROUP BY sub.TypeName
;
当我意识到这段代码(上面)不适用于 MySQL 时,我调整了我的代码,如下所示,它在 MySQL 中的执行效果与在我当前的绝对数据库中的执行效果一样好。原因是特定的 NULL 处理避免了 dBase、Paradox 以及 Absolute Database 慷慨地接受主流数据库不接受的 COUNT(NULL) = 0 的陷阱。 所以相信这在大多数数据库中都能很好地执行(处理 CASE ..)这是我改编的代码:
SELECT
sub.TypeName
, SUM(sub.AssetCase) AS "Total"
, SUM(CASE WHEN sub.StatusName = 'Deployed' THEN sub.AssetCase ELSE 0 END) AS "Deployed"
, SUM(CASE WHEN sub.StatusName = 'Inventory' THEN sub.AssetCase ELSE 0 END) AS "Inventory"
, SUM(CASE WHEN sub.StatusName = 'Shipped' THEN sub.AssetCase ELSE 0 END) AS "Shipped"
FROM
(
SELECT
c.TypeName
, c.StatusName
, CASE WHEN a.AssetID IS NULL THEN 0 ELSE 1 END AS "AssetCase"
FROM
(
SELECT
t.ID AS tID
, t.TypeName
, s.ID AS sID
, s.StatusName
FROM
AssetTypes t, AssetStatus s
) c
LEFT JOIN Assets a
ON a.AssetType = c.tID AND a.AssetStatus = c.sID
) sub
GROUP BY
sub.TypeName
;
致以诚挚的问候 尼尔斯·卡纳布