[SQL] 統計年度各業務銷售金額 - 使用 PIVOT、Case ... When

摘要:[SQL] 統計年度各業務銷售金額 - 使用 PIVOT、Case ... When

 

其實本篇是「多筆資料合併為一筆」的延續,上次說到可以利用「FOR XML PATH('')」及 SubQuery 來串起相關連的資料,並使其列為同一行(ROW),這樣的作法有時是為了串接(cascade)相關連的資料,但更常用於統計週、月、季、年的資料彙整。

本文以 Northwind 範例資料庫為例,說明如何統計每一年度每一個業務員的銷售金額。

使用到顧客「Employees」、訂單「Orders」、訂單明細「Order Details」三個資料表

SELECT [EmployeeID],[LastName] FROM [Northwind].[dbo].[Employees] 

 

SELECT [OrderID],[EmployeeID],[OrderDate] FROM[Northwind].[dbo].[Orders]

SELECT [OrderID],[UnitPrice],[Quantity] FROM [Northwind].[dbo].[Order Details]

-------分隔線 ----------------------------------------------

利用最基本 Join 方式串起3個資料表

 

SELECT e.EmployeeID, e.LastName, convert(varchar(10),o.OrderDate,101) AS orderdate ,(od.Quantity *od.UnitPrice) sale 
FROM dbo.Employees e
inner join orders o on o.EmployeeID = e.EmployeeID
inner join [Order Details] od on od.OrderID = o.OrderID

 

經過這樣的 Join 之後就可以得到銷售員與訂單之間的關聯,並得到該張訂單的金額。所以想要知道某一位銷售員在哪一年的銷售總額是多少,只要再加上簡單的 where 條件跟彙總函數 Sum ,就可以得到。

但是想要一次計算完,就要用一些方法來處理

方法一:利用 Case 欄位 where 條件 then 成立動作 else 不成立動作 end;

 

;
with emp (eid,ename, orderdate,  sale)
as
(
select e.EmployeeID, e.LastName, convert(varchar(10),o.OrderDate,101) as orderdate, (od.Quantity *od.UnitPrice) sale fromdbo.Employees e
inner join orders o on o.EmployeeID = e.EmployeeID
inner join [Order Details] od on od.OrderID = o.OrderID
)
select eid ,ename ,SUM(sale) 'Total',
case YEAR(orderdate) when 1996 then SUM(sale) else 0 end as '1996',
case YEAR(orderdate) when 1997 then SUM(sale) else 0 end as '1997',
case YEAR(orderdate) when 1998 then SUM(sale) else 0 end as '1998'
from emp
group by eid ,ename,YEAR(orderdate)
order by 1

得到下列的結果,顯然不夠好,怎麼會同一個人變成3行呢?

換個方法試試

;
with emp(ename, orderdate,  sale)
as
(
select e.LastName, YEAR(convert(varchar(10),o.OrderDate,101)) as orderdate, (od.Quantity *od.UnitPrice) sale from dbo.Employees e
inner join orders o on o.EmployeeID = e.EmployeeID
inner join [Order Details] od on od.OrderID = o.OrderID
)
select * from emp
pivot
(
sum(sale) for orderdate in ([1996],[1997],[1998])
) as pivot1

ㄝ ~搞定了哩,這不就是我要的「統計每一年度每一個業務員的銷售金額」嗎!

是的!這用了一個 SQL2005 之後提供的功能 PIVOT,他可以提供類似 EXCEL 的轉置函數,將一堆一維的資料轉換成二維的統計結果。

MSDN說明:「LINK」他的格式是

 

SELECT <非樞紐資料行>,
    [第一個樞紐資料行] AS <資料行名稱>,
    [第二個樞紐資料行] AS <資料行名稱>,
    ...
    [最後一個樞紐資料行] AS <資料行名稱>
FROM
    (<產生資料的SELECT 查詢>)
    AS <來源查詢的別名>
PIVOT
(
    <彙總函式>(<要彙總的資料行>)
FOR
[<包含將變成資料行標頭之值的資料行>]
    IN ( [第一個樞紐資料行], [第二個樞紐資料行],
    ... [最後一個樞紐資料行])
) AS <樞紐分析表的別名>
<選擇性的ORDER BY 子句>;

我來用 MSDN 方式重寫一下我上面的 T-SQL,不過格式變動一下

select  *  from
(
select 
e.LastName ,
YEAR(convert(varchar(10),o.OrderDate,101)) as orderdate ,(od.Quantity *od.UnitPrice) sale from dbo.Employees e
inner join orders o on o.EmployeeID = e.EmployeeID
inner join [Order Details] od on od.OrderID = o.OrderID ) as emp
pivot
(
sum(sale) for orderdate in ([1996],[1997],[1998])
) as pivot1

當然你也可以仿照 MSDN 格式寫出你需要的彙總結果。

~  End

QQ:難道真的無法用 Case ... when 來做嗎?
A:當然可以!

 

;
with emp(ename,yr,sale)
as
(
Select e.LastName,YEAR(o.OrderDate)  ,SUM(od.Quantity*od.UnitPrice)
From Employees e
      inner join Orders o on o.EmployeeID = e.EmployeeID 
      inner join [Order Details] od on o.OrderID = od.OrderID 
Group By  e.LastName, YEAR(o.OrderDate)
)
select  ename,
sum(case yr when 1996 then sale else 0 end) as '1996',
sum(case yr when 1997 then sale else 0 end) as '1997',
sum(case yr when 1998 then sale else 0 end) as '1998'
  from emp
Group By ename 

QQ2:或許你會問~為何我無法在 Northwind 執行你的 T-SQL 呢?
AA2:那是因為 Northwind 是很早期的範例資料庫,其Compatibility Level 在你安裝/還原時預設為 2000 ,請把它改為 2005 或 2008 才能使用 PIVOT ,因為這個指令在2005之後才支援。