Pass a table name and time stamp variable to a PL/SQL - oracle

I am writing a below PL/SQL code in SQL developer to delete data from a table with a timestamp column in the where condition. How can I modify this code to pass the table name and the timestamp value to values that I want based on what table and time records I want to delete the data from and create a stored procedure that can be reused.
DBMS_OUTPUT.ENABLE;
DECLARE
counter INTEGER := 0;
stop INTEGER;
BEGIN
dbms_output.put_line('START');
LOOP
counter := counter + 1;
DELETE my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM'
AND ROWNUM <= 100000;
SELECT COUNT(*)
INTO stop
FROM my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM';
EXIT WHEN stop <= 0;
COMMIT;
END LOOP;
dbms_output.put_line('Counter: ' || counter);
dbms_output.put_line('Left: ' || stop);
COMMIT;
END;

Adapting your anonymous to a stored procedure will, as indicated, require converting it to dynamic SQL. Always more difficult. And subject to SQL injection. For this you should validate string replacement parameters. I have a couple other changes:
Pass the desired as a timestamp, not a string, this allows/forces the
calling routine to determine the format and necessary conversion, if
any.
Added a parameter for column name as well. This frees naming columns
from the requirement of the procedure.
There is no need to count remaining items. Your loop processes until
that value reaches 0, but this can be determined by the number of
rows deleted on the last pass. Delete sets sql%rowcount to the number
of rows deleted. When the pass deletes 0 rows the process is
complete.
Removed the results display and the commit from the procedure, again
offloading this to the caller.
create or replace
procedure delete_values_by_timestamp
( p_table_name in varchar2
, p_column_name in varchar2
, p_timestamp in timestamp
, p_result_msg out varchar2
)
IS
table_name_parameter_invalid exception;
pragma exception_init(table_name_parameter_invalid, -44002);
column_name_parameter_invalid exception;
pragma exception_init(column_name_parameter_invalid, -44003);
k_nl constant varchar2(1) := chr(10);
k_max_delete_per_interation constant integer := 100000;
k_base_delete varchar2(256) :=
'delete from <table_name>' ||
' where <column_name> <= :1' ||
' and rownum <= :2';
v_delete_sql varchar2 (256) ;
v_rows_deleted integer := 0;
begin
v_delete_sql := replace(replace(k_base_delete,'<table_name>', dbms_assert.sql_object_name(p_table_name))
,'<column_name>',dbms_assert.simple_sql_name(p_column_name));
dbms_output.put_line('Running SQL:' || k_nl || v_delete_sql);
loop
execute immediate v_delete_sql using p_timestamp, k_max_delete_per_interation;
exit when sql%rowcount = 0;
v_rows_deleted :=v_rows_deleted + sql%rowcount;
end loop;
if v_rows_deleted = 0
then
p_result_msg := 'No Data Found';
else
p_result_msg := 'Number of Rows Deleted ' || to_char(v_rows_deleted);
end if;
exception
when table_name_parameter_invalid then
raise_application_error(-20199,'Invalid Table Name (' || p_table_name || ') specified.');
when column_name_parameter_invalid then
raise_application_error(-20198,'Invalid Column Name (' || p_column_name || ') specified.');
end delete_values_by_timestamp;
See example: In the example I reduce the number of rows deleted on each iteration from 100000 to 20. An additionally enhancement would be to pass the number of rows for each iteration as a parameter.

