工作日志,常見的統計解決方案


最近用MySQL做統計的需求比較多,這里整理一些常用的場景方便后期查閱,同時也是拋磚引玉的過程。其中包括普通的分組統計連續的每日統計區間范圍統計

技術:MySQL, SpringDataJpa, Kotlin
說明:文章前半部分是場景分析,后半部分是語法分析
要點:GROUP BY, UNION, DATE_FORMAT, 流程控制函數

普通分組統計

場景一:根據訂單狀態統計訂單數量。

一個很常見,也很簡單的統計需求。其中狀態字段是訂單實體的一個屬性。參考代碼:(Kotlin語法)

@Query("SELECT status, COUNT(id) FROM Order GROUP BY status")
fun summaryOrderByStatus(): Array<Array<String>>?

場景二:根據訂單中商品類目統計訂單數量和金額。

比場景一稍微麻煩了一點,商品字段是訂單實體的一個屬性,而類目字段才是商品實體的一個屬性。參考代碼:(Kotlin語法)

@Query("SELECT commodity.category, COUNT(id), SUM(finalPrice) FROM Order GROUP BY commodity.category")
fun summaryOrderByCommodityCategory(): Array<Array<String>>?

小結:
一)、分組統計少不了GROUP BY語句,如果需要加查詢條件,請在其前面添加 WHERE 語句。
二)、統計數量用COUNT,統計總和用SUM函數,有GROUP BY的地方,少不了這些聚合函數。
三)、統計返回的結果是字符串類型的二維數組。
四)、以內嵌屬性分組,如果是SpringDataJpa框架,則可以直接通過"實體類.屬性名"的方式。

每日統計

在做每日,每周,每月統計時,遇到返回日期不是連續的情況。原因是數據庫中沒有值,而我們理想狀態應該是:如果沒有值則默認為零,使其數據是連續的日期。

場景三:統計結果日期可能不連續

如果數據庫中某個時間段沒有值,那統計出來的結果會缺這段時間。參考代碼:(sql語句)

--統計每小時
SELECT HOUR(create_date) hour,count(*) count FROM order WHERE create_date like '2019-06-24%' GROUP BY hour ORDER BY hour;
-- 統計每日
SELECT DATE_FORMAT(create_date,'%Y-%m-%d') as days, COUNT(id) count FROM order GROUP BY days;
-- 統計每周
SELECT DATE_FORMAT(create_date,'%Y-%u') as weeks, COUNT(id) count FROM order GROUP BY weeks;
-- 統計每月
SELECT DATE_FORMAT(create_date,'%Y-%m') as months, COUNT(id) count FROM order GROUP BY months;

場景四:統計結果日期連續

要讓日期連續,又要代碼優雅。說實話,困擾了我很久,一直沒有找到很好的解決方法,雖然目前這個方法很挫。但可以解決問題。畢竟抓到老鼠的都是好貓。如果各位有好的建議,望賜教!

解決思路:
第一步:創建一張date_summary輔助表,字段只需要有date和count(默認值為零)。
第二步:先向date_summary表插入10年內的數據。
第三步:通過UNION ALL 聯合查詢,將空缺的日期補上。

第二步參考代碼(Kotlin語法)

val startDate = Calendar.getInstance()
startDate.set(2018, 6, 1)
val startTIme = startDate.timeInMillis
val endDate = Calendar.getInstance()
endDate.set(2028, 11, 30)
val endTime = endDate.timeInMillis
val oneDay = 1000 * 60 * 60 * 24L
var time = startTIme
val dates: MutableList<DateSummary> = arrayListOf()
while (time<=endTime) {
	val dateSummary = DateSummary()
	dateSummary.date = SimpleDateFormat("yyyy-MM-dd").format(Date(time))
	dateSummary.count = 0
	dates.add(dateSummary)
	time += oneDay
}
dateSummaryRepository.saveAll(dates)

第三步統計每日的SQL語句

SELECT
	summary.oneDay,
	summary.count 
FROM
	(
	SELECT
		DATE_FORMAT( created_date, '%Y-%m-%d' ) oneDay,
		COUNT(id) count 
	FROM
		service_order 
	WHERE created_date BETWEEN "2018-06-01" and "2018-08-01"
	GROUP BY oneDay 
	UNION ALL
		(
		SELECT
			DATE_FORMAT( date, '%Y-%m-%d' ) templateDay,
			count
		FROM
			date_summary
		WHERE date BETWEEN "2018-06-01" and "2018-08-01"
		GROUP BY
			templateDay
		) 
	) summary 
GROUP BY
	summary.oneDay 
ORDER BY
	summary.oneDay ASC

