如果一個計算應該在資料庫端執行,而卻沒有這麼做,那將是大錯特錯。
我們並非提倡將所有業務邏輯盲目地移到資料庫,但當我在 StackOverflow 看到一個這樣的問題時,就有種衝動想讓你看下這篇熱文—— 10 Common Mistakes Java Developers Make When Writing SQL ——中的第二點。
那個 stack overflow 上的問題,本質上來說是這樣的:
在如下的中型表中,我想統計每個 App ID 中 status 為 0 或 1 的 doc 數
1 2 3 4 5 6 7 |
AppID | DocID | DocStatus ------+-------+---------- 1 | 100 | 0 1 | 101 | 1 2 | 200 | 0 2 | 300 | 1 ... | ... | ... |
我該使用 Hibernate 嗎?
答案是:不!別用 hibernate 來做(除非是寫 native query),應該用 SQL 來做 。S——Q——L !使用SQL Server的話,有很多簡單的做法來實現這個查詢,而且時間會比將全部資料不作聚集就裝到 java 記憶體少很多。
以 SQL Server 來舉個例:
使用 GROUP BY
這是最簡單的做法,但它不一定完全符合你的要求。即是,子集合不在同一行。
1 2 3 |
SELECT [AppID], [DocStatus], count(*) FROM [MyTable] GROUP BY [AppID], [DocStatus] |
如 SQLFiddle 的例子,會查出這樣的東西:
1 2 3 4 5 6 |
| APPID | DOCSTATUS | COLUMN_2 | |-------|-----------|----------| | 1 | 0 | 2 | | 2 | 0 | 3 | | 1 | 1 | 3 | | 2 | 1 | 2 | |
使用 nested select
這可能就是那個特殊使用者所要的效果。他們可能想每個集合分別一列,而nest select就是最通用的實現手法之一。記住這種做法在某些資料庫上是有點慢的,需要花些時間制定執行計劃。
1 2 3 4 5 6 7 8 9 |
SELECT [AppID], (SELECT count(*) FROM [MyTable] [t2] WHERE [t1].[AppID] = [t2].[AppID] AND [DocStatus] = 0) [Status_0], (SELECT count(*) FROM [MyTable] [t2] WHERE [t1].[AppID] = [t2].[AppID] AND [DocStatus] = 1) [Status_1] FROM [MyTable] [t1] GROUP BY [AppID] |
例如 SQLFiddle ,跑出這樣的東西:
1 2 3 4 |
| APPID | STATUS_0 | STATUS_1 | |-------|----------|----------| | 1 | 2 | 3 | | 2 | 3 | 2 | |
使用 sum()
這可能是最佳的作法。它跟前面那個nest select一樣,但只能用於簡單的查詢,而前者更靈活。
1 2 3 4 5 |
SELECT [AppID], SUM(IIF([DocStatus] = 0, 1, 0)) [Status_0], SUM(IIF([DocStatus] = 1, 1, 0)) [Status_1] FROM [MyTable] [t1] GROUP BY [AppID] |
例如 SQLFiddle ,結果如上。
使用 PIVOT
這種方案是為 SQL 迷而準備的。它使用了 T-SQL 的 PIVOT 子句!
1 2 3 4 5 6 7 8 9 10 |
SELECT [AppID], [0], [1] FROM ( SELECT [AppID], [DocStatus] FROM [MyTable] ) [t] PIVOT ( count([DocStatus]) FOR [DocStatus] IN ([0], [1]) ) [pvt] |
SQL 迷就該用 PIVOT !
例如 SQLFiddle ,結果亦如上。
結論
以上建議任君選擇,而且我也相信還有更多方案。它們都比基於java的聚合好幾個數量級,即使是在少量資料的情況下。我們將多次重申這個觀點。
正如 Gavin King 所說的:
你在使用 Hibernate,並不就意味著你把它套到所有事情上。這是我說了十幾年的話了。
轉為我們的說法就是:
該用 SQL 的時候就用 SQL ,它的適應性超乎你想象。