I couldn't test it but you could create a function whcih takes the table name and the timestamp as parameter.
As long you want to delete every record with the given timestamp you don't need to loop for each record.
This function should be just an example.
FUNCTION delete_values_by_timestamp (p_table_name IN VARCHAR2 DEFAULT NULL,
p_timestamp IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2
IS
v_count NUMBER := 0;
v_query VARCHAR2 (500) := '';
BEGIN
IF p_table_name IS NOT NULL
THEN
IF p_timestamp IS NOT NULL
THEN
v_query := 'SELECT COUNT(*)
FROM my_schema.' || p_table_name | '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query INTO v_count;
IF v_count > 0
THEN
v_query := 'DELETE FROM my_schema.' || p_table_name || '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query;
ELSE
RETURN 'NO RECORDS FOUND!';
END IF;
ELSE
RETURN 'TIMESTAMP EMPTY!';
END IF;
ELSE
RETURN 'TABLE NAME EMPTY!';
END IF;
END;

Related

Bulk into table objects

I want populate my table of objects and I try of different ways but I canĀ“t. With this code I have an extensive error: "Encountered the symbol "L_T_O_TYPE" when expecting one of the following: .(*#_-+/", it is a big message
CREATE OR REPLACE TYPE O_Type AS OBJECT (
depar_id NUMBER,
depar_name VARCHAR2(20),
man_id NUMBER,
loca_id NUMBER,
CONSTRUCTOR FUNCTION O_Type(
depar_id NUMBER,
depar_name VARCHAR2,
man_id NUMBER,
loca_id NUMBER)
RETURN SELF AS RESULT
);
CREATE OR REPLACE TYPE BODY O_Type IS
CONSTRUCTOR FUNCTION O_Type(
depar_id NUMBER,
depar_name VARCHAR2,
man_id NUMBER,
loca_id NUMBER,
)RETURN AS SELF AS RESULT IS
BEGIN
self.depar_id := depar_id;
self.depar_name := depar_name;
self.man_id := man_id;
self.loca_id := loca_id;
RETURN;
END;
END;
/
CREATE OR REPLACE TYPE T_o_type AS TABLE OF O_Type;
/
DECLARE
CURSOR C_depar IS SELECT *
FROM departments;
TYPE T_C_DEPAR IS TABLE OF C_depar%ROWTYPE;
L_TABLE T_C_DEPAR;
l_T_o_type T_o_type;
BEGIN
l_T_o_type := T_o_type();
OPEN C_depar;
LOOP
FETCH C_depar BULK COLLECT INTO L_TABLE;
EXIT WHEN C_depar%NOTFOUND;
FORALL i IN 1..L_TABLE.count
l_T_o_type(i) : = T_o_type(L_TABLE.DEPARTMENT_ID, L_TABLE.DEPARTMENT_NAME, L_TABLE.MANAGER_ID , L_TABLE.LOCATION_ID)
END LOOP;
CLOSE C_depar;
END;
/
Can someone tell me the best way to populate my object table?
"Best" is subjective.
If you want all the rows in a single collection, use SELECT ... BULK COLLECT INTO:
DECLARE
l_depts T_o_type;
BEGIN
SELECT O_Type(id, name, man_id, loc_id)
BULK COLLECT INTO l_depts
FROM departments;
FOR i IN 1 .. l_depts.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(
l_depts(i).depar_id
|| ' ' || l_depts(i).depar_name
|| ' ' || l_depts(i).man_id
|| ' ' || l_depts(i).loca_id
);
END LOOP;
END;
/
If you want to process the rows in batches then you can use:
DECLARE
CURSOR C_depar IS
SELECT O_Type(id, name, man_id, loc_id)
FROM departments;
l_depts T_o_type;
BEGIN
OPEN C_depar;
LOOP
FETCH C_depar BULK COLLECT INTO L_depts LIMIT 10;
FOR i IN 1 .. l_depts.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(
l_depts(i).depar_id
|| ' ' || l_depts(i).depar_name
|| ' ' || l_depts(i).man_id
|| ' ' || l_depts(i).loca_id
);
END LOOP;
EXIT WHEN C_depar%NOTFOUND;
END LOOP;
CLOSE C_depar;
END;
/
db<>fiddle here

How to handle NO_DATA_FOUND in sys-refcursor using bind variables

How to handle the case when select statement don't return any record in SYS_REFCURSOR ?
I am using dynamic sql generation process using bind variables.
create or replace
procedure test_dynamic_sql
(
last_name varchar2,
rc out sys_refcursor,
jobid varchar2,
sdate date,
edate date,
status out varchar2,
message out varchar2 )
is
q long;
lname varchar2(240);
begin
q := 'select employee_id
from employees e
where 1=1';
if last_name is not null then
q := q || 'and (e.LAST_NAME = :LAST_NAME)';
else
q := q || 'and (1=1 or :LAST_NAME is null)';
end if;
if jobid is not null then
q := q || 'and (e.JOB_ID = :JOBID)';
else
q := q || 'and (1=1 or :JOBID is null)';
end if;
if sdate is not null then
q := q || 'and (e.hire_date >= :sdate)';
else
q := q || 'and (1=1 or :sdate is null)';
end if;
if edate is not null then
q := q || 'and (e.hire_date <= :edate)';
else
q := q || 'and (1=1 or :edate is null)';
end if;
open rc for q using last_name, jobid, sdate, edate;
/*
IF rc%NOTFOUND THEN
STATUS := 'NR';
MESSAGE := 'Not Found';
ELSE
STATUS := 'S';
MESSAGE := 'Found';
END IF;
*/
exception
when others then
STATUS :='E';
message := sqlcode||'-->'||sqlerrm;
end;
I have tried %NOTFOUND and %FOUND attributes but its not working.
I have also tried NO_DATA_FOUND exception but its also not working.
I need to return status as 'S', 'E', 'NR'
S --> SUCCESS (when records found)
E --> ERROR (when any error occured)
NR--> NO RECORDS (when 0 records)
Thanks!
This answer is to address the issues you are having while working with Ref Cursors as Out Parameters. The below code calls your test_dynamic_sql() Procedure. Within this Proc we OPEN the cursor, FETCH the data it is pointing to and we DO NOT CLOSE the cursor as we are immediately using that Cursor again outside of the test_dynamic_sql() Procedure. One thing to NOTE - When FETCH is used that Cursor will no longer provide you with the data and must be opened again. Since your Cursor is utilizing Dynamic SQL we must Declare our Dynamic 'Query' in the same place we are declaring the rest of our Global Variables.
"Cursors are NOT designed to be re-used: you read them once, keep moving forward and as you're doing so you're discarding any previously scanned rows." This fact was stolen from this SO Post: Oracle. Reuse cursor as parameter in two procedures.
Outside of this procedure we first must check if the Cursor was successfully initialized by using an IF statement to check if the Cursor exists: IF (g_rc IS NOT NULL) THEN.
Full Code Sample below:
DECLARE
/* g for Global */
g_status VARCHAR2(5);
g_message VARCHAR2(100);
g_rc SYS_REFCURSOR;
/* Store Dynamic SQL Query */
g_SQL VARCHAR2(200);
/* Bind Variables */
g_jobid NUMBER;
g_last_name VARCHAR2(240);
/* Declare Global Record used to FETCH data into */
g_rec_employee employees%ROWTYPE;
PROCEDURE test_dynamic_sql(pv_last_name VARCHAR2,
p_rc OUT SYS_REFCURSOR,
pv_jobid VARCHAR2,
pv_status OUT VARCHAR2,
pv_message OUT VARCHAR2,
pv_q OUT VARCHAR2)
AS
/* Declare Record used to FETCH data into */
rec_employee employees%ROWTYPE;
/* Bind Variables */
jobid NUMBER := to_number(pv_jobid);
last_name VARCHAR2(240) := pv_last_name;
BEGIN
/* Reset/Initialize Cursor Output Variable */
p_rc := NULL;
/* Dynamic SQL statement with placeholder: */
pv_q := 'SELECT * FROM employees WHERE 1=1';
IF last_name IS NOT NULL
THEN pv_q := pv_q || ' AND (lastname = :LAST_NAME)';
ELSE pv_q := pv_q || ' AND (1=1 or :LAST_NAME is null)';
END IF;
IF jobid IS NOT NULL
THEN pv_q := pv_q || ' AND (ID = :JOBID)';
ELSE pv_q := pv_q || ' AND (1=1 or :JOBID is null)';
END IF;
/* Open cursor & specify bind argument in USING clause: */
OPEN p_rc FOR pv_q USING last_name, jobid;
LOOP
/* In order to work with any Data that a Cursor 'points' to we must FETCH the data. This allows us to use %ROWCOUNT and %NOTFOUND */
FETCH p_rc INTO rec_employee;
EXIT WHEN p_rc%NOTFOUND;
END LOOP;
IF p_rc%ROWCOUNT = 0 THEN
pv_status := 'NR';
pv_message := 'Not Found';
--EXIT;
ELSIF p_rc%ROWCOUNT = 1 THEN
pv_status := 'S';
pv_message := 'Found';
--EXIT;
ELSE
pv_status := 'MU';
pv_message := 'Multiple Records';
END IF;
--dbms_output.put_line('Final Count: ' || p_rc%ROWCOUNT);
/* Close Cursor - We don't close the Cursor here as we want to use this cursor as an OUT Parameter outside of this Proc */
CLOSE p_rc;
EXCEPTION
WHEN OTHERS THEN
pv_status :='E';
pv_message := sqlcode||'-->'||sqlerrm;
dbms_output.put_line('STATUS: ' || pv_status);
dbms_output.put_line('MESSAGE: ' || pv_message);
CLOSE p_rc;
END test_dynamic_sql;
BEGIN
g_jobid := null;
g_last_name := 'Loch';
test_dynamic_sql(pv_last_name => g_last_name,
p_rc => g_rc, /* Out Parameter */
pv_jobid => g_jobid,
pv_status => g_status, /* Out Parameter */
pv_message => g_message, /* Out Parameter */
pv_q => g_SQL /* Out Parameter */
);
/* Output OUT Variables aka Provide Output to User */
dbms_output.put_line('STATUS: ' || g_status);
dbms_output.put_line('MESSAGE: ' || g_message);
IF (g_rc IS NOT NULL) THEN
dbms_output.put_line('We have something here. Fetching Data Now:');
OPEN g_rc FOR g_sql USING g_last_name, g_jobid;
LOOP
FETCH g_rc INTO g_rec_employee;
EXIT WHEN g_rc%NOTFOUND;
/* Print the Job ID just to show it is working */
dbms_output.put_line('Job_ID: ' || g_rec_employee.id || ' is the id');
END LOOP;
dbms_output.put_line('Total of: '|| g_rc%ROWCOUNT || ' records Fetched.');
CLOSE g_rc;
ELSE
dbms_output.put_line('null');
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('Error '||TO_CHAR(SQLCODE)||': '||SQLERRM);
CLOSE g_rc;
END;
The above works with the same Employees table data as in my first Answer to this SO Question.
There are a few things you are missing if you wish to work with a cursor in the manner you are attempting regardless of the implicit Ref Cursor due to the Dynamic SQL or an explicit Cursor.
In order to make use of %ROWCOUNT or %NOTFOUND you must first FETCH the Cursor. This Link "PL/SQL 101 : Understanding Ref Cursors" that provides a ton of information on this topic but all that is needed to help with answering your question is to know that you must FETCH the data first.
Below is an image showing the data in my Employees table. NOTE that there are two employees with the last name 'Loch'.
The below Code is all within it's own anonymous block but can easily be converted into a Procedure/Function. It has all of your required Status's and Messages. In order to handle searches that have more than one result I added an additional Status/Message to tell the user that Multiple Records were returned. Last, just to make it easier to work with your code I took out all but two of your Parameters. NOTE: If the Procedure's Parameters are all passed in as NULL the Dynamic SQL That is generated will query the entire table as it basically removes all of the Filters in the WHERE clause.
DECLARE
/* Parameters */
rc SYS_REFCURSOR;
q VARCHAR2(200);
status VARCHAR2(5);
message VARCHAR2(100);
/* Declare Record used to FETCH data into */
rec_employee employees%ROWTYPE;
/* Bind Variables */
jobid NUMBER := null;
last_name VARCHAR2(240) := 'Loch';
BEGIN
/* Dynamic SQL statement with placeholder: */
q := 'SELECT * FROM employees WHERE 1=1';
IF last_name IS NOT NULL
THEN q := q || ' AND (lastname = :LAST_NAME)';
ELSE q := q || ' AND (1=1 or :LAST_NAME is null)';
END IF;
IF jobid IS NOT NULL
THEN q := q || ' AND (ID = :JOBID)';
ELSE q := q || ' AND (1=1 or :JOBID is null)';
END IF;
/* Open cursor & specify bind argument in USING clause: */
OPEN rc FOR q USING last_name, jobid;
LOOP
/* In order to work with any Data that a Cursor 'points' to we must FETCH the data. This allows us to use %ROWCOUNT and %NOTFOUND */
FETCH rc INTO rec_employee;
EXIT WHEN rc%NOTFOUND;
END LOOP;
IF rc%ROWCOUNT = 0 THEN
STATUS := 'NR';
MESSAGE := 'Not Found';
--EXIT;
ELSIF rc%ROWCOUNT = 1 THEN
STATUS := 'S';
MESSAGE := 'Found';
--EXIT;
ELSE
STATUS := 'MU';
MESSAGE := 'Multiple Records';
END IF;
dbms_output.put_line('Final Count: ' || rc%ROWCOUNT);
/* Close Cursor */
CLOSE rc;
/* Return Variables or Provide Output to User */
dbms_output.put_line('STATUS: ' || STATUS);
dbms_output.put_line('MESSAGE: ' || MESSAGE);
EXCEPTION
WHEN OTHERS THEN
STATUS :='E';
message := sqlcode||'-->'||sqlerrm;
dbms_output.put_line('STATUS: ' || STATUS);
dbms_output.put_line('MESSAGE: ' || MESSAGE);
END;

Fetching data of a dynamic column from a dynamic view using cursor

The requirement is to check data consistency of a view. It's a bit complicated, so let's move step by step.
A table check_data_column has basically 5 imp. columns: ViewName, ColumnName, Mandatory, MaxLength, DataType.
It will contain Information about each field of a particular view. For example:
ViewName: Employee_V
ColumnName: EmployeeNo
Mandatory: 1 (True)
MaxLength: 10
DataType: Number
ViewName: Employee_V
ColumnName: EmployeeName
Mandatory: 1 (True)
MaxLength: 20
DataType: String
Now I have to write a function which takes all the entries from this check_data_column and check the data in the appropriate view for each mentioned column.
From the above example, it will check the data in the Employee_V.
Each entry in EmployeNo column should not be null, max length should be 10 and it should be a numeric value.
Similarly, each entry in EmployeeName column should not be null, max length should be 20 and it should be a string.
Number of views is unknown and no. of columns in each view is unknown.
To solve the above problem, I wrote the following code:
FUNCTION CheckData(viewname VARCHAR2)
RETURN VARCHAR2
Is
return_v VARCHAR2(1000);
query_v VARCHAR2(200);
column_c SYS_REFCURSOR;
column_v column_c%ROWTYPE;
CURSOR ddc_c IS
SELECT *
FROM check_data_column;
BEGIN
return_v := null;
FOR ddc_v IN ddc_c LOOP
query_v := 'SELECT' || ddc_v.column_name || 'FROM anc_sap.' || ddc_v.viewname;
OPEN column_c FOR query_v;
LOOP
FETCH column_c INTO column_v;
EXIT WHEN column_c%NOTFOUND;
IF LENGTH(column_v) > ddc_v.max_length THEN
return_v := 'Max. length exceeded';
END IF;
----Other validations (on mandatory and data type)
END LOOP;
CLOSE column_c;
END LOOP;
RETURN return_v;
END CheckData;
Problem:
The problem I am facing here is in declaring column_v variable for sys_refcursor column_c. Since at this point of time I am not able to think replacement for sys_refcursor, is there anything else I can do?
This function worked in simple tests. It checks nulls and length, you have to add rest of validations.
create or replace function CheckData(i_viewname VARCHAR2)
RETURN VARCHAR2
Is
query_v VARCHAR2(2000);
v_cnt number := 0;
CURSOR ddc_c IS
SELECT * FROM check_data_column where viewname = i_viewname;
BEGIN
FOR ddc_v IN ddc_c LOOP
-- check nulls
if ddc_v.mandatory = 1 then
query_v := 'select count(1) from '|| ddc_v.viewname
||' where '||ddc_v.columnname||' is null';
execute immediate query_v into v_cnt;
if v_cnt > 0 then
return 'null values for mandatory column '
||ddc_v.viewname ||'.'||ddc_v.columnname||' exists';
end if;
end if;
-- check column length
query_v := 'select count(1) from '|| ddc_v.viewname
||' where length('||ddc_v.columnname||') > '||ddc_v.maxlength;
execute immediate query_v into v_cnt;
if v_cnt > 0 then
return 'max length in column '||ddc_v.viewname
||'.'||ddc_v.columnname||' exceeded';
end if;
-- other validations
END LOOP;
RETURN 'OK';
END CheckData;

Need help in execute immediate update query

I have this query and it's not updating into the database. The given "where" clause is valid. When I run the query independently, it works fine but in this Procedure it's not working. There is no exception or no error. Could you guys help me in figuring out where the problem is?
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
I didn't write the commit code before. Just to clarity.
I suppose that col_id is the primary key. So in the update statement
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
you are always updating at most one row and thus the condition
SQL%ROWCOUNT > 1
is never true ( 1 is not > 1 )
So if you don't have any other commit statement in your procedure, you will never commit those updates.
By the way: what is the purpose of this
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
why don't you just commit at the end of your work?
The following code works okay (ie updates the row).
I suspect your error is elsewhere.
For example, if you don't initialise COUNTER, the increment will still leave it as null and it will never commit.
Or, l_vc_ColId may be the wrong datatype and suffering from an invalid conversion.
declare
v_emp_id number := 7839;
v_name varchar2(4) := 'DING';
v_tab varchar2(3) := 'EMP';
begin
execute immediate 'update '||v_tab||
' set ename = :v_name Where empno = :v_emp_id'
using v_name, v_emp_id;
dbms_output.put_line('C:'||sql%rowcount);
end;
you may want to reconsider your design if your using dynamic sql to change the "dest" table in thousands of updates.
Much better to know your dest and use bind variables for the where conditions. then you can commit every x rows using mod or similar:
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
But for your example, Marcin is correct, if you are updating only 1 row at a time, then
if SQL%ROWCOUNT > 1
will never be true;
EDIT:
A simple example knowing your "dest" table:
declare
cursor sel_cur is
select col1, col2, from sourceTable where col3 = 'X';
v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
v_ctr := v_ctr + 1;
-- implicit bind variables used
update destTable
set col1 = rec.col1,
col2 = rec.col2
where col3 = 'Z';
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
end loop;
exception
when others then rollback;
raise;
end;
If using dynamic SQL, a simple example using explicit bind variables (USING clause) from Oracle docs:
CREATE OR REPLACE PROCEDURE raise_emp_salary (column_value NUMBER,
emp_column VARCHAR2, amount NUMBER) IS
v_column VARCHAR2(30);
sql_stmt VARCHAR2(200);
BEGIN
-- determine if a valid column name has been given as input
SELECT COLUMN_NAME INTO v_column FROM USER_TAB_COLS
WHERE TABLE_NAME = 'EMPLOYEES' AND COLUMN_NAME = emp_column;
sql_stmt := 'UPDATE employees SET salary = salary + :1 WHERE '
|| v_column || ' = :2';
EXECUTE IMMEDIATE sql_stmt USING amount, column_value;
IF SQL%ROWCOUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('Salaries have been updated for: ' || emp_column
|| ' = ' || column_value);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Invalid Column: ' || emp_column);
END raise_emp_salary;
/
For more reading, see here.
Hope this helps, happy coding
Execute immediate needs explicit commit. I guess you checked that ?

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