這天,小悅懶洋洋地步入辦公樓下的咖啡館,意外地與一位男子不期而遇。他顯然因前一晚的辛勤工作而略顯疲憊,卻仍選擇早到此地,尋找一絲寧靜與放鬆。他叫逸塵,身姿挺拔,衣著簡約而不失格調,晨光下更顯英俊不凡,吸引了周遭的目光。兩人彷彿心有靈犀,不約而同地走向各自的位置。
小悅手中輕握著新出爐的拿鐵,眼睛緊緊盯著手機上的工作郵件,心思全然沉浸在工作的海洋中,對前方即將發生的“小插曲”渾然未覺。而逸塵,正欲伸手取桌上的檔案,兩人的手在不經意間悄然相遇,伴隨著一陣輕微的碰撞,小悅手中的拿鐵微微傾斜,幾滴熱燙的咖啡瞬間在逸塵潔白的襯衫上綻放,如同從樹上不經意間灑落的晨露,雖美卻略顯突兀。
“哎呀,真的非常抱歉!”小悅連忙道歉,臉頰上泛起了紅暈,手忙腳亂地在包中搜尋紙巾,希望能為這突如其來的尷尬場面做些什麼。逸塵則以他特有的紳士風度,輕輕接過紙巾,自行處理起那片不速之客。
“沒關係,下次小心些便是。”逸塵的話語中雖帶有一絲不易察覺的責備,但更多的是溫柔與寬容。他皺眉的瞬間,非但沒有減少魅力,反而增添了幾分成熟與穩重。
小悅心中五味雜陳,既有對自己疏忽的懊惱,也有對逸塵那不經意間流露出的嚴厲與溫柔交織的複雜情感。她低聲細語:“我真的不是故意的。”這句話雖輕如蚊蚋,卻清晰地傳入了逸塵的耳中,兩人的心間彷彿被一股莫名的力量輕輕觸碰,營造出一種難以言喻的微妙氛圍。
當時,小悅手機上的郵件內容深深吸引了她的注意,郵件中詳細列出了一項sql任務:要求根據公司名稱和月份進行分組,統計出2024年全年的訂單表總數量,並進一步細分出已下單數量(狀態1)、送貨中數量(狀態2)以及已收貨數量(狀態3)。這一挑戰性的任務讓小悅不禁陷入了沉思,她迅速地在腦海中構想出了初步的方案1:
SELECT o.company, EXTRACT(MONTH FROM o.order_date) AS month, (SELECT COUNT(*) FROM orders o2 WHERE o2.company = o.company AND EXTRACT(MONTH FROM o2.order_date) = EXTRACT(MONTH FROM o.order_date) AND EXTRACT(YEAR FROM o2.order_date) = 2024) AS total_orders, (SELECT COUNT(*) FROM orders o2 WHERE o2.company = o.company AND EXTRACT(MONTH FROM o2.order_date) = EXTRACT(MONTH FROM o.order_date) AND o2.order_status = 1 AND EXTRACT(YEAR FROM o2.order_date) = 2024) AS ordered_count, (SELECT COUNT(*) FROM orders o2 WHERE o2.company = o.company AND EXTRACT(MONTH FROM o2.order_date) = EXTRACT(MONTH FROM o.order_date) AND o2.order_status = 2 AND EXTRACT(YEAR FROM o2.order_date) = 2024) AS delivering_count, (SELECT COUNT(*) FROM orders o2 WHERE o2.company = o.company AND EXTRACT(MONTH FROM o2.order_date) = EXTRACT(MONTH FROM o.order_date) AND o2.order_status = 3 AND EXTRACT(YEAR FROM o2.order_date) = 2024) AS received_count FROM orders o WHERE EXTRACT(YEAR FROM o.order_date) = 2024 GROUP BY o.company, EXTRACT(MONTH FROM o.order_date) ORDER BY o.company, month;
方案1查詢語句使用了多個子查詢來計算每個公司和月份的訂單數量,雖然可以實現所需的功能,但也存在一些缺點:
-
效能問題:
- 每個子查詢都需要對
orders
表進行獨立的掃描,這會導致多次重複的資料庫查詢,增加了資料庫的負擔。 - 對於大型資料集,這種多次掃描和查詢的方式會導致效能顯著下降。
- 每個子查詢都需要對
-
可讀性和維護性:
- 使用多個子查詢使得SQL語句變得複雜,難以閱讀和理解。
- 如果需要修改或除錯,需要逐個檢查每個子查詢,增加了維護的難度。
-
重複程式碼:
- 相同的條件(如公司、月份、年份)在每個子查詢中重複出現,導致程式碼冗餘。
- 如果需要修改這些條件,必須在每個子查詢中逐一修改,容易遺漏或出錯。
-
索引利用:
- 子查詢可能無法有效利用索引,尤其是在沒有合適的索引情況下,查詢效能會進一步下降。
隨後,小悅沒有放棄,反而更加專注地投入到方案一的最佳化中。她仔細分析了初步方案的可行性,並考慮到了效能最佳化和資料處理效率的問題。於是,她提出了最佳化後的方案2(Oracle/MySql/Mssql):
SELECT company, EXTRACT(MONTH FROM order_date) AS month, COUNT(*) AS total_orders, COUNT(CASE WHEN status = 1 THEN 1 END) AS ordered_count, COUNT(CASE WHEN status = 2 THEN 1 END) AS delivering_count, COUNT(CASE WHEN status = 3 THEN 1 END) AS received_count FROM orders WHERE EXTRACT(YEAR FROM order_date) = 2024 GROUP BY company, EXTRACT(MONTH FROM order_date) ORDER BY company, month;
方案2查詢語句使用了COUNT(CASE WHEN ...)
語法,具有以下優點:
-
效能最佳化:
- 透過在一個查詢中完成所有計算,避免了多次掃描和查詢資料庫,從而提高了查詢效能。
- 資料庫引擎可以更好地最佳化查詢計劃,利用索引和快取來加速查詢。
-
簡潔性和可讀性:
- 使用
COUNT(CASE WHEN ...)
語法使得SQL語句更加簡潔,減少了冗餘程式碼。 - 查詢邏輯清晰,易於閱讀和理解,便於維護和除錯。
- 使用
-
減少重複程式碼:
- 相同的條件(如公司、月份、年份)只需要在
WHERE
子句中寫一次,避免了在多個子查詢中重複書寫相同的條件。 - 如果需要修改查詢條件,只需在一個地方進行修改,減少了出錯的可能性。
- 相同的條件(如公司、月份、年份)只需要在
-
靈活性:
COUNT(CASE WHEN ...)
語法非常靈活,可以輕鬆地新增或修改條件,以適應不同的查詢需求。- 可以很容易地擴充套件到其他狀態或條件,而不需要重構整個查詢。
-
索引利用:
- 這種查詢方式可以更好地利用索引,尤其是在有合適的索引情況下,查詢效能會得到進一步提升。
小悅意識到雖然方案2的CASE
語法可以實現需求,但使用COUNT FILTER
語法在PostgreSQL中更為簡潔高效,而且由於國產資料庫大多相容PostgreSQL,這種選擇不僅提升了查詢效能,還確保了程式碼在國產資料庫環境中的廣泛適用性。方案3(PostgreSQL語法):,
SELECT company, EXTRACT(MONTH FROM order_date) AS month, COUNT(*) AS total_orders, COUNT(*) FILTER (WHERE status = 1) AS ordered_count, COUNT(*) FILTER (WHERE status = 2) AS delivering_count, COUNT(*) FILTER (WHERE status = 3) AS received_count FROM orders WHERE EXTRACT(YEAR FROM order_date) = 2024 GROUP BY company, EXTRACT(MONTH FROM order_date) ORDER BY company, month;
方案3中的COUNT(*) FILTER (WHERE status = 1)` 這種語法是 SQL:2003 標準引入的一個新特性,稱為"過濾聚合"(Filtered Aggregation)。
過濾聚合的出現是為了解決一些常見的 SQL 分析需求,例如:
1. 在統計訂單總數的同時,也統計已完成訂單的數量。
2. 在統計銷售總額的同時,也統計已付款訂單的銷售額。
3. 在統計某個商品的總銷量中,也統計該商品的正常銷量和退貨銷量。
在傳統的 SQL 中,解決這類需求通常需要使用多個子查詢或者分組之後進行過濾,程式碼會比較複雜。
過濾聚合的出現,讓這類需求的實現變得更加簡單和優雅。開發者可以在聚合函式中直接加上 `FILTER (WHERE ...)` 子句,對聚合的資料進行過濾,從而得到所需的統計結果。
比如上面的例子中,`COUNT(*) FILTER (WHERE status = 1)` 就可以直接統計狀態為 1 的訂單數量,無需再額外新增子查詢。
這種語法在 SQL:2003 標準中引入,PostgreSQL首先實現了這個語法。它極大地簡化了 SQL 的編寫,提高了程式碼的可讀性和可維護性。
Oracle /MySql/MsSql,對於這個 SQL 標準的新特性,並沒有直接支援,只能透過case when的形式實現。
示例,在Having中使用過濾聚合語法:
--case語法示例 SELECT company, EXTRACT(MONTH FROM order_date) AS month, COUNT(*) AS total_orders, COUNT(CASE WHEN status = 1 THEN 1 END) AS ordered_count, COUNT(CASE WHEN status = 2 THEN 1 END) AS delivering_count, COUNT(CASE WHEN status = 3 THEN 1 END) AS received_count FROM orders WHERE EXTRACT(YEAR FROM order_date) = 2024 GROUP BY company, EXTRACT(MONTH FROM order_date) Having COUNT(CASE WHEN status = 1 THEN 1 END)>0 ORDER BY company, month; --filter語法示例 SELECT company, EXTRACT(MONTH FROM order_date) AS month, COUNT(*) AS total_orders, COUNT(*) FILTER (WHERE status = 1) AS ordered_count, COUNT(*) FILTER (WHERE status = 2) AS delivering_count, COUNT(*) FILTER (WHERE status = 3) AS received_count FROM orders WHERE EXTRACT(YEAR FROM order_date) = 2024 GROUP BY company, EXTRACT(MONTH FROM order_date) Having COUNT(*) FILTER (WHERE status = 1)>0 ORDER BY company, month;