web-dev-qa-db-ja.com

クロス集計クエリの作成方法を知る必要があります

以下の結果を作成するのに助けが必要です。 SQLピボットについて考えましたが、使用方法がわかりません。いくつかの例を見て、解決策を思い付くことができません。これを達成する方法に関する他のアイデアも歓迎します。ステータス列は動的に生成される必要があります。

3つのテーブル、assets、assettypes、assetstatusがあります

テーブル:資産
 assetid int 
 assettag varchar(25)
 assettype int 
 assetstatus int 
 
 Table :assettypes 
 id int 
 typename varchar(20)(例:デスクトップ、ラップトップ、サーバーなど)
 
テーブル:assetstatus 
 id int 
 statusname varchar(20)(例:展開済み、インベントリ、出荷など)

望ましい結果:

 AssetTypeの展開されたインベントリの合計出荷数... 
 -------------------------------- --------------------------- 
デスクトップ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 
 
 assettypesテーブル:
 1、Desktop 
 2、Laptop 
 3、Server 
 
 assetstatusテーブル:
 1、Deployed 
 2、Inventory 
 3、Shipped 
24
Sam

このタイプの変換はピボットと呼ばれます。使用しているデータベースを指定しなかったため、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 |
45
Taryn

ピボットに準拠していない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で動作しないことに気づいたので、現在のAbsolute Databaseと同様にMySQLでも同様に実行できるようにコードを修正しました。その理由は、dBase、Paradox、および主流データベースでは受け入れられないCOUNT(NULL)= 0を寛大に受け入れるAbsolute Databaseの落とし穴を回避する特定のNULL処理です。だから、これはほとんどのデータベースでうまく実行されると信じて(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
;

よろしくNiels Knabe

0
Niels Knabe