Procedure is not ending - oracle

I am very new to PLSQL and stack overflow. I've written PLSQL code to post dummy entries in my assignment. I've wrote following code to post entries using dynamic SQL (I've read about it on this forum and internet and tried to use it). One of the issue I'm facing is that this procedure is not ending/exiting. I've looked into it multiple times but code seems find to me.
Could you please suggest what needs to be updated so I can post entries successfully?
Thanks in advance for kind help.
PS: This is my first question here. Please let me know if any updates are required.
--package spec
CREATE OR REPLACE PACKAGE POST_ENTRIES AS
PROCEDURE POST_DAILY() ;
PROCEDURE IN_LEDGER_STAT_DAILY
(
V_IDENTITY_CODE NUMBER,
V_CONSOLIDATION_CD NUMBER,
V_FINANCIAL_ELEM_ID NUMBER,
V_ORG_UNIT_ID NUMBER,
V_GL_ACCOUNT_ID NUMBER,
V_COMMON_COA_ID NUMBER,
V_PRODUCT_1_ID NUMBER,
V_PRODUCT_ID NUMBER,
V_PRODUCT_3_ID NUMBER,
V_DATE DATE,
V_AMOUNT NUMBER,
V_MEMO_GL_ACCOUNT_ID NUMBER DEFAULT 0,
V_POSTINGTYPE CHAR DEFAULT 'N',
V_BALANCE_TYPE_CD NUMBER DEFAULT 0
);
END POST_ENTRIES;
/
--package body
CREATE OR REPLACE PACKAGE BODY POST_ENTRIES AS
PROCEDURE POST_DAILY() AS
--BASE VARIABLES
i number := 0;
DS NUMBER := 82;
j number;
V_NEXT_WORKING_DATE DATE;
V_IDENTITY_CODE CONSTANT NUMBER(6):=112233;
V_CONSOLIDATION_CD CONSTANT NUMBER(3):=100;
v_RECORDS_EXPECTED number;
v_RECORDS_ACTUAL number;
-- variables store result of dynamic cursor
V_SQL VARCHAR2(2500);
TYPE V_CURSOR IS REF CURSOR;
seq V_CURSOR;
V_ORG_UNIT_ID LEDGER_STAT_DLY.ORG_UNIT_ID%TYPE;
V_GL_ACCOUNT_ID LEDGER_STAT_DLY.GL_ACCOUNT_ID%TYPE;
V_CMN_COA_ID LEDGER_STAT_DLY. COMMON_COA_ID%TYPE;
V_PROD1 LEDGER_STAT_DLY.PRODUCT_1_ID%TYPE;
V_PROD2 LEDGER_STAT_DLY.PRODUCT_ID%TYPE;
V_PROD3 LEDGER_STAT_DLY.PRODUCT_3_ID%TYPE;
V_DATE DAILYGL.AS_OF_DATE%TYPE;
V_POSTED APPLICATION_TABLE_RECORD_COUNT.DLY_AS_OF_DATE%TYPE;
V_UPPER number;
V_DATE_PARAM DATE;
V_END_BAL NUMBER;
V_AVG_BAL NUMBER;
V_SWITCH NUMBER(1);
V_FLAG AL_LOOKUP.FLAG1%TYPE;
V_FIN AL_LOOKUP.FIN_ELEM%TYPE;
V_FIN1 AL_LOOKUP.FIN_ELEM%TYPE;
V_ACCT_TYPE NUMBER(2);
BEGIN
V_DATE:= '31-may-2019' -- this is getting returned from function but I've placed value here directly.
V_NEXT_WORKING_DATE := '03-Jun-2019' -- this is getting returned from function but I've placed value here directly.
V_DATE_PARAM := V_DATE ; --POSTING DATE, START WITH V_DATE IT WILL INCREMENT INSIDE WHILE LOOP. EXIT WHEN V_DATE_PARAM = V_NEXT_WORKING_DATE
------------------------------
WHILE V_DATE_PARAM < V_NEXT_WORKING_DATE
loop
V_SQL := 'SELECT A.ACCOUNT_TYPE, B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID, B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3,
SUM(CURRENT_BAL) AS CB_SUM, SUM(AVG_BAL) AS AB_SUM, B.FLAG1 FROM DAILYGL_TEST A, AL_LOOKUP B
WHERE A.GL_ACCOUNT_ID = B.GL_ACCT AND A.AS_OF_DATE = '||chr(39)||V_DATE||chr(39)||
' and rownum <=3 GROUP BY A.ACCOUNT_TYPE, B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID,B.CMN_COA_ID, B.PROD1,
B.PROD2, B.PROD3, A.AS_OF_DATE, B.FLAG1';
OPEN seq FOR V_SQL;
LOOP
V_SWITCH := 1;
FETCH seq INTO
V_ACCT_TYPE,
V_FIN,
V_ORG_UNIT_ID,
V_GL_ACCOUNT_ID,
V_CMN_COA_ID,
V_PROD1,
V_PROD2,
V_PROD3,
V_END_BAL,
V_AVG_BAL,
V_FLAG;
IF V_FLAG = 'Y' THEN
V_SWITCH := -1;
ELSE
V_SWITCH := 1;
END IF;
IF V_ACCT_TYPE = 5 OR V_ACCT_TYPE = 10 OR V_ACCT_TYPE = 20 OR V_ACCT_TYPE = 35 THEN
V_SWITCH := V_SWITCH * -1;
END IF;
IF (V_END_BAL <> 0 OR V_AVG_BAL <>0) THEN
IF V_ACCT_TYPE = 1 OR V_ACCT_TYPE = 5 OR V_ACCT_TYPE = 10 OR V_ACCT_TYPE = 90 THEN
V_FIN1 := SUBSTR(V_FIN,1,2) || '100';
IN_LEDGER_STAT_DAILY(V_IDENTITY_CODE,V_CONSOLIDATION_CD,V_FIN1,V_ORG_UNIT_ID,V_GL_ACCOUNT_ID,V_CMN_COA_ID,
V_PROD1, V_PROD2, V_PROD3,V_DATE_PARAM ,V_SWITCH * V_END_BAL);
IN_LEDGER_STAT_DAILY(V_IDENTITY_CODE,V_CONSOLIDATION_CD,V_FIN,V_ORG_UNIT_ID,V_GL_ACCOUNT_ID,V_CMN_COA_ID,
V_PROD1, V_PROD2, V_PROD3,V_DATE_PARAM,V_SWITCH * V_AVG_BAL);
ELSE
IN_LEDGER_STAT_DAILY(V_IDENTITY_CODE,V_CONSOLIDATION_CD,V_FIN,V_ORG_UNIT_ID,V_GL_ACCOUNT_ID,V_CMN_COA_ID,
V_PROD1, V_PROD2, V_PROD3,V_DATE_PARAM,V_SWITCH * V_END_BAL);
END IF;
END IF;
END LOOP;
CLOSE seq;
V_DATE_PARAM := V_DATE_PARAM +1;
end loop;
END POST_DAILY;
-------IN_LEDGER_STAT_DAILY procedure
PROCEDURE IN_LEDGER_STAT_DAILY
(
V_IDENTITY_CODE NUMBER,
V_CONSOLIDATION_CD NUMBER,
V_FINANCIAL_ELEM_ID NUMBER,
V_ORG_UNIT_ID NUMBER,
V_GL_ACCOUNT_ID NUMBER,
V_COMMON_COA_ID NUMBER,
V_PRODUCT_1_ID NUMBER,
V_PRODUCT_ID NUMBER,
V_PRODUCT_3_ID NUMBER,
V_DATE DATE,
V_AMOUNT NUMBER,
V_MEMO_GL_ACCOUNT_ID NUMBER DEFAULT 0,
V_POSTINGTYPE CHAR DEFAULT 'N',
V_BALANCE_TYPE_CD NUMBER DEFAULT 0
)
IS
V_CNT NUMBER;
V_D NUMBER;
V_DAY CHAR(6);
V_MONTH CHAR(2);
V_MO NUMBER;
V_YEAR_S NUMBER;
-- variables store result of dynamic cursor
V_SL VARCHAR2(2500);
V_TARGET_COLUMN VARCHAR2(6 CHAR);
k number := 0;
BEGIN
IF V_POSTINGTYPE = 'N' THEN
IF NVL(V_AMOUNT,0) <> 0 THEN
V_MO := (MONTH(V_DATE));
V_MONTH := LPAD(V_MO,2,'0');
V_YEAR_S := (YEAR(V_DATE));
V_D := (DAY(V_DATE));
V_DAY := 'DAY_' || lpad(V_D, 2, '0');
SELECT /*+ index(a LEDGER_STAT_DLY_IDX02_IN) */
COUNT(*) INTO V_CNT
FROM LEDGER_STAT_DLY A
WHERE
IDENTITY_CODE = NVL(V_IDENTITY_CODE,0)
AND YEAR_S = NVL(V_YEAR_S,0)
AND MONTH_NO = NVL(V_MONTH,0)
AND CONSOLIDATION_CD = NVL(V_CONSOLIDATION_CD,0)
AND FINANCIAL_ELEM_ID = NVL(V_FINANCIAL_ELEM_ID,0)
AND ORG_UNIT_ID = NVL(V_ORG_UNIT_ID,0)
AND GL_ACCOUNT_ID = NVL(V_GL_ACCOUNT_ID,0)
AND COMMON_COA_ID = NVL(V_COMMON_COA_ID,0)
AND PRODUCT_1_ID = NVL(V_PRODUCT_1_ID,0)
AND PRODUCT_ID = NVL(V_PRODUCT_ID,0)
AND PRODUCT_3_ID = NVL(V_PRODUCT_3_ID,0)
AND COST_TYPE_ID = NVL(V_MEMO_GL_ACCOUNT_ID,0)
AND BALANCE_TYPE_CD = NVL(V_BALANCE_TYPE_CD,0) ;
IF V_CNT = 0 THEN
INSERT INTO LEDGER_STAT_DLY VALUES(
NVL(V_IDENTITY_CODE,0),
NVL(V_YEAR_S,0) ,
NVL(V_MONTH,0) ,
'D',
NVL(V_CONSOLIDATION_CD,0),
NVL(V_FINANCIAL_ELEM_ID,0),
NVL(V_ORG_UNIT_ID,0) ,
NVL(V_GL_ACCOUNT_ID,0) ,
NVL(V_COMMON_COA_ID,0) ,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
NVL(V_PRODUCT_1_ID,0) ,
NVL(V_PRODUCT_ID,0) ,
NVL(V_PRODUCT_3_ID,0) ,
TRUNC(NVL(V_ORG_UNIT_ID,0) / 10000000),
'USD', -- ISO_CURRENCY_CD
NULL, -- CURRENCY_TYPE_CD
NVL(V_BALANCE_TYPE_CD,0),
0, -- VOLUME_ID
NVL(V_MEMO_GL_ACCOUNT_ID,0), -- COST_TYPE_ID
0 -- CHANNEL_ID
);
END IF;
EXECUTE IMMEDIATE UTL_LMS.FORMAT_MESSAGE('UPDATE /*+ index(a LEDGER_STAT_DLY_IDX02_IN) */ LEDGER_STAT_DLY A
SET %s = NVL(%s,0) + NVL(:THE_AMOUNT,0)
WHERE IDENTITY_CODE = NVL(:THE_IDENTITY_CODE,0)
AND YEAR_S = NVL(:THE_YEAR_S,0)
AND MONTH_NO = NVL(:THE_MONTH,0)
AND CONSOLIDATION_CD = NVL(:THE_CONSOLIDATION_CD,0)
AND FINANCIAL_ELEM_ID = NVL(:THE_FINANCIAL_ELEM_ID,0)
AND ORG_UNIT_ID = NVL(:THE_ORG_UNIT_ID,0)
AND GL_ACCOUNT_ID = NVL(:THE_GL_ACCOUNT_ID,0)
AND COMMON_COA_ID = NVL(:THE_COMMON_COA_ID,0)
AND PRODUCT_1_ID = NVL(:THE_PRODUCT_1_ID,0)
AND PRODUCT_ID = NVL(:THE_PRODUCT_ID,0)
AND PRODUCT_3_ID = NVL(:THE_PRODUCT_3_ID,0)
AND COST_TYPE_ID = NVL(:THE_MEMO_GL_ACCOUNT_ID,0)
AND BALANCE_TYPE_CD = NVL(:THE_BALANCE_TYPE_CD,0)', V_DAY, V_DAY)
USING V_AMOUNT, V_IDENTITY_CODE, V_YEAR_S, V_MONTH, V_CONSOLIDATION_CD, V_FINANCIAL_ELEM_ID, V_ORG_UNIT_ID,
V_GL_ACCOUNT_ID, V_COMMON_COA_ID, V_PRODUCT_1_ID, V_PRODUCT_ID, V_PRODUCT_3_ID, V_MEMO_GL_ACCOUNT_ID, V_BALANCE_TYPE_CD;
END IF;
END IF;
END IN_LEDGER_STAT_DAILY;
END POST_ENTRIES ;
/

The loop in your first procedure never exits; you need to add something like
EXIT WHEN seq%NOTFOUND;
i.e:
...
OPEN seq FOR V_SQL;
LOOP
V_SWITCH := 1;
FETCH seq INTO
V_ACCT_TYPE,
V_FIN,
V_ORG_UNIT_ID,
V_GL_ACCOUNT_ID,
V_CMN_COA_ID,
V_PROD1,
V_PROD2,
V_PROD3,
V_END_BAL,
V_AVG_BAL,
V_FLAG;
EXIT WHEN seq%NOTFOUND;
IF V_FLAG = 'Y' THEN
...
As mentioned in comments, the cursor query in that procedure doesn't need to be dynamic; and you should be using actual dates rather than strings that then rely on implicit conversion and NLS settings - or at a minimum, explicit conversion between strings and dates.

Related

Q: Detecting Insert/Update/Delete on a Table using OCN and pass the "real_id" to UTL_HTTP.REQUEST on the callback?

I'm trying to detect Insert, Update or Delete on a Table. I know I can't use QRCN for this so I'm trying to use OCN. What I'm trying to achieve is... when an Insert/Update is detected call "callback_iu" when a Delete is detected call 'callback_d'.
DECLARE
qosflags NUMBER;
reginfo_iu cq_notification$_reg_info;
reginfo_d cq_notification$_reg_info;
regid_iu NUMBER;
regid_d NUMBER;
v_cursor SYS_REFCURSOR;
opfilter_iu NUMBER;
opfilter_d NUMBER;
BEGIN
qosflags := DBMS_CQ_NOTIFICATION.QOS_ROWIDS;
opfilter_iu := DBMS_CQ_NOTIFICATION.INSERTOP + DBMS_CQ_NOTIFICATION.UPDATEOP;
opfilter_d := DBMS_CQ_NOTIFICATION.DELETEOP;
reginfo_iu := cq_notification$_reg_info('callback_iu', qosflags,0, opfilter_iu, 0);
regid_iu := DBMS_CQ_NOTIFICATION.NEW_REG_START(reginfo_iu);
OPEN v_cursor FOR
SELECT DBMS_CQ_NOTIFICATION.QOS_ROWIDS, department_id, department_name, manager_id, location_id
FROM HR.departments
CLOSE v_cursor;
DBMS_CQ_NOTIFICATION.REG_END;
reginfo_d := cq_notification$_reg_info('callback_d', qosflags,0, opfilter_d, 0);
regid_d := DBMS_CQ_NOTIFICATION.NEW_REG_START(reginfo_d);
OPEN v_cursor FOR
SELECT DBMS_CQ_NOTIFICATION.QOS_ROWIDS, department_id, department_name, manager_id, location_id
FROM HR.departments
CLOSE v_cursor;
DBMS_CQ_NOTIFICATION.REG_END;
END;
I know that I can get ROWID and the "real_id" using QRCN like this:
CREATE OR REPLACE PROCEDURE chnf_callback
(ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR)
IS
event_type NUMBER;
tbname VARCHAR2(60);
numtables NUMBER;
operation_type NUMBER;
numrows NUMBER;
row_id VARCHAR2(2000);
numqueries NUMBER;
qid NUMBER;
real_id NUMBER;
BEGIN
event_type := ntfnds.event_type;
numqueries :=0;
IF (event_type = DBMS_CQ_NOTIFICATION.EVENT_QUERYCHANGE) THEN
numqueries := ntfnds.query_desc_array.count;
FOR i in 1..numqueries LOOP
qid := ntfnds.QUERY_DESC_ARRAY(i).queryid;
numtables := 0;
numtables := ntfnds.QUERY_DESC_ARRAY(i).table_desc_array.count;
FOR j IN 1..numtables LOOP
tbname := ntfnds.QUERY_DESC_ARRAY(i).table_desc_array(j).table_name;
operation_type := ntfnds.QUERY_DESC_ARRAY(i).table_desc_array(j).Opflags;
IF (bitand(operation_type, DBMS_CQ_NOTIFICATION.ALL_ROWS) = 0)
THEN
numrows := ntfnds.query_desc_array(i).table_desc_array(j).numrows;
ELSE
numrows :=0; /* ROWID INFO NOT AVAILABLE */
END IF;
/* The body of the loop is not executed when numrows is ZERO */
FOR k IN 1..numrows LOOP
Row_id := ntfnds.query_desc_array(i).table_desc_array(j).row_desc_array(k).row_id;
select department_id into real_id from hr.departments where rowid = Row_id;
-->INSERT IN NFROWCHANGES<--
INSERT INTO nfrowchanges VALUES(qid, tbname, Row_id, real_id);
END LOOP; /* loop over rows */
END LOOP; /* loop over tables */
END LOOP; /* loop over queries */
END IF;
COMMIT;
END;
I also know that I can check operation_type to know if it's an Insert (2) or Update (4), but I can't make it work with Delete.
So how can I get ROWID and the "real_id" using OCN and pass it to UTL_HTTP.REQ on the callback?
DECLARE req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
BEGIN
req := utl_http.begin_request(
url => 'localhost:3000/departments/'||real_id,
method => 'GET'
);
resp := utl_http.get_response(r => req);
utl_http.end_response(r => resp);
END;
I still don't know how to work with OCN, in this solution I'm using QRCN. Since I can't get row_id when a row is deleted, instead of deleting the row I created a new column called "Active", so when I want to "delete" a row I change "Active" from "Yes" to "No".
So here is the solution to send the "real_id" from the Inserted/Updated/"Deleted" row using UTL_HTTP.
anonymous block:
DECLARE
l_reginfo CQ_NOTIFICATION$_REG_INFO;
l_cursor SYS_REFCURSOR;
l_regid NUMBER;
qosflags NUMBER;
BEGIN
qosflags := DBMS_CQ_NOTIFICATION.QOS_QUERY + DBMS_CQ_NOTIFICATION.QOS_ROWIDS;
l_reginfo := cq_notification$_reg_info ('query_callback', qosflags, 0, 0, 0);
l_regid := dbms_cq_notification.new_reg_start(l_reginfo);
OPEN l_cursor FOR
SELECT
id,
city_name,
country_name,
votes,
active
FROM hr.jsao_super_cities
WHERE active = 'YES';
CLOSE l_cursor;
dbms_cq_notification.reg_end;
END;
/
callback:
CREATE OR REPLACE PROCEDURE query_callback
(ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR)
IS
event_type NUMBER;
numtables NUMBER;
operation_type NUMBER;
numrows NUMBER;
row_id VARCHAR2(2000);
is_active VARCHAR2(20);
numqueries NUMBER;
real_id NUMBER;
l_req UTL_HTTP.REQ;
l_resp UTL_HTTP.RESP;
BEGIN
event_type := ntfnds.event_type;
numqueries :=0;
IF (event_type = DBMS_CQ_NOTIFICATION.EVENT_QUERYCHANGE) THEN
numqueries := ntfnds.query_desc_array.count;
FOR i in 1..numqueries LOOP
numtables := 0;
numtables := ntfnds.QUERY_DESC_ARRAY(i).table_desc_array.count;
FOR j IN 1..numtables LOOP
operation_type := ntfnds.QUERY_DESC_ARRAY(i).table_desc_array(j).Opflags;
IF (bitand(operation_type, DBMS_CQ_NOTIFICATION.ALL_ROWS) = 0)
THEN
numrows := ntfnds.query_desc_array(i).table_desc_array(j).numrows;
ELSE
numrows :=0;
END IF;
FOR k IN 1..numrows LOOP
Row_id := ntfnds.query_desc_array(i).table_desc_array(j).row_desc_array(k).row_id;
--getting "real_id"
select id into real_id from hr.jsao_super_cities where rowid = Row_id;
-- 2 = insert
IF(operation_type = 2) THEN
l_req := utl_http.begin_request(
url => 'localhost:3000/city/'||real_id,
method => 'GET'
);
l_resp := utl_http.get_response(r => l_req);
utl_http.end_response(r => l_resp);
-- 4 = update
ELSIF (operation_type = 4) THEN
select active into is_active from hr.jsao_super_cities where id = real_id;
IF (is_active = 'YES') THEN
l_req := utl_http.begin_request(
url => 'localhost:3000/city/'||real_id,
method => 'GET'
);
l_resp := utl_http.get_response(r => l_req);
utl_http.end_response(r => l_resp);
ELSIF (is_active = 'NO') THEN
l_req := utl_http.begin_request(
url => 'localhost:3000/delete/'||real_id,
method => 'GET'
);
l_resp := utl_http.get_response(r => l_req);
utl_http.end_response(r => l_resp);
END IF;
END IF;
END LOOP; /* loop over rows */
END LOOP; /* loop over tables */
END LOOP; /* loop over queries */
END IF;
END query_callback;
/
I hope this can help someone else.

Diffing 2 rows of 2 tables based on column values (without prior knowledge of columns names)

I don't even know if it is possible - so any idea will be welcome:
I want a function, which will return a string(varchar2) representation of the differences in values of columns of 2 specific tables.
So it's task will be to
find difference between 2 rows that belong to 2 tables (which happens
to have the same structure).
Consider the following scenario.
Table A (A_rowid,col1,col2,col3,col4,col5...,coln) and values (id1,val1,val2,val3,..,valn)
Table B (B_rowid,col1,col2,col3,col4,col5...,coln) and values (id2,val1,val2',val3,..,valn')
*A_rowid - unique key of tableA, B_rowid - unique key of table B
fnction diff(A_rowid number, B_rowid number) returns varchar2 is
begin
--do something
end;
All columns of the tables could be treated as Varchar2.
Thus,
The expected output would be -->
Null if no difference is found
or
diff: col2:val2->val2', coln:valn->valn'
What is important here is that I would like to do that without hard coding column names
(table names are hard coded though).
e.g. if and when we add additional columns to our tables - function should still work.
You can use this one:
FUNCTION diff(A_rowid NUMBER, B_rowid NUMBER) RETURN VARCHAR2 IS
CURSOR TabColumns IS
SELECT COLUMN_NAME, COLUMN_ID
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'TABLE_A'
AND COLUMN_NAME <> 'A_ROWID'
ORDER BY COLUMN_ID;
sqlstr VARCHAR2(1000);
val_a VARCHAR2(4000);
val_b VARCHAR2(4000);
res VARCHAR2(30000);
BEGIN
FOR aCol IN TabColumns LOOP
BEGIN
sqlstr := 'SELECT a.'||aCol.COLUMN_NAME||', b.'||aCol.COLUMN_NAME;
sqlstr := sqlstr ||' FROM TABLE_A a CROSS JOIN TABLE_B b ';
sqlstr := sqlstr || ' WHERE A_rowid = :aRow AND B_rowid = :bRow ';
sqlstr := sqlstr || ' AND LNNVL(a.'||aCol.COLUMN_NAME||' = b.'||aCol.COLUMN_NAME||') ';
sqlstr := sqlstr || ' AND COALESCE(a.'||aCol.COLUMN_NAME||', b.'||aCol.COLUMN_NAME||') IS NOT NULL ';
EXECUTE IMMEDIATE sqlstr INTO val_a, val_b USING A_rowid, B_rowid;
res := res ||', '||aCol.COLUMN_NAME||':'||val_a||'->'||val_b;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
END LOOP;
RETURN REGEXP_REPLACE(res, '^, ', 'diff:');
END;
Note, function LNNVL(a.'||aCol.COLUMN_NAME||' = b.'||aCol.COLUMN_NAME||') is needed in case of NULL values.
Condition a.COLUMN_NAME <> b.COLUMN_NAME does return anything when one of the values is NULL.
LNNVL(a.COLUMN_NAME = b.COLUMN_NAME) is equivalent to
( a.COLUMN_NAME <> b.COLUMN_NAME
OR (a.COLUMN_NAME IS NULL AND b.COLUMN_NAME IS NOT NULL)
OR (a.COLUMN_NAME IS NOT NULL AND b.COLUMN_NAME IS NULL) )
However, use function above only if you are not concerned about performance. The more advanced solution would be this one:
FUNCTION diff(A_rowid NUMBER, B_rowid NUMBER) RETURN VARCHAR2 IS
CURSOR TabColumns IS
SELECT COLUMN_NAME, COLUMN_ID
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'TABLE_A'
AND COLUMN_NAME <> 'A_ROWID'
ORDER BY COLUMN_ID;
sqlstr VARCHAR2(10000);
val_a VARCHAR2(4000);
val_b VARCHAR2(4000);
res VARCHAR2(30000);
cur INTEGER;
p INTEGER;
res INTEGER;
BEGIN
sqlstr := 'SELECT '
FOR aCol IN TabColumns LOOP
sqlstr := ' a.'||aCol.COLUMN_NAME||'_A, b.'||aCol.COLUMN_NAME||'_B, ';
END LOOP;
sqlstr := REGEXP_REPLACE(sqlstr, ', $', ' FROM TABLE_A a CROSS JOIN TABLE_B b ');
sqlstr := sqlstr || ' WHERE A_rowid = :aRow AND B_rowid = :bRow ';
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE (cur, ':aRow', A_rowid);
DBMS_SQL.BIND_VARIABLE (cur, ':bRow', B_rowid);
p := 1;
FOR aCol IN TabColumns LOOP
DBMS_SQL.DEFINE_COLUMN(cur, p, aCol.COLUMN_NAME||'_A', 4000);
DBMS_SQL.DEFINE_COLUMN(cur, p+1, aCol.COLUMN_NAME||'_B', 4000);
p := p + 2;
END LOOP;
res := DBMS_SQL.EXECUTE_AND_FETCH(cur, TRUE);
p := 1;
FOR aCol IN TabColumns LOOP
DBMS_SQL.COLUMN_VALUE(cur, p, val_a);
DBMS_SQL.COLUMN_VALUE(cur, p+1, val_b);
p := p + 2;
IF val_a <> val_b OR (val_a IS NULL AND val_b IS NOT NULL) OR (val_a IS NOT NULL AND val_b IS NULL) THEN
res := res ||', '||aCol.COLUMN_NAME||':'||val_a||'->'||val_b;
END IF;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cur);
RETURN REGEXP_REPLACE(res, '^, ', 'diff:');
END;
Try this function
FUNCTION getdiff(arowid varchar, browid varchar) RETURN CLOB IS
v_line clob;
v_col_cnt INTEGER;
v_ind NUMBER;
rec_tab dbms_sql.desc_tab;
v_cursor NUMBER;
v_sql clob;
V_FIRST clob;V_SECOND CLOB;
V_FINAL CLOB;
begin
V_SQL := Q'$ select * from(select * from Table1 where rowid=:arowid)a,
(select * from Table2 where rowid=:browid)b $';
V_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(V_CURSOR, V_SQL, DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE (V_CURSOR, ':arowid', arowid);
DBMS_SQL.BIND_VARIABLE (V_CURSOR, ':browid', browid);
DBMS_SQL.DESCRIBE_COLUMNS(V_CURSOR, V_COL_CNT, REC_TAB);
FOR V_POS IN 1 .. REC_TAB.LAST LOOP
V_LINE := REC_TAB(V_POS).COL_NAME;
DBMS_SQL.DEFINE_COLUMN(V_CURSOR, V_POS, V_LINE);
END LOOP;
V_IND := DBMS_SQL.EXECUTE(V_CURSOR);
LOOP
V_IND := DBMS_SQL.FETCH_ROWS(V_CURSOR);
EXIT WHEN V_IND = 0;
FOR V_COL_SEQ IN 1 .. REC_TAB.COUNT LOOP
if v_col_seq <=V_COL_CNT/2 then
DBMS_SQL.COLUMN_VALUE(V_CURSOR, V_COL_SEQ, V_LINE);
V_FIRST := V_LINE;
DBMS_SQL.COLUMN_VALUE(V_CURSOR, V_COL_SEQ+3, V_LINE);
V_SECOND := V_LINE;
IF V_FIRST <> V_SECOND THEN
V_FINAL := V_FINAL || rec_tab(v_col_seq).col_name || ':' || V_FIRST ||'->'||V_SECOND || ',';
END IF;
end if;
END LOOP;
END LOOP;
RETURN V_FINAL;
end;
output of getdiff function is in clob format because limit of varchar2 datatype is 32767 so after reach limit function give us error.
Use:
select to_char(getdiff('AAAjOuAAEAAA697AAC','AAAjOuAAEAAA697AAk')) from dual;
here to_char function is used for convert clob data format to char,so thats give us perfect string output.

Only one row added "ORA-02290: check constraint (SYS_C0012762) violated ORA-01403: no data found"

I created a package which loads data from a staging table to a live table in Oracle DB. The package comprises of four functions which perform live load, update, insert and error check on the records. For some reason, I am only able to load one record from the staging table and then the following error occurs:
ORA-02290: check constraint (VIQDATA.SYS_C0012762) violated ORA-01403: no data found
create or replace PACKAGE VAL_PROC
AS
pi_sif_id NUMBER := 0;
CURSOR v_d_c (pi_sif_id NUMBER)
IS
SELECT *
FROM S_V_C_R
WHERE SIF_ID =PI_SIF_ID
AND SVC_PROCESS_FLAG='N';
TYPE v_d_c_t IS TABLE OF v_d_c%ROWTYPE;
FUNCTION vr_live_load (pi_sif_id NUMBER,
po_err_msg OUT VARCHAR2,
po_success_count OUT NUMBER,
po_failed_count OUT NUMBER)
RETURN NUMBER;
FUNCTION check_error_record (p_v_d_r IN vr_dtl_cur%ROWTYPE,
lv_prc_code OUT VARCHAR2,
lv_err_flag OUT VARCHAR2)
RETURN NUMBER;
FUNCTION insert_v (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2, po_prc_code OUT VARCHAR2)
RETURN NUMBER;
FUNCTION update_v (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2, po_prc_code OUT VARCHAR2)
RETURN NUMBER;
END VAL_PROC;
create or replace PACKAGE BODY VAL_PROC
AS
LV_X1_ID SERIALS.X1_ID%TYPE;
LV_VC_ID VR_COM.X1_ID%TYPE; --change name of this variable later
FUNCTION vr_live_load(pi_sif_id NUMBER, --live load function
po_err_msg OUT VARCHAR2,
po_success_count OUT NUMBER,
po_failed_count OUT NUMBER)
RETURN NUMBER
IS
v_d_r v_d_c_tbl;
dup_prc NUMBER := 0;
E_DUP_PRC EXCEPTION;
RETVAL NUMBER;
pi_d_co VARCHAR2(10);
ERR_FLAG VARCHAR2 (1);
ret_prc_code VARCHAR2 (15);
lv_err_code VARCHAR2 (1000);
lv_found NUMBER;
lv_PRS_ID NUMBER;
lv_sif_filename VARCHAR2 (200);
lv_proc_success_count NUMBER := 0;
lv_proc_failed_count1 NUMBER := 0;
BEGIN
BEGIN
SELECT COUNT (1)
INTO DUP_PRC
FROM s_fi
WHERE SIF_ID = PI_SIF_ID AND s_p_f = 'P';
IF dup_prc > 0
THEN
RAISE e_dup_prc;
END IF;
SELECT sif_filename
INTO lv_sif_filename
FROM s_fi
WHERE sif_id = pi_sif_id;
INSERT INTO p_s (PCG_CODE,
SIF_ID,
PRS_START_DATETIME,
sif_filename)
VALUES ('V_ODL',
pi_sif_id,
SYSDATE,
lv_sif_filename)
RETURNING PRS_ID
INTO lv_PRS_ID;
COMMIT;
OPEN v_d_c(pi_sif_id);
LOOP
FETCH v_d_c
BULK COLLECT INTO v_d_r
LIMIT 1000;
FOR i IN 1 .. v_d_r.COUNT
LOOP
retval :=
check_error_record (p_v_d_r => v_d_r(i),
lv_prc_code => ret_prc_code,
lv_err_flag => err_flag);
IF retval = 0
THEN
IF ERR_FLAG = 'N'
THEN
BEGIN
SELECT 1
INTO lv_found
FROM VR_COM
WHERE X1_ID = LV_X1_ID; --v_d_r(i).SVC_d_co = D.X1_ID;
retval :=
update_vr (p_v_d_r => v_d_r (i),
po_err_msg => lv_err_code,
po_prc_code => ret_prc_code);
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'P'
WHERE S_ID = v_d_r (i).S_ID;
IF retval = 0
THEN
lv_proc_success_count := lv_proc_success_count + 1;
ELSE
lv_proc_failed_count1 := lv_proc_failed_count1 + 1;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
retval :=
insert_v (
p_v_d_r => v_d_r (i),
po_err_msg => lv_err_code,
po_prc_code => ret_prc_code);
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'P'
WHERE S_ID = v_d_r(i).S_ID;
IF retval = 0
THEN
lv_proc_success_count :=
lv_proc_success_count + 1;
ELSE
lv_proc_failed_count1 :=
lv_proc_failed_count1 + 1;
END IF;
END;
ELSE /*update table with prc_code when err_flag <> 'N' */
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'E'
WHERE S_ID = v_d_r (i).S_ID;
lv_proc_failed_count1 := lv_proc_failed_count1 + 1;
END IF;
ELSE --if check_error_record return non-zero value
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'E'
WHERE S_ID = v_d_r (i).S_ID;
lv_proc_failed_count1 := lv_proc_failed_count1 + 1;
END IF;
COMMIT;
END LOOP;
EXIT WHEN v_d_c%NOTFOUND;
END LOOP;
CLOSE v_d_c;
COMMIT;
EXCEPTION
WHEN e_dup_prc
THEN --file already processed
UPDATE s_fi
SET s_p_f = 'E'
WHERE sif_id = pi_sif_id;
COMMIT;
po_err_msg := 'This File has previously been processed.';
END;
UPDATE s_fi
SET s_p_f = 'P'
WHERE sif_id = pi_sif_id;
UPDATE p_s
SET PLS_END_DATETIME = SYSDATE, PRS_ERRORED = 'Successful'
WHERE PRS_ID = lv_PRS_ID;
--Populate file load summary
BEGIN
p_p_f_l_s (P_SIF_ID => pi_sif_id);
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
COMMIT;
po_success_count := lv_proc_success_count;
po_failed_count := lv_proc_failed_count1;
RETURN 0;
EXCEPTION
WHEN OTHERS
THEN
lv_err_code := SQLERRM;
UPDATE s_fi
SET s_p_f = 'E'
WHERE sif_id = pi_sif_id;
UPDATE p_s
SET PLS_END_DATETIME = SYSDATE, PRS_ERRORED = lv_err_code
WHERE PRS_ID = lv_PRS_ID;
COMMIT;
po_err_msg := SQLERRM;
CLOSE v_d_c;
RETURN SQLCODE;
END vr_live_load;
FUNCTION insert_v (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2,po_prc_code OUT VARCHAR2) --inserts records to live table
RETURN NUMBER
IS
RETNUM number := 0;
begin
INSERT INTO VR_COM (VC_ID,
co_pe,
COUNTRY_CODE,
X1_ID,
TIME_PERIOD
)
VALUES (VC_ID_SQ.NEXTVAL,
p_v_d_r.s_perc,
p_v_d_r.s_coun,
(select X1_ID from SERIALS where d_co=p_v_d_r.SVC_d_co),
p_v_d_r.s_time
);
po_prc_code := 'S11';
RETURN retnum;
EXCEPTION
WHEN OTHERS
THEN
PO_ERR_MSG := SQLERRM;
PO_PRC_CODE := 'O11';
DBMS_OUTPUT.PUT_LINE('SQLERRM :'||SQLERRM);
RETURN SQLCODE;
END insert_v;
FUNCTION update_vr (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2, po_prc_code OUT VARCHAR2) --update the live table if data matches by ID
RETURN NUMBER
IS
LV_d_co SERIALS.d_co%TYPE;
BEGIN
SELECT VRC.X1_ID, D.d_co
INTO LV_VC_ID, LV_d_co
FROM VR_COM VRC, SERIALS D
WHERE VRC.X1_ID = D.X1_ID;
update VR_COM
set co_pe = p_v_d_r.s_perc,
COUNTRY_CODE = p_v_d_r.s_coun,
TIME_PERIOD = p_v_d_r.s_time
WHERE X1_ID = LV_VC_ID;
return 0;
EXCEPTION
WHEN OTHERS
THEN
po_err_msg := SQLERRM;
PO_PRC_CODE := 'O11';
DBMS_OUTPUT.PUT_LINE('SQLERRM :'||SQLERRM);
RETURN SQLCODE;
END update_vr;
FUNCTION check_error_record (p_v_d_r IN v_d_c%ROWTYPE,
lv_prc_code OUT VARCHAR2,
lv_err_flag OUT VARCHAR2) --check error
RETURN NUMBER
IS
--LV_d_co SERIALS.d_co%TYPE;
retnum NUMBER := 0;
lv_found VARCHAR2 (10);
LV_FLAG varchar2 (2);
LV_PROC_FLAG VARCHAR2 (2);
BEGIN
lv_prc_code := NULL;
lv_err_flag := NULL;
BEGIN
IF LV_X1_ID IS NOT NULL THEN
SELECT d_co, d_a_s_c
INTO LV_X1_ID, LV_FLAG
FROM SERIALS
WHERE p_v_d_r.SVC_d_co =d_co;
END IF;
lv_err_flag := 'N';
EXCEPTION
WHEN NO_DATA_FOUND
THEN
lv_prc_code := 'P5';
lv_err_flag := 'Y';
lv_proc_flag := 'E';
END;
IF p_v_d_r.SVC_d_co IS NULL
THEN
lv_prc_code := 'V2';
lv_err_flag := 'Y';
ELSIF p_v_d_r.s_perc IS NULL
THEN
lv_prc_code := 'V6';
lv_err_flag := 'Y';
ELSIF p_v_d_r.s_time IS NULL
THEN
lv_prc_code := 'V4';
lv_err_flag := 'Y';
ELSIF p_v_d_r.s_coun IS NULL
THEN
lv_prc_code := 'V1';
lv_err_flag := 'Y';
ELSE
lv_prc_code := 'S11';
lv_err_flag := 'N';
end if;
RETURN retnum;
EXCEPTION
WHEN OTHERS
THEN
lv_err_flag := 'Y';
RETURN SQLCODE;
END check_error_record;
END VAL_PROC;
P.S SYS_C0012762 constraint Check SVC_PROCESS_FLAG IN ('E', 'N', 'S')
Looks like you are trying to introduce a new value into that flag.
At the moment, the database table only allows a value of E, N or S in the column SVC_PROCESS_FLAG.
If you're trying to put a value of P in that field, you would need to replace the check constraint to also allow it as valid.
how to modify an existing check constraint?

Can I iterate over columns of a composite type?

Let's say I have the following table named bar:
key | columnA | columnB | columnC
A | B | C | D
E | F | G | H
I want to write a function taking a key and a string and doing the following (best described by examples):
Input: ('A', '${columnB} - ${columnA}') / Output : 'C - B'
Input: ('B', 'Hello ${columnC}') / Output: 'Hello H'
For the moment, I have this implementation:
CREATE OR REPLACE FUNCTION foo
( param_key IN VARCHAR2
, format_string IN VARCHAR2
)
RETURN VARCHAR2
IS
my_row bar%ROWTYPE;
retval VARCHAR2(4000);
BEGIN
BEGIN SELECT * INTO my_row FROM bar WHERE "key" = param_key;
EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL;
END;
retval := format_string;
retval := REPLACE(retval, '${columnA}', my_row.columnA);
retval := REPLACE(retval, '${columnB}', my_row.columnB);
retval := REPLACE(retval, '${columnC}', my_row.columnC);
RETURN retval;
END;
/
I would like to avoid enumerating all columns one by one in the last part, because the structure of my table can change (new columns for instance). Is there a way to iterate on all columns of my_row, and to replace ${the column name} with the value stored in that column, in a generic way?
Thank you
Another way to achieve this.
Create xmltype from table row.
Create xsl-transform from format_string.
Transform xml using xsl
declare
v_string_format varchar2(200) := '{columnA} + {columnB} + {columnA}{columnB}';
v_key varchar2(10) := 'A';
v_cursor sys_refcursor;
l_xml xmltype;
v_xslt VARCHAR2(500):='<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/ROWSET/ROW">{patern}</xsl:template></xsl:stylesheet>';
begin
-- create xsl transform
v_string_format := upper(v_string_format);
v_string_format := REPLACE(v_string_format,'{','<xsl:value-of select="');
v_string_format := REPLACE(v_string_format,'}','"/>');
v_xslt := replace(v_xslt,'{patern}',v_string_format);
dbms_output.put_line(v_string_format);
-- open cursor for table
open v_cursor for select * from bar where key = v_key;
-- get v_cursor as xmltype.
l_xml := xmltype(v_cursor);
-- print xml
dbms_output.put_line(l_xml.getClobVal());
-- tranform xml and print result
dbms_output.put_line(l_xml.transform(xmltype(v_xslt)).getClobVal());
close v_cursor;
end;
A more efficient solution is this one. For sure you have to write more code and it uses the full scope of dynamic SQL.
CREATE OR REPLACE FUNCTION foo (param_key IN VARCHAR2, format_string IN VARCHAR2)
RETURN VARCHAR2 IS
retval VARCHAR2(4000) := format_string;
cur SYS_REFCURSOR;
curId INTEGER;
descTab DBMS_SQL.DESC_TAB;
colCnt NUMBER;
numvar NUMBER;
datevar DATE;
namevar VARCHAR2(4000);
tsvar TIMESTAMP;
BEGIN
OPEN cur FOR SELECT * FROM bar WHERE "key" = param_key;
curId := DBMS_SQL.TO_CURSOR_NUMBER(cur);
DBMS_SQL.DESCRIBE_COLUMNS(curId, colCnt, descTab);
-- Define columns
FOR i IN 1..colcnt LOOP
IF desctab(i).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, numvar);
ELSIF desctab(i).col_type = DBMS_TYPES.TYPECODE_DATE THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
ELSIF desctab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, tsvar);
ELSIF desctab(i).col_type = DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar, 4000);
--ELSIF desctab(i).col_type = ... THEN
--DBMS_SQL.DEFINE_COLUMN(curid, i, ...);
END IF;
END LOOP;
-- Fetch Rows
IF DBMS_SQL.FETCH_ROWS(curid) > 0 THEN
-- Fetch only the first row and do not consider if further rows exist,
-- otherwise use WHILE DBMS_SQL.FETCH_ROWS(curid) > 0 LOOP
FOR i IN 1..colcnt LOOP
IF desctab(i).col_type = DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DBMS_SQL.COLUMN_VALUE(curid, i, namevar);
retval := REPLACE(retval, '${'||desctab(i).col_name||'}', namevar);
ELSIF desctab(i).col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
DBMS_SQL.COLUMN_VALUE(curid, i, numvar);
retval := REPLACE(retval, '${'||desctab(i).col_name||'}', numvar);
ELSIF desctab(i).col_type = DBMS_TYPES.TYPECODE_DATE THEN
DBMS_SQL.COLUMN_VALUE(curid, i, datevar);
retval := REPLACE(retval, '${'||desctab(i).col_name||'}', datevar);
ELSIF desctab(i).col_type = DBMS_TYPES.TYPECODE_TIMESTAMP THEN
DBMS_SQL.COLUMN_VALUE(curid, i, tsvar);
retval := REPLACE(retval, '${'||desctab(i).col_name||'}', tsvar);
--ELSIF desctab(i).col_type = ... THEN
--DBMS_SQL.COLUMN_VALUE(curid, i, ...);
--retval := REPLACE(retval, '${'||desctab(i).col_name||'}', ...);
END IF;
END LOOP;
ELSE
retval := NULL;
END IF;
DBMS_SQL.CLOSE_CURSOR(curId);
RETURN retval;
END;
You can get the result you are after using dynamic queries...
CREATE OR REPLACE FUNCTION foo
( param_key IN VARCHAR2
, format_string IN VARCHAR2
)
RETURN VARCHAR2
IS
retval VARCHAR2(4000) := format_string;
cols SYS.ODCIVARCHAR2LIST;
BEGIN
SELECT COLUMN_NAME
BULK COLLECT INTO cols
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'bar'
ORDER BY COLUMN_ID;
FOR i IN 1 .. cols.COUNT LOOP
EXECUTE IMMEDIATE 'SELECT REPLACE( :1, ''${' || cols(i) || '}'', ' || cols(i) || ' ) FROM bar WHERE key = :2'
INTO retval
USING retval, param_key;
END LOOP;
RETURN retval;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
/
... but:
This uses dynamic SQL to query the table directly and does not use a %ROWTYPE record.
You may not have access to USER_TAB_COLUMNS (or may need ALL_TAB_COLUMNS) and the DBA might not want you to have access to the data dictionary tables.
It is probably (almost certainly) very inefficient.
I've seen this done before and never let it pass a code review (writing out the explicit column names has always seemed preferable).
So, while it is possible, I would say don't do this.

