今天在工做中遇到一個比較有意思的業務場景,不知道你們平時是怎麼解決。(Oracle數據庫)前端
後臺管理小功能,統計系統每一天的客戶轉化率,也就是 當天註冊並已經下單的客戶數/當天註冊的總客戶數java
返回給前端的數據格式是:sql
{ "code": 200, "data": [ { "time": "2021-07-22", "ratio": "60%" }, { "time": "2021-07-23", "ratio": "0%" }, { "time": "2021-07-26", "ratio": "100%" } ] }
這裏涉及了兩張表,一張是註冊表,一張是訂單表,根據註冊表的用戶id去訂單表查詢,若是有數據,證實這我的已經下單了。數據庫
參考了同事的相似的業務場景實現:oracle
他是根據前端傳的時間範圍,在java業務層遍歷這個時間範圍,拿到每一天的相關數據,好比說,先查詢出這天註冊並已經下單的客戶數,再查詢出當天註冊的總客戶數,在業務層進行相除,封裝號數據進行返回。函數
這樣的好處就是sql好寫,很容易的兩條sql,可是壞處就是發起的sql請求太屢次了,一天就是2次sql,一年就是730,十年就是7300次sql,數據量一大這個接口確定會有問題。性能
那咱們能不能用一次sql來解決這個問題(Oracle數據庫)優化
個人思路是:spa
因此首先是按照用戶id將訂單表左鏈接到註冊表,而後根據註冊表的註冊時間進行按天分組,注意得用左鏈接,不用全鏈接,這樣沒有購買的註冊數據纔會出現。code
而後在以每一天分組中,統計組內的數據總數也就是當天註冊的總客戶數,再統計組內訂單狀態爲購買的數據,也就是當天註冊並已經下單的客戶數,二者相除
第一步:訂單表左鏈接到註冊表,而後根據註冊表的註冊時間進行按天分組
SELECT TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd') AS TIME FROM SYS_USER
LEFT JOIN SYS_ORDER ON SYS_USER.USER_ID=SYS_ORDER.USER_ID
GROUP BY TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd')
第二步:統計出各組的總條數 ,也就是當天註冊的總客戶數
SELECT TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd') AS TIME , COUNT(*) AS TOTAL FROM SYS_USER LEFT JOIN SYS_ORDER ON SYS_USER.USER_ID=SYS_ORDER.USER_ID GROUP BY TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd')
咱們會發現,總客戶數數量不對,這個問題是由於一個客戶可能下了屢次單,使用訂單表有不少條數據,當左連接的時候,總條數就增長了。
那應該如何解決?
應該把訂單表中的同個用戶id進行分組排序,取第一條數據。
這裏用到oracle開窗函數:先分組,再按某字段排序,取分組內第一條數據
select t.* from (select a.*, row_number() over(partition by 須要分組的字段 order by 須要排序的字段 desc) rw from 表 a) t where t.rw = 1
第三步:這樣咱們就能夠利用子查詢,把sql再整合一下。
SELECT TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd') AS TIME , COUNT(*) AS TOTAL FROM SYS_USER LEFT JOIN (
select t.*
from (select a.*, row_number() over(partition by USER_ID order by STATUS desc) rw
from SYS_ORDER a) t
where t.rw = 1
)
GROUP BY TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd')
第四步:重要的一步,如何去查詢出當天註冊並已經下單的客戶數,咱們知道,訂單表有狀態,狀態爲0就是訂單完成。
因此就轉化成:查詢分組中,訂單狀態爲0的記錄總條數。能夠藉助DECODE函數來實現。關於DECODE函數你們能夠自行百度
SELECT TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd') AS TIME , COUNT(*) AS TOTAL ,COUNT(DECODE(ORDER.STATUS,0,1,NULL)) AS BUY_TOTAL
FROM SYS_USER LEFT JOIN (
select t.*
from (select a.*, row_number() over(partition by USER_ID order by STATUS desc) rw
from SYS_ORDER a) t
where t.rw = 1
)
GROUP BY TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd')
DECODE(ORDER.STATUS,0,1,NULL) 表示:ORDER.STATUS這個字段若是等於0那這個函數結果就是1,若是不等於0結果爲NULL,咱們知道COUNT(*)是不統計null的
第五步:相除
SELECT TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd') AS TIME , ROUND(COUNT(DECODE(ORDER.STATUS,0,1,NULL))/COUNT(*)*100,2)||'%' AS RATIOAS BUY_TOTAL
FROM SYS_USER
LEFT JOIN (
select t.*
from (select a.*, row_number() over(partition by USER_ID order by STATUS desc) rw
from SYS_ORDER a) t
where t.rw = 1
)
GROUP BY TO_CHAR(SYS_USER.DATE,'yyyy-mm-dd')
這樣的相同的業務場景一條sql就能夠實現,不用在代碼業務層進行循環遍歷,不單單減小代碼也優化了接口的性能。