web-dev-qa-db-ja.com

同じテーブルの異なる列の数を取得する方法

テーブル#01 Status

StatusID    Status
-----------------------
 1          Opened
 2          Closed
 3          ReOpened
 4          Pending

テーブル#02 Claims

ClaimID     CompanyName StatusID
--------------------------------------
1               ABC     1
2               ABC     1
3               ABC     2
4               ABC     4
5               XYZ     1
6               XYZ     1

期待される結果:

CompanyName TotalOpenClaims TotalClosedClaims TotalReOpenedClaims TotalPendingClaims
--------------------------------------------------------------------------------
ABC                 2           1                      0               1
XYZ                 2           0                      0               0

期待どおりの結果を得るには、どのようにクエリを記述する必要がありますか?

15
Kaishu

SUM()CASEステートメントを使用するのが最も簡単です。

select CompanyName, 
sum(case when StatusID=1 then 1 else 0 end) as TotalOpenClaims,
sum(case when StatusID=2 then 1 else 0 end) as TotalClosedClaims,
sum(case when StatusID=3 then 1 else 0 end) as TotalReOpenedClaims,
sum(case when StatusID=4 then 1 else 0 end) as TotalPendingClaims
from Claims
group by CompanyName;
27
Philᵀᴹ

これは典型的なピボット変換であり、条件付き集計は Philによって提案 がそれを実装する古き良き方法です。

同じ結果を得る、PIVOT句を使用するより現代的な構文もあります。

SELECT
  CompanyName,
  TotalOpenClaims     = [1],
  TotalClosedClaims   = [2],
  TotalReOpenedClaims = [3],
  TotalPendingClaims  = [4]
FROM
  dbo.Claims
  PIVOT
  (
    COUNT(ClaimID)
    FOR StatusID IN ([1], [2], [3], [4])
  ) AS p
;

内部的には、この間違いなくシンプルに見える構文は、PhilのGROUP BYクエリと同等です。より正確には、このバリエーションと同等です。

SELECT
  CompanyName,
  TotalOpenClaims     = COUNT(CASE WHEN StatusID = 1 THEN ClaimID END),
  TotalClosedClaims   = COUNT(CASE WHEN StatusID = 2 THEN ClaimID END),
  TotalReOpenedClaims = COUNT(CASE WHEN StatusID = 3 THEN ClaimID END),
  TotalPendingClaims  = COUNT(CASE WHEN StatusID = 4 THEN ClaimID END)
FROM
  dbo.Claims
GROUP BY
  CompanyName
;

したがって、PIVOTクエリは本質的に、暗黙のGROUP BYクエリです。

ただし、PIVOTクエリは、条件付き集計を使用した明示的なGROUP BYクエリよりも処理が面倒です。 PIVOTを使用しているときは、常に次のことを覚えておく必要があります。

  • ピボットされるデータセットのすべての列(この場合は[Claims))。PIVOT句で明示的に言及されていないはGROUP BY列です。

Claimsが例に示されている3つの列のみで構成されている場合、明らかにCompanyNameがPIVOTで明示的に言及されていない唯一の列であり、最終的には暗黙のGROUP BYの唯一の基準。

ただし、Claimsに他の列(たとえば、ClaimDate)がある場合、それらは暗黙的に追加のGROUP BY列として使用されます。つまり、クエリは基本的に

GROUP BY CompanyName, ClaimDate, ... /* whatever other columns there are*/`

結果はおそらくあなたが望むものではないでしょう。

ただし、これは簡単に修正できます。無関係な列を暗黙的なグループ化に参加させないようにするには、派生テーブルを使用するだけで、結果に必要な列のみを選択できますが、クエリの見栄えが悪くなります。

SELECT
  CompanyName,
  TotalOpenClaims     = [1],
  TotalClosedClaims   = [2],
  TotalReOpenedClaims = [3],
  TotalPendingClaims  = [4]
FROM
  (SELECT ClaimID, CompanyName, StatusID FROM dbo.Claims) AS derived
  PIVOT
  (
    COUNT(ClaimID)
    FOR StatusID IN ([1], [2], [3], [4])
  ) AS p
;

それでも、Claimsがすでに派生テーブルである場合は、ネストの別のレベルを追加する必要はありません。現在の派生テーブルで、出力の生成に必要な列のみを選択していることを確認してください。

PIVOTの詳細については、次のマニュアルを参照してください。

16
Andriy M

確かに私の経験はほとんどMySQLでの経験であり、SQL Serverにあまり時間を費やしていません。次のクエリが機能しない場合、私は非常に驚きます:

SELECT 
  CompanyName, 
  status, 
  COUNT(status) AS 'Total Claims' 
FROM Claim AS c 
  JOIN Status AS s ON c.statusId = s.statusId 
GROUP BY 
  CompanyName, 
  status;

これは、希望する形式で出力を提供するわけではありませんが、ゼロのケースを除いて、必要なすべての情報を提供します。これは、クエリ内でCASEステートメントを処理するよりもずっと簡単だと感じます。クエリ内でCASEステートメントをフォーマットに使用するだけの場合は、これは特に悪い考えのように思えます。

1
Harageth