web-dev-qa-db-fra.com

J'ai besoin de savoir comment créer une requête d'analyse croisée

J'ai besoin d'aide pour créer les résultats ci-dessous. J'ai pensé à un pivot SQL mais je ne sais pas comment l'utiliser. Regardé quelques exemples et ne peut pas trouver une solution. Toute autre idée sur la manière d’y parvenir est également la bienvenue. Les colonnes d'état doivent être générées dynamiquement.

Avoir trois tables, assets, assettypes, assetstatus

 Table: assets 
 Assetid int
 Assettag varchar (25) 
 Assettype int 
 Assetstatus int 
.. Tableau. ) (ex: bureau, ordinateur portable, serveur, etc.) 

 Table: assetstatus
id int 
 statusname varchar (20) (ex: déployé, inventaire, expédié, etc.) 

Les résultats souhaités:

 AssetType Total de l'inventaire déployé expédié ...
---------------------------------------- -----------------------
 Bureau 100 75 20 5 ...
 Ordinateur portable 75 56 19 1 ...
 Serveur 60 50 10 0 ...

Quelques données:

 tableau des actifs: 
 1, hol1234,1,1 
 2, hol1233,1,2 
 3, hol3421,2,3 
 4, svr1234,3,1 
. __ assertypes table: 
 1, Desktop 
 2, Laptop 
 3, Server 

 assetstatus table: 
 1, Deployed 
 2, Inventory 
 3, Expédié 
23
Sam

Ce type de transformation s'appelle un pivot. Vous n'avez pas spécifié la base de données que vous utilisez, je vais donc vous fournir une réponse pour SQL Server et MySQL.


SQL Server: Si vous utilisez SQL Server 2005+, vous pouvez implémenter la fonction PIVOT.

Si vous souhaitez convertir un nombre connu de valeurs en colonnes, vous pouvez coder en dur la requête.

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;

Voir SQL Fiddle avec Demo .

Mais si vous avez un nombre inconnu de valeurs status, vous devrez utiliser SQL dynamique pour générer la liste des colonnes au moment de l'exécution. 

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)

Voir SQL Fiddle avec Demo

Cela peut également être écrit en utilisant une fonction d'agrégat avec une expression de cas:

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

Voir SQL Fiddle avec Demo


MySQL: Cette base de données n'a pas de fonction pivot, vous devrez donc utiliser la fonction d'agrégat et une expression CASE. Il n’a pas non plus de fonctions de fenêtrage, vous devrez donc modifier légèrement la requête comme suit:

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;

Voir SQL Fiddle avec Demo

Ensuite, si vous avez besoin d’une solution dynamique dans MySQL, vous devrez utiliser une instruction préparée pour générer la chaîne SQL à exécuter:

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;

Voir SQL Fiddle avec Demo .

Le résultat est le même pour toutes les requêtes des deux bases de données:

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

En utilisant un SGBD non conforme au pivot (base de données absolue), j'ai mieux réussi à utiliser cette instruction équivalente à plusieurs tableaux croisés 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
;

Quand j'ai réalisé que ce code (ci-dessus) ne fonctionnait pas avec MySQL, j'ai adapté mon code comme ci-dessous, aussi bien dans MySQL que dans ma base de données Absolute actuelle. La raison en est que la gestion de la valeur NULL spécifique évite les pièges de dBase, de Paradox ainsi que de la base de données absolue acceptant généreusement COUNT (NULL) = 0 non accepté dans les bases de données classiques . ) c'est mon code adapté:

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
;

Cordialement Niels Knabe

0
Niels Knabe