項目中需要做一個船舶代理費的功能,針對代理的船進行收費,那么該功能的第一步便是選擇進行代理費用信息的錄入,在進行船舶選擇的時候,發現加載相關船舶信息十分的慢,其主要在sql語句的執行,因為測試的時候數據較少,實際使用中,數據量較大。
關於regexp_substr函數的使用可查看Oracle通過一個字段的值將一條記錄拆分為多條記錄
需求和表結構
船舶相關的信息在系統中有船舶動態表(CBDT),另外有一張船舶代理費表(CBDLF),要求對於已經錄入代理費的船舶不再出現在列表中(CBDLF表中有記錄的需要過濾掉),CBDT中有一個合同清單字段,HTQD,該字段由分號";"拼接多個合同,由於選了船舶,需要計算這個船上所有合同的作業量(拿合同字段和其他表做連接),因此需要切割,方便后繼的作業量計算,需求引入就是這里——需要切割合同清單字段(HTQD),存在幾個合同,就要將該行變成幾條記錄。
- 船舶動態表CBDT(肯定是省略的啦,哪有這么簡單的表)
CBBH | HC | HTQD |
---|---|---|
0001 | 191210 | N20191202-xx;N20191203-xx |
- 船舶動態表CBDLF
CBBH | HC | Free |
---|---|---|
0001 | 191210 | 12534.23 |
原來的方案
對於之前的sql,執行時間長達5秒多,最快也是4秒多,而且是只有一個月的數據。
看看原來的sql語句
select CBDT.CBBH, CBDT.HC,
regexp_substr(CBDT.HTBHQD,'[^;]+', 1,LEVEL,'i') HTTDBH
FROM CBDT
WHERE (CBDT.CBBH, CBDT.HC) not IN (SELECT CBBH, HC from CBDLFB)
AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
connect by LEVEL <=regexp_count(CBDT.HTBHQD, ';') + 1
第一次嘗試
使用了not in
,顯然這滿足要求,但事實是not in
的效率是十分低下的,(當初在用的時候,我也不知道啊,手動捂臉),所以應該改成join
,有了下面的sql
select CBDT.CBBH, CBDT.HC,
regexp_substr(CBDT.HTBHQD,'[^;]+', 1,LEVEL,'i') HTTDBH
FROM CBDT
LEFT JOIN CBDLFB ON CBDT.CBBH = CBDLFB.CBBH and CBDT.HC = CBDLFB.HC
WHERE CBDLFB.CBBH is NULL
AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
connect by LEVEL <=regexp_count(CBDT.HTBHQD, ';') + 1
這樣改了之后,基本維持在4秒左右,當然,這還是不能忍的啊。
第二次嘗試
通過改變時間,無論是延長還是縮短,sql執行的時間基本都在4秒左右,所以,目前的數據量對sql的影響不是很大了,那么肯定是sql本身的問題,去掉regexp_substr后,果然,只需要0.0xx秒的時間,所以基本確定了是這個函數的問題。開始度娘和谷歌。然而只找到了一個百度經驗說性能問題,也沒有說怎么解決。直到在谷歌上有人說,regexp_substr是正則
,其本身效率就不高,不推薦。但是不推薦如前我的需求是必須要用啊(不知道有沒有其他方案),找了許久依舊沒有解決方案,回頭再觀察sql,regexp_substr
是正則表達式毫無疑問,然后發現最后的regexp_count
,這個那應該也是正則,但是regexp_count(CBDT.HTBHQD, ';')
的意思是計算有幾個分號,這個函數可以換掉啊。所以改用了LENGTH(CBDT.HTBHQD) -LENGTH(REPLACE(CBDT.HTBHQD,';','')) + 1
,運行,奇跡發生了。
新的sql
select CBDT.CBBH, CBDT.HC,
regexp_substr(CBDT.HTBHQD,'[^;]+', 1,LEVEL,'i') HTTDBH
FROM CBDT
LEFT JOIN CBDLFB ON CBDT.CBBH = CBDLFB.CBBH and CBDT.HC = CBDLFB.HC
WHERE CBDLFB.CBBH is NULL
AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
connect by LEVEL <= LENGTH(CBDT.HTBHQD) -LENGTH(REPLACE(CBDT.HTBHQD,';','')) + 1
速度提到了約:67%。
1秒多的時間,雖然較原來的5秒要好太多,但是1秒多的卡頓,始終還是不好,那么繼續嘗試吧。
找新的方案去了,待更新........(不到1秒內,誓不回);
——————————————我是分割線——————————————
我回來了,因為找到了終極優化,從5秒到0.025s,還是谷歌啊。
最后執行時間
話不多說,直接看改后的sql
select CBDT.CBBH, CBDT.HC,
regexp_substr(CBDT.HTBHQD,'[^;]+', 1,l) HTTDBH -- 原來的LEVEL換成了l,注意
FROM CBDT
LEFT JOIN CBDLFB ON CBDT.CBBH = CBDLFB.CBBH and CBDT.HC = CBDLFB.HC,
(SELECT LEVEL l FROM DUAL CONNECT BY LEVEL<=100) b -- 關鍵
WHERE CBDLFB.CBBH is NULL
AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
AND l <= LENGTH(CBDT.HTBHQD) -LENGTH(REPLACE(CBDT.HTBHQD,';','')) + 1
之前的connect 是使用到sql最后,這樣的方式會導致數據出現很多冗余,而且冗余特別嚴重,需要使用distinct,至於原因,還在找。使用regexp_substr函數必須配對使用connect,但是沒想到居然可以這樣使用。
5—>0.023 這速度提高99.5%;頁面秒開,爽。
最后
本文可在我的小站中查看記Oracle中regexp_substr函數的一次調優
生命不息,使勁造。