一條有思考SQL編寫(Oracle數據庫,DECODE函數)

今天在工做中遇到一個比較有意思的業務場景,不知道你們平時是怎麼解決。(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就能夠實現,不用在代碼業務層進行循環遍歷,不單單減小代碼也優化了接口的性能。

相關文章
相關標籤/搜索