Concatenation of CLOB datatypes in a LOOP in PL/SQL

I am trying to concatenate clobs in a PL/SQL loop and it has been returning null whilst when using DBMS_OUTPUT prints out the loop values and when executing each result of the clobs gives an output as well.
The system is meant to execute an already stored SQL in a table based on the report name passed into it. This particular report has many report names; hence the concatenation of each of the reports. The arguments passed are the report name, version of the report you're interested in, the kind of separator you want, and an argument list for the unknowns in the SQL if any. There are also two main types of SQL; 1 that needs the table_name be replaced with a temp table_name and another that needs an ID be appended to a table_name in the SQL.
please find below the code for the REPREF1 function.
CREATE OR REPLACE FUNCTION REPREF1(P_VER IN VARCHAR2 DEFAULT 'LATEST',
P_SEPARATOR IN VARCHAR2 DEFAULT ', ',
P_ARGLIST IN VAR DEFAULT NULL) RETURN CLOB IS
L_CLOB CLOB;
FUNCTION GET_CLOB(P_REPNAM IN VARCHAR2,
P_VER IN VARCHAR2 DEFAULT 'LATEST',
P_SEPARATOR IN VARCHAR2 DEFAULT ', ',
P_ARGLIST IN VAR DEFAULT NULL) RETURN CLOB IS
---------------------------------------------------------------------------------
-- TITLE - GET_CLOB beta - b.0 DATE 2010Mar12
--
-- DESCRIPTION - A function that return a report based on the report name put in
--
-- USAGE - select get_clob(p_repnam,p_ver, p_separator, var(varay(val_1,...val_n), varay(val_1,...val_n))) FROM dual
-----------------------------------------------------------------------------------------------------------------------------
V_SQL VARCHAR2(32767);
L_RESULT CLOB;
V_TITLE VARCHAR2(4000);
V_REPDATE VARCHAR2(30);
V_CNT NUMBER(2);
V_NUMARG NUMBER(3);
V_CDCRU NUMBER(3);
V_BCNT NUMBER(3);
V_NEWTABDAT VARCHAR2(30);
V_NEWTABLIN VARCHAR2(30);
L_COLLIST VARAY;
V_VER VARCHAR2(6);
N PLS_INTEGER;
V_CNTTAB NUMBER(3);
-- EXEC_SQL_CLOB
FUNCTION EXEC_SQL_CLOB(P_SQL IN VARCHAR2,
P_NUMARG IN NUMBER,
P_COLLIST IN VARAY DEFAULT NULL,
P_ARGLIST IN VARAY DEFAULT NULL,
P_SEPARATOR IN VARCHAR2 DEFAULT '') RETURN CLOB IS
------------------------------------------------------------------------------------------------------
-- TITLE - EXEC_SQL_CLOB beta - b.0 DATE 2010Mar22
--
-- DESCRIPTION - A function that returns a clob value after executing the sql query that is passed into it
--
-- USAGE - select exec_sql_clob(p_sql, p_numarg, var(varay(val_1, val_2,...val_n), varay(val_1, val_2,...val_n))) FROM dual
---------------------------------------------------------------------------------------------------------------
L_CUR INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
L_STATUS INTEGER;
V_COL VARCHAR2(4000);
L_RESULT CLOB;
L_COLCNT NUMBER DEFAULT 0;
L_SEPARATOR VARCHAR2(10) DEFAULT '';
V_NUMARG NUMBER(3);
BEGIN
-- parse the query for the report
DBMS_SQL.PARSE(L_CUR, P_SQL, DBMS_SQL.NATIVE);
-- whilst it is not more than 255 per line
FOR I IN 1 .. 255
LOOP
BEGIN
-- define each column in the select list
DBMS_SQL.DEFINE_COLUMN(L_CUR, I, V_COL, 2000);
L_COLCNT := I;
EXCEPTION
WHEN OTHERS THEN
IF (SQLCODE = -1007) THEN
EXIT;
ELSE
RAISE;
END IF;
END;
END LOOP;
-- If query has no bind variables
IF (P_ARGLIST IS NULL) THEN
IF (P_NUMARG = 0) THEN
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
-- Query has bind variables
ELSE
-- Check if the numarg passed is the same has stored in the table
SELECT NUMARG
INTO V_NUMARG
FROM REPVER
WHERE REPCODE = P_SQL;
-- If number of arguments is greater than 0
IF (V_NUMARG > 0) THEN
-- Check if the number of arguments are the same
IF (P_NUMARG = V_NUMARG) THEN
-- Replace the bind variables in the query
FOR J IN 1 .. P_ARGLIST.COUNT
LOOP
DBMS_SQL.BIND_VARIABLE(L_CUR, P_COLLIST(J), P_ARGLIST(J));
END LOOP;
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
ELSE
-- If the number of argument is equal to 0
IF (P_NUMARG = 0) THEN
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
END IF;
END IF;
-- Close cursor
DBMS_SQL.CLOSE_CURSOR(L_CUR);
RETURN L_RESULT;
END EXEC_SQL_CLOB;
BEGIN
-- Check if the version entered is null or latest
IF (P_VER IS NULL)
OR (UPPER(P_VER) = UPPER('LATEST')) THEN
SELECT MAX(VER)
INTO V_VER
FROM REPORT B, REPVER R
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF;
ELSE
V_VER := P_VER;
END IF;
-- Check if the repname and version entered exists
SELECT COUNT(*)
INTO V_CNT
FROM REPORT B, REPVER R
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND VER = V_VER
AND B.REPREF = R.REPREF;
IF (V_CNT > 0) THEN
-- Store the SQL statement, title and number of arguments of the report name passed.
SELECT REPCODE, REPTITLE, NUMARG, COLLIST
INTO V_SQL, V_TITLE, V_NUMARG, L_COLLIST
FROM REPVER R, REPORT B
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
V_REPDATE := TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI');
L_RESULT := V_TITLE || ' (' || P_REPNAM || ' version ' || V_VER || ') generated ' || V_REPDATE || CHR(13) || CHR(13);
-- Check for some specific type of queries
SELECT COUNT(*)
INTO V_CDCRU
FROM REPVER R, REPORT B
WHERE CTDDATA = 'Y'
AND UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
SELECT COUNT(*)
INTO V_BCNT
FROM REPVER R, BODCREPS B
WHERE BENLIST = 'Y'
AND UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
IF (V_CDCRU > 0) THEN
V_NEWTABDATA := 'CT_' || 'DAT_' || P_ARGLIST(1) (P_ARGLIST(1).FIRST);
V_NEWTABLINK := 'CT_' || 'LIN_' || P_ARGLIST(1) (P_ARGLIST(1).FIRST);
-- Check if the tables exist
SELECT COUNT(*)
INTO V_CNTTAB
FROM ALL_TABLES
WHERE TABLE_NAME = V_NEWTABDAT
OR TABLE_NAME = V_NEWTABLIN
AND OWNER = 'SCOTT';
IF (V_CNTTAB > 0) THEN
V_SQL := UPPER(V_SQL);
V_SQL := REPLACE(V_SQL, 'CT_DAT_CRU', V_NEWTABDAT);
V_SQL := REPLACE(V_SQL, 'CT_LIN_CRU', V_NEWTABLIN);
ELSE
V_SQL := 'SELECT ''THE TABLE NOT CREATED YET''
FROM DUAL';
END IF;
END IF;
IF (V_BCNT > 0) THEN
V_SQL := UPPER(V_SQL);
V_SQL := REPLACE(V_SQL, 'LIST', P_ARGLIST(1) (P_ARGLIST(1).LAST));
END IF;
IF (P_ARGLIST IS NULL) THEN
-- execute the query
L_RESULT := L_RESULT || EXEC_SQL_CLOB(V_SQL, V_NUMARG, L_COLLIST, NULL, P_SEPARATOR);
ELSE
N := P_ARGLIST.COUNT;
-- execute the query
L_RESULT := L_RESULT || EXEC_SQL_CLOB(V_SQL, V_NUMARG, L_COLLIST, P_ARGLIST(N), P_SEPARATOR);
END IF;
RETURN L_RESULT;
ELSE
RAISE_APPLICATION_ERROR(-20012, P_REPNAM || ' or ' || P_VER || ' DOES NOT EXIST ');
END IF;
END GET_CLOB;
BEGIN
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
SELECT CONCAT_CLOB(GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST))
INTO L_CLOB
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
-- DBMS_OUTPUT.PUT_LINE (COUNT(i.REPNAM));
END LOOP;
RETURN L_CLOB;
END REPREF1;
/
Cheers,
Tunde
Many thanks APC for making the code look better.
#Robert, the last loop in the code returns null even with the CONCAT_CLOB aggregate function that concatenates clobs.
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
SELECT CONCAT_CLOB(GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST))
INTO L_CLOB
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
END LOOP;
when I try this,
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
L_CLOB := L_CLOB || CHR(13) || GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST);
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
END LOOP;
It also gives null; but this time the dbms output for the repnam are not complete.
Don't know about your code. Here is how it works for me:
Whenever I create a function returning a clob value I do this:
function foo return clob is
l_clob clob;
begin
dbms_lob.createtemporary(lob_loc => l_clob, cache => true, dur => dbms_lob.call);
...
return l_clob;
end;
When concatenating values into a clob I use a function:
procedure add_string_to_clob(p_lob in out nocopy clob
,p_string varchar2) is
begin
dbms_lob.writeappend(lob_loc => p_lob, amount => length(p_string), buffer => p_string);
end;
You have to use
dbms_lob.substr(your clob parameter,start position, length)
e.g
dbms_output('my clob value:' || dbms_lob.substr(your clob parameter,start position, length);
But you can print in a string max 4000 character, you can then use this in a looping function to print 4000 characters in each line.

Resources