----------------------------------------------補充修改--------------------------------------------------------
上面一段sql在MySQL5.7之前是可以正常運行的,當Mysql5.7之后會提示錯誤
xxx which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
原因是MySQL5.7 默認為ONLY_FULL_GROUP_BY: 大概意思就是:用了group by field,field就要有聚合操作。
解決方法:嚴格按照MySQL的標准來修改sql語句,極力反對通過修改配置取消MySQL的ONLY_FULL_GROUP_BY校驗的方法。

SELECT
	summary.oneDay, summary.count 
FROM
	(
SELECT
	DATE_FORMAT( create_time, '%Y-%m-%d' ) oneDay, COUNT( id ) count 
FROM
	alarm_record 
WHERE
	alarm_object_type = '0' AND create_time BETWEEN '2019-05-01 00:00:00' AND '2019-05-30 00:00:00' 
GROUP BY
	oneDay UNION ALL
		(
			SELECT
				DATE_FORMAT( date, '%Y-%m-%d' ), count 
			FROM
				date_summary 
			WHERE
				date BETWEEN DATE_FORMAT( '2019-05-01 00:00:00', '%Y-%m-%d' ) AND DATE_FORMAT( '2019-05-30 00:00:00', '%Y-%m-%d' ) 
		) 
	) summary 
ORDER BY
	summary.oneDay ASC

小結:
一)、MySQL的DATE_FORMAT(date,format) 函數用於以不同的格式顯示日期/時間數據,文章后面會詳細介紹
二)、MySQL的UNION 操作符用於合並兩個或多個SELECT語句的結果集,文章后面會詳細介紹

區間范圍統計

這是一個較為常見的需求,比如按照年齡段統計人員分布情況,甚至要求分別統計男女人數分布情況。

場景五:根據小區年齡段統計人數

只根據年齡范圍統計,沒有其他限制條件,使用SUM只需要加一。

SELECT INTERVAL(age,10,20,30,40,50,60,70,80,90) AS ageRatio, SUM(1) AS count FROM user GROUP BY ageRatio

場景六:根據小區年齡段統計男女人數

在場景五的基礎上多了一個區分性別,用流程控制函數來設置SUM加一的情況。

SELECT INTERVAL(age,10,20,30,40,50,60,70,80,90) AS ageRatio, 
SUM(CASE WHEN sex=1 THEN 1 ELSE 0 END) AS male,
SUM(CASE WHEN sex=0 THEN 1 ELSE 0 END) AS female FROM user GROUP BY ageRatio

小結:
一)、通過區間統計需要使用MySQL的INTERVAL函數,第一個參數是需要比較的字段,后面是比較的區間,值必須從小到大
二)、區間統計的結果也是二維數組,注意返回的結果可能不是連續的(這里的不連續可以用代碼解決,畢竟區間數量較少)。第一個參數返回的是區間的下標,從0開始。
三)、當age的值在區間范圍內就SUM加一,也可以通過流程控制函數(CASE WHEN THEN ELSE END)來判斷是加一還是加零

MySQL知識點

知道現在都是快餐文化,大家都很忙,很少有時間去揣摩各語法的特點。所以先把常用的場景寫在前面,語法知識寫在后面。

GROUP BY 分組

一)、分組一般與聚合函數一起使用如SUM,COUNT等
二)、GROUP BY 在WHERE 語句之后

DATE_FORMAT 時間格式化

一)、用來修改時間的格式
二)、語法格式: DATE_FORMAT(date,format) date必須是合格的時間參數,format是輸出時間格式
三)、常見的format格式有:

  • %Y: 4位數的年,
  • %y: 2位數的年,
  • %m: 2位數的月(00~12),
  • %M: 英文單詞的月,
  • %d: 2位數的日(00~31),
  • %u: 周,星期一是一周的第一條,
  • 更多可以訪問w3school

UNION 聯合結果

一)、UNION可以合並、聯合,將多次查詢結果合並成一個結果,通過查詢結果合並解決了統計不連續的情況。
二)、多條查詢語句的列數必須一致,各列的順序最好一致。場景四中,兩條sql都只查詢了date和count,且順序保持一致。
三)、union 去重,union all包含重復項

INTERVAL 比較間距

一)、INTERVAL()函數是比較列表(N, arg1, arg2, arg3...argN)中的N值。
二)、INTERVAL()函數如果N<arg1則返回0,如果N<arg2則返回1,如果N<arg3則返回2,如果N為NULL,它將返回-1。
三)、列表值必須是arg1 < arg2 < arg3 的形式才能正常工作。

流程控制函數

一)、case when then else end 是流程控制函數中的一種,還有一種是if函數
二)、使用語法:

case 
when 條件1 then 值1
when 條件2 then 值2
...
else 值n
end

文章到這里就結束了。如果文章對你有幫助,可以點個"推薦",也可以"關注"我,獲得更多豐富的知識。若文中有什么不對或者不嚴謹的地方,請指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM