【问题标题】:I need to know how to create a crosstab query我需要知道如何创建交叉表查询
【发布时间】:2018-10-09 03:21:23
【问题描述】:

我需要帮助来创建以下结果。我想到了一个 sql pivot 但我不知道如何使用它。看了几个例子,想不出解决办法。也欢迎任何其他关于如何实现这一点的想法。状态列必须是动态生成的。

拥有三个表,assets,assettypes,assetstatus

表:资产 资产ID 资产标签 varchar(25) 资产类型 int 资产状态整数 表:资产类型 id int typename varchar(20)(例如:台式机、笔记本电脑、服务器等) 表:资产状况 id int statusname varchar(20)(例如:已部署、库存、已发货等)

期望的结果:

AssetType Total Deployed Inventory Shiped ... -------------------------------------------------- --------- 台式机 100 75 20 5 ... 笔记本电脑 75 56 19 1 ... 服务器 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、发货

【问题讨论】:

  • 目前这对我来说没有多大意义你有一些例子说明你的表格中有什么,一些实际的行......表格之间必须有一些共同点将它们链接在一起的参考点...如果您提供该详细信息,我将尝试一下。
  • 你用的是什么关系型数据库?
  • assets.assettype 在assettypes.id 上,asset.assetstatus 在assetstatus 上
  • 部署的 (75, 56, 50) 值从何而来?它们不会出现在您的数据中。
  • @EricS 这些是每种类型的总计数,但数字与样本数据不匹配。

标签: sql pivot crosstab


【解决方案1】:

这种类型的转换称为枢轴。您没有指定您使用的数据库,所以我将提供 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 with Demo

但是如果您有未知数量的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)

SQL Fiddle with Demo

这也可以使用带有 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

SQL Fiddle with Demo


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;

SQL Fiddle with Demo

那么如果你需要在 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 with Demo

两个数据库中所有查询的结果都是相同的:

| TYPENAME | TOTAL | DEPLOYED | INVENTORY | SHIPPED |
-----------------------------------------------------
|  Desktop |     2 |        1 |         1 |       0 |
|   Laptop |     1 |        0 |         0 |       1 |
|   Server |     1 |        1 |         0 |       0 |

【讨论】:

  • 已知编号的 SQL 服务器运行良好。使用未知数动态 sql 我得到几个错误: Msg 1038, Level 15, State 4, Line 15 对象或列名丢失或为空。对于 SELECT INTO 语句,验证每一列都有一个名称。对于其他语句,请查找空别名。不允许使用定义为 "" 或 [] 的别名。将别名更改为有效名称。
  • @Sam 你能用你试图运行的代码编辑SQL Fiddle吗?将代码放在右侧面板中,然后执行sql。然后在此处的评论中发布链接。
  • 我创建表,执行代码,一切正常。当我将它应用到我的数据库时,我得到一个错误。字段名称正确。不知道为什么它不起作用。
  • 这是你的代码。工作正常,但不能在具有完全相同字段的实际数据库上。
  • 好的,我发现了问题,我的assetstatus 表有一条空白状态的记录,我将空白状态更改为“无状态”,现在一切正常。谢谢一百万。
【解决方案2】:

使用不符合数据透视表的 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
;

最好的问候 尼尔斯·克纳贝

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-08
    • 2018-10-02
    • 1970-01-01
    • 2015-02-10
    • 2019-03-02
    相关资源
    最近更新 更多