工做日誌,常見的統計解決方案

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

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

普通分組統計

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

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

@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;

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

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

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

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

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

文章到這裏就結束了。若是文章對你有幫助,能夠點個"推薦",也能夠"關注"我,得到更多豐富的知識。若文中有什麼不對或者不嚴謹的地方,請指正。

相關文章
相關標籤/搜索