Preciso de saber como criar uma consulta crosstab

Preciso de Ajuda para criar os resultados abaixo. Pensei num pivô sql, mas não sei como usá-lo. Analisámos alguns exemplos e não conseguimos encontrar uma solução. Quaisquer outras ideias sobre como conseguir isso também são bem-vindas. As colunas de Estado devem ser geradas dinamicamente.

tem três quadros, activos, assettypes, assetstatus

Table: assets
assetid     int
assettag    varchar(25)
assettype   int
assetstatus int

Table: assettypes
id         int
typename   varchar(20)  (ex: Desktop, Laptop, Server, etc.)

Table: assetstatus
id         int
statusname varchar(20)  (ex: Deployed, Inventory, Shipped, etc.)

resultados desejados:

AssetType     Total   Deployed   Inventory  Shipped     ...
-----------------------------------------------------------
Desktop         100       75        20          5       ...
Laptop           75       56        19          1       ...
Server           60       50        10          0       ...

Alguns Dados:

assets table:
1,hol1234,1,1
2,hol1233,1,2
3,hol3421,2,3
4,svr1234,3,1

assettypes table:
1,Desktop
2,Laptop
3,Server

assetstatus table:
1,Deployed
2,Inventory
3,Shipped
Author: Sam, 2013-03-30

2 answers

Este tipo de transformação é chamado de pivô. Você não especificou que banco de dados você está usando então eu vou fornecer uma resposta para o servidor SQL e MySQL.


SQL Server: Se estiver a usar o SQL Server 2005 + pode implementar a função PIVOT.

Se você tem um número conhecido de valores que deseja converter para colunas, então você pode programar a consulta.

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;

Ver Violino SQL com demonstração .

Mas se tiver um desconhecido número de valores status, então terá de usar o SQL dinâmico para gerar a lista de colunas no tempo de execução.

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)

Ver Violino SQL com demonstração

Isto também pode ser escrito usando uma função agregada com uma expressão de Caso:

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

Ver Violino SQL com demonstração


MySQL: Esta base de dados não tem uma função pivot por isso terá de usar a função agregada e uma expressãoCASE. Também não tem funções de janelas, por isso terá de alterar ligeiramente a consulta para o seguinte:

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;

Ver Violino SQL com demonstração

Então, se precisar de uma solução dinâmica no MySQL, terá de usar uma declaração preparada para gerar o texto sql a executar:

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;

Ver Violino SQL com demonstração .

O resultado é o mesmo para todas as consultas em ambas as bases de Dados:

| TYPENAME | TOTAL | DEPLOYED | INVENTORY | SHIPPED |
-----------------------------------------------------
|  Desktop |     2 |        1 |         1 |       0 |
|   Laptop |     1 |        0 |         0 |       1 |
|   Server |     1 |        1 |         0 |       0 |
 42
Author: Taryn, 2013-03-30 03:17:42

Usando um DBMS não compatível com pivot (base de dados Absolute) I foi mais bem sucedido usando esta declaração equivalente SQL Cross-tab:

SELECT
  TypeName
, SUM([Count]) AS "Total"
, SUM(CASE WHEN AssetStatus='1' THEN [Count] ELSE 0 END) Deployed
, SUM(CASE WHEN AssetStatus='2' THEN [Count] ELSE 0 END) Inventory
, SUM(CASE WHEN AssetStatus='3' THEN [Count] ELSE 0 END) 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
 )
GROUP BY TypeName
;
 0
Author: Niels Knabe, 2018-10-06 12:37:09