記錄這篇文章前,是今天寫了一個存過的需求,嗯,咋說,不想新建臨時表,所以想了下用自定義type的形式想辦法在純過裏面搞一個table級別的數據集合,先上代碼:
CREATE OR REPLACE PROCEDURE GET_TAG_COM(
INDATA IN VARCHAR2,
OUTDATA OUT SYS_REFCURSOR
)
IS
-----------------重點:自定義類型-------------------------------------------------------------
V_MYTAB1 MYTABLE_COL1_TAB;
V_MYTAB4 MYTABLE_COL4_TAB;
V_INDATA MYARRAY;
V_ONEDATA MYARRAY;
---------------------------------------------------------------------------------------------
V_CONDITION VARCHAR2(30000);
I NUMBER;
V_QUERYID NUMBER;
V_TIME NUMBER;
V_SQL1 VARCHAR2(30000);
V_SQL2 VARCHAR2(30000);
BEGIN
--自定義方法用於數據庫切分字符串------------------------------------------
--前端入參格式:["UPDATETIME=5,isCalCount=Y,CHANNELID=21,QUERYID=1,PageIndex=1,startLine=1,recCount=20,PageSize=20"]
--自定義函數切分後得到的格式:["UPDATETIME=5","isCalCount=Y","CHANNELID=21","QUERYID=1","PageIndex=1","startLine=1","recCount=20","PageSize=20"]
V_INDATA := COMMON_FUNC.SplitString(INDATA,',');
--用於where後面的sql拼接,一個很好的解決是否要and的方法
V_CONDITION := ' AND 1=1';
--循環遍歷的初始賦值
I := 0
FOR I IN 1 .. V_INDATA.COUNT LOOP
--再次調用自定義切分函數,由於第一次已經把字符串切分成一個數組,這裏循環數組取出單個進行而且切分
--切分前格式:["UPDATETIME=5"]
--切分後格式:["UPDATETIME","5"]
V_ONEDATA := COMMON_FUNC.SplitString(V_INDATA(I),'=');
IF UPPER(V_ONEDATA(1)) = 'QUERYID'
THEN
--由於二次切分,就可以更具數組判定入參值了,甚至還能調整想要的參數
V_QUERYID := TO_NUMBER(V_ONEDATA(2));
ELSIF UPPER(V_ONEDATA(1)) = 'UPDATETIME'
THEN
V_TIME := TO_NUMBER(V_ONEDATA(2)/(24*60));
END IF;
END LOOP;
---------------------------------全量--------------------------------------------------
--根據入參可以分邏輯處理事件
IF V_QUERYID = 1
THEN
--------------------------------獲取參數數據--------------------------------------------
FOR I IN 1 .. V_INDATA.COUNT LOOP
V_ONEDATA := COMMON_FUNC.SplitString(V_INDATA(1),'=');
--這裏再次調用自定義二次切分函數,通過憑藉到where 1=1 可以得到where條件的拼接
SELECT V_CONDITION || DECODE(V_ONEDATA(1),
'CHANNELID',
' AND TZC.Channelid IN( ''' || REPLACE(V_ONEDATA(2),'.',''',''') || ''')',
'UPDATETIME',
'',
'') INTO V_CONDITION FROM DUAL;
END LOOP;
---------------------------------獲取商品列表----------------------------------------
--sql拼接
V_SQL1 := '
SELECT MYTABLE_COL1_OBJ(TAG_ID) FROM (
SELECT TCT.TAG_ID
FROM T_ZS_CHANNEL_BRANDORCOMMODITY TZC
JOIN T_COMMODITY_INFO TCI
ON TZC.OBJID = TCI.COMMODITY_ID AND TZC.TYPE = 2 AND TCI.STATUS = 1
JOIN T_COMMODITY_TAG_BINDING TCTB
ON TCTB.COMMODITY_ID = TCI.COMMODITY_ID
LEFT JOIN T_COMMODITY_TAG TCT
ON TCTB.TAG_ID = TCT.TAG_ID AND TCT.TAG_TYPE = 3
WHERE TCI.ISRELEASE = 1 ' || V_CONDITION || '
)';
--自定義類型定義的表,不能和拼接sql一起執行,只能單獨into,如果參考網上的select into教程,會直接報錯
EXECUTE IMMEDIATE V_SQL1 BULK COLLECT INTO V_MYTAB1;
------------------------------獲取該標籤下的所有商品及其省份--------------------------
--下面的join關聯了上面sql執行的結果數據,而 V_MYTAB1 就相當於一張臨時表來緩存數據,並在接下來能夠繼續使用
SELECT MYTABLE_COL4_OBJ(TAG_ID,TAG_NAME,PROVINCECODE,COUNTNUM)BULK COLLECT INTO V_MYTAB4 FROM (
SELECT TCT.TAG_ID,TCT.TAG_NAME,TCE.PROVINCECODE,COUNT(TCI.COMMODITY_ID) AS COUNTNUM
FROM T_COMMODITY_TAG TCT
JOIN T_COMMODITY_TAG_BINDING TCTB
ON TCTB.TAG_ID = TCT.TAG_ID AND TCT.TAG_TYPE = 3
JOIN T_COMMODITY_INFO TCI
ON TCTB.COMMODITY_ID = TCI.COMMODITY_ID AND TCI.STATUS = 1 AND TCI.ISRELEASE = 1
JOIN T_COMMODITY_EXCHANGESCOPE TCE
ON TCE.COMMODITYID = TCI.COMMODITY_ID
JOIN TABLE(V_MYTAB1) TAB ON TCT.TAG_ID = TAB.COL1
GROUP BY TCT.TAG_ID,TCT.TAG_NAME,TCE.PROVINCECODE
);
--遊標的out不能直接返回自定義類型的表,只能通過查詢的方式返回自定義類型的表數據
OPEN OUTDATA FOR SELECT * FROM TABLE(V_MYTAB4);
--後面的邏輯都差不多
-----------------------------------------獲取增量數據---------------------------------------
ELSIF V_QUERYID = 2
THEN
FOR I IN 1 .. V_INDATA.COUNT LOOP
V_ONEDATA := COMMON_FUNC.SplitString(V_INDATA(1),'=');
SELECT V_CONDITION || DECODE(V_ONEDATA(1),
'CHANNELID',
' AND TZC.Channelid IN( ''' || REPLACE(V_ONEDATA(2),'.',''',''') || ''')',
''
) INTO V_CONDITION FROM DUAL;
END LOOP;
------------------------------------獲取變動過的tagid---------------------------------
V_SQL1 := '
SELECT MYTABLE_COL1_OBJ(TAG_ID) FROM (
SELECT TCT.TAG_ID
FROM T_ZS_CHANNEL_BRANDORCOMMODITY TZC
JOIN T_COMMODITY_INFO TCI
ON TZC.OBJID = TCI.COMMODITY_ID AND TZC.TYPE = 2 AND TCI.STATUS = 1
LEFT JOIN T_COMMODITY_TAG_BINDING TCTB
ON TCTB.COMMODITY_ID = TCI.COMMODITY_ID
LEFT JOIN T_COMMODITY_TAG TCT
ON TCTB.TAG_ID = TCT.TAG_ID AND TCT.TAG_TYPE = 3
WHERE TCI.ISRELEASE = 1
AND TCT.UPDATE_TIME > SYSDATE-'|| V_TIME || '
OR TCI.UPDATE_TIME > SYSDATE-'|| V_TIME || '
OR TCTB.UPDATE_TIME > SYSDATE-'|| V_TIME || V_CONDITION || '
)';
EXECUTE IMMEDIATE V_SQL1 BULK COLLECT INTO V_MYTAB1;
------------------------------獲取該標籤下的所有商品及其省份--------------------------
SELECT MYTABLE_COL4_OBJ(TAG_ID,TAG_NAME,PROVINCECODE,COUNTNUM)BULK COLLECT INTO V_MYTAB4 FROM (
SELECT TCT.TAG_ID,TCT.TAG_NAME,TCE.PROVINCECODE,COUNT(TCI.COMMODITY_ID) AS COUNTNUM
FROM T_COMMODITY_TAG TCT
JOIN T_COMMODITY_TAG_BINDING TCTB
ON TCTB.TAG_ID = TCT.TAG_ID AND TCT.TAG_TYPE = 3
JOIN T_COMMODITY_INFO TCI
ON TCTB.COMMODITY_ID = TCI.COMMODITY_ID AND TCI.STATUS = 1 AND TCI.ISRELEASE = 1
JOIN T_COMMODITY_EXCHANGESCOPE TCE
ON TCE.COMMODITYID = TCI.COMMODITY_ID
JOIN TABLE(V_MYTAB1) TAB ON TCT.TAG_ID = TAB.COL1
GROUP BY TCT.TAG_ID,TCT.TAG_NAME,TCE.PROVINCECODE
);
OPEN OUTDATA FOR SELECT * FROM TABLE(V_MYTAB4);
END IF;
END GET_TAG_COM;
這個存過精彩點在哪裏?
V_MYTAB1 MYTABLE_COL1_TAB;
V_MYTAB4 MYTABLE_COL4_TAB;
V_INDATA MYARRAY;
V_ONEDATA MYARRAY;
這裏的自定義類型解決了我想要在sql執行過程中產生的中間數據需要緩存,並在接下來的sql中關聯這些數據進行使用;
要想玩懂這種方法,需要弄清楚Oracle的type,
什麼是type?
type可以在哪些位置使用?
在這個分割線裏,插入一句,存過產生的臨時數據還有另一種解決辦法,就是建立一張僅存儲過程能有調用的臨時表,這種臨時表有着事物執行完後自動刪除數據,不允許用戶訪問等權限,也能保障和解決存過需要使用過度數據的方法,具體方式這裏不做記錄;
繼續type
Oracle有5中type類型,而且在ocp考試的時候會考這點,type的解釋官方文檔地址:
https://docs.oracle.com/cd/E18283_01/appdev.112/e11822/adobjcol.htm
再Oracle中,作爲一個DBA,我之前問過我自己一個問題:
爲什麼create table a();這個創建表a的語句要用create table?爲什麼不直接是create a?
爲什麼定義字段a,後面必須要跟個類型,不管是int還是varchar2;
之後學習過程中,我瞭解到,數據存儲結構可以以類型來作爲區分。
table是一種類型,varchar2也是一種類型,同樣,官方定義的類型再多,也有需要定製的時候。所以提供一種獨特的類型:
自定義type;
既然type可以定義數據結構,那麼同樣的,我想要自定義數據結構的時候,就需要使用自定義type這種類型方式;
在PLSQL中
左邊欄,在存過後邊緊跟着的就是type文件夾,定義的type都放在裏面
type和package差不多,一樣有頭,一樣有body;
結構雖然差不多,但是type可以只要頭就能夠使用,不像package必須要頭和body一起才能使用;
CREATE TYPE person_typ AS OBJECT (
idno NUMBER,
name VARCHAR2(30),
phone VARCHAR2(20),
MAP MEMBER FUNCTION get_idno RETURN NUMBER,
MEMBER PROCEDURE display_details ( SELF IN OUT NOCOPY person_typ ) );
/
CREATE TYPE BODY person_typ AS
MAP MEMBER FUNCTION get_idno RETURN NUMBER IS
BEGIN
RETURN idno;
END;
MEMBER PROCEDURE display_details ( SELF IN OUT NOCOPY person_typ ) IS
BEGIN
-- use the put_line procedure of the DBMS_OUTPUT package to display details
DBMS_OUTPUT.put_line(TO_CHAR(idno) || ' - ' || name || ' - ' || phone);
END;
END;
/
CREATE TYPE people_typ AS TABLE OF person_typ; -- nested table type
/
以上是官方創建type的舉例,裏面的body是爲了返回個頭中map部分而創建的,如果用不上可以不寫,比如寫成:
CREATE TYPE person_typ AS OBJECT (
idno NUMBER,
name VARCHAR2(30),
phone VARCHAR2(20),
);
CREATE TYPE people_typ AS TABLE OF person_typ; -- nested table type
官方這裏舉例定義了一個三個字段的object;
所以後面必須要跟一個crate type 語句as 一下這個object才能構成一個type;
如果只需要一個字段的一維數組,直接用create type就可以創建了,比如我這裏用的存儲數據type定義
CREATE OR REPLACE TYPE "MYARRAY" is Table of varchar2(4000)
這裏可能就有點繞了,爲什麼要object,
其實object是固定的寫法,自定義type固定創建步驟是:
1、創建一個需求格式的object
2、創建type並且應用所創建的object
之所以創建myarray直接用了varchar2,而沒有指向一個object,是因爲varchar2本身就是一個單列的object,
所以創建單列自定義type的時候,
不需要額外的定義object,直接指向Oracle擁有的object即可;同樣的,number,time等也是單列object,
所以創建myarray時沒有單獨定義一個obj,由於oracle沒有自帶多列的obj,所以創建多列自定義type時需要定義一個多列obj;
說了這麼多爲什麼要自定義type,一般來說自定義type都是在存過裏面用的,因爲存過在處理數據庫邏輯的時候可能產生很多中間數據,或者會收到外部的不是數據庫結構的數據,當我們需要解析這些數據的時候,需要自己處理,而處理的第一步就是把數據緩存下來,定義type的作用就是爲了給緩存數據一個緩存空間。
這樣就能在存過裏面使用我們定義的這個type,就有了一個空間去榮譽數據;
比如本次所用的四個字段type:
先創建一個object
create or replace type MYTABLE_COL4_OBJ as object
(
col1 varchar2(100),
col2 varchar2(100),
col3 varchar2(100),
col4 varchar2(100)
)
在創建一個type 使用object類型:
create or replace type MYTABLE_COL4_TAB as table of MYTABLE_COL4_OBJ;
這樣我就能在存過中定義一個變量,讓這個變量成爲一張四個字段的表:
同樣MYARRAY也是自定一個字段的表;
當然創建自定義type有table/array等多種類型,需要使用哪種或者這兩種的區別可以在官方文檔中查找;
注意幾點:
1、採用自定義type的變量當作表用,不能直接返回給遊標,解決辦法是返回他的select查詢結果
2、同樣的這種變量不能和帶有其他變量的拼接sql一起執行;解決辦法就是執行拼接sql後把值給再into到這個變量中;
3、join等使用自定義type表時,table類型需要加上table()才能正常使用,直接使用是會報錯的;
4、官方的語句的MAP MEMBER FUNCTION get_idno RETURN NUMBER這句話,其實很重要,如果用不上可以不加,
但是他能做到一些非常棒的功能,比如你傳入出生年齡能夠返回傳入的出生年齡和歲數,具體歲數的實現就是用map跳轉到body裏面去實現的,這裏沒有用到body,就不做過多記錄,主要是body聊下來就太長了。
當然本次用到的自定義切割方法,不在此討論;