聚合函數,聽起來高深莫測,但是其實很簡單,只要懂國小數學就懂聚合函數,我們從字面上的意義來看,聚合函數就是把多筆資料聚合成一筆資料,例如把多筆的資料聚合成一筆總筆數的資料(COUNT),把多筆的金額然後聚合成一筆總金額(SUM)、把多筆的金額聚合選出一筆最大的金額(MAX)等等。
告訴聚合函數要用哪一個欄位來聚合,例如你想知道有幾筆的訂單,如果這個資料表的設計是每一筆資料就是一筆訂單的話,那麼你隨意放哪個欄位都可以算出有幾筆的訂單,但是如果是想知道訂單金額最高的是哪一筆,那麼就要跟聚合函數說你要找的其實是訂單金額,這樣才會找出這一筆的金額。
id | 訂單金額 |
---|---|
1 | 100 |
2 | 300 |
3 | 200 |
SELECT COUNT(*) FROM orders;
SELECT MAX(訂單金額) FROM orders;
只有聚合函數,能夠做的事情還有限,它還需要另一個好搭檔GROUP BY,以SUM聚合函數為例,如果對整個資料表使用SUM,那麼就會回傳所有客戶加起來的總金額,但是我們希望的是計算出每個客戶的總金額,加上 GROUP BY 讓資料庫知道 SUM 聚合函數的範圍。
id | 金額 | 客戶 |
---|---|---|
1 | 100 | 小圖 |
2 | 300 | 小烏 |
3 | 100 | 小圖 |
4 | 500 | 小卡 |
5 | 100 | 小卡 |
SELECT 客戶, SUM(金額)
FROM orders
GROUP BY 客戶;
計算平均值的聚合函數會有除不盡的問題,我們可以使用快樂夥伴(?)四捨五入來讓資料取出我們想要的值,例如我們希望計算的結果只取到小數點第二位。
SELECT ROUND(42.4382,2) -- 輸出結果為42.44
這是一個訂飲料的小範例,包含顧客資料表、訂單資料表、訂單項目資料表、付款方式資料表以及品項資料表。
資料表會使用主鍵與外鍵進行關聯,不過這個範例主要只是要示範聚合函數所使用,並不會對於之前Day 18 所提的外鍵限制進行太多的探討。
CREATE TABLE customers(
id SERIAL PRIMARY KEY,
name VARCHAR(300) NOT NULL,
phone VARCHAR(300) NOT NULL
);
CREATE TABLE products(
id SERIAL PRIMARY KEY,
name VARCHAR(300) NOT NULL,
price INT
);
CREATE TABLE payments(
id SERIAL PRIMARY KEY,
name VARCHAR(300) NOT NULL
);
CREATE TABLE orders(
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
payment_id INT REFERENCES payments(id),
note VARCHAR(300)
);
CREATE TABLE order_items(
id SERIAL PRIMARY KEY,
order_id INT REFERENCES orders(id),
product_id INT REFERENCES products(id),
quantity INT
);
寫入orders有一筆資料是插入NULL,因為這個欄位預設是可以不寫入的,不過因為插入資料的時候有指定要寫note欄位,所以要寫入NULL。
INSERT INTO customers (name,phone)
VALUES
('陳冬瓜','0999099099'),
('蘇芋頭','0988777666'),
('張香腸','0977789789'),
('廖五花','0966566566');
INSERT INTO products (name,price)
VALUES
('紅茶', 35),
('蓮藕茶', 40),
('枸杞茶', 40),
('苦茶', 50);
INSERT INTO payments (name)
VALUES ('現金'),('行動支付');
INSERT INTO orders (customer_id,payment_id,note)
VALUES
(1,1,'不要吸管'),
(2,2,NULL),
(3,1,'不同的飲料分開放'),
(4,2,'我有故事老闆有苦茶嗎?');
INSERT INTO order_items (order_id,product_id,quantity)
VALUES
(1,1,3),
(1,2,2),
(2,3,1),
(3,1,3),
(3,2,3),
(3,3,4),
(4,4,6);
為了簡化聚合函數範例的簡潔度,建立一個VIEW來封裝SQL的複雜度。
CREATE VIEW order_detail
AS SELECT order_id AS 訂單編號,
c.name AS 訂購人,
c.phone AS 電話,
pa.name AS 付款方式,
p.name AS 飲料名稱,
p.price AS 飲料金額,
oi.quantity AS 飲料數量,
p.price * oi.quantity AS 小計
FROM orders AS o
JOIN order_items AS oi ON oi.order_id = o.id
JOIN products AS p ON oi.product_id = p.id
JOIN customers AS c ON customer_id = c.id
JOIN payments AS pa ON payment_id = pa.id;
這個範例要計算平均值是有一點傷腦筋,因為架構上必需使用尚未學習到的子查詢或視窗函數,不過因為這個系列尚未提及,所以我選擇使用在Day 14所提過的VIEW來當作替代方案,本文結尾也會提供視窗函數的使用方法。
CREATE VIEW order_total
AS SELECT 訂單編號, SUM(小計) AS 總計
FROM order_detail
GROUP BY 訂單編號
ORDER BY 訂單編號
我們想知道,總共有幾筆的訂單。
SELECT COUNT(*) FROM orders;
SELECT MIN(price) AS min_price
FROM products;
SELECT MAX(price) AS max_price
FROM products;
SELECT 訂單編號,SUM(小計) FROM order_detail
GROUP BY 訂單編號
ORDER BY 訂單編號;
SELECT SUM(總計) AS 總收入, ROUND(AVG(總計),2) AS 客單平均額
FROM order_total;
Day 12 學過使用where來對資料做過濾,聚合過的資料則必需用having來進行過濾,例如老闆只想看到超過新台幣100元的訂單資料,由於訂單總金額的資料是從SUM(小計)所產生的,因此就需要使用HAVING來進行過濾啦~
SELECT 訂單編號, SUM(小計) AS 總計
FROM order_detail
GROUP BY 訂單編號
HAVING SUM(小計) > 100
ORDER BY 訂單編號;
我們有時候會很想看原始資料跟統計值放在同一個表格內,不過統計值通常是透過函數所產生出來的,那麼我們目前所學到的內容是無法跟原始資料一起顯示的,別氣餒,我們即將使用一個超級好用的視窗函數來辦到這件事情,這個主題建議延伸閱讀這篇精彩的文章 十分鐘內快速上手與使用 Window function|SQL 教學
id | 訂單小計 | 訂單總計 |
---|---|---|
1 | 100 | 200 |
1 | 100 | 200 |
資料表最後加上一個欄位,放整個資料表的小計值總和。
SELECT *, SUM(小計) OVER()
FROM order_detail
資料表最後加上一個欄位,放以訂單編號進行分割的小計值總和。
SELECT *, SUM(小計) OVER(PARTITION BY 訂單編號)
FROM order_detail
資料表最後加上一個欄位,放以訂單編號進行分割並且依小計值累計小計值總和。
SELECT *, SUM(小計) OVER(PARTITION BY 訂單編號 ORDER BY 小計) AS 累計
FROM order_detail