Dynamic SQL Stored Procedure in Oracle - oracle

Morning All, Hopefully someone can help. I'm an MSSQL specialist and have very little experience of Oracle PL/SQL. I've been asked to write an Oracle Stored Procedure which gets specific fields from the latest Monthly audit table. This is what I've come up with but it doesn't seem to run or output anywhere.
Can someone help?
CREATE OR REPLACE PROCEDURE GetLatestMonthAuditTable
AS
BEGIN
DECLARE
v_lastmonth date := interval '-1' month +systimestamp;
v_year varchar2(4) := extract(year from v_lastmonth);
v_month varchar2(2) := extract(month from v_lastmonth);
v_day varchar2(2) := extract(day from LAST_DAY(v_lastMonth));
v_sql varchar2(256) := 'SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_' || v_year || v_month || v_day;
BEGIN
EXECUTE IMMEDIATE v_sql;
END;
END;

You didn't actually run it - you just created a procedure.
Could've been like this:
Sample table:
SQL> CREATE TABLE AUDIT_20211031
2 AS
3 SELECT 1 act_code, 'Littlefoot' change_by, SYSDATE change_date FROM DUAL;
Table created.
Procedure (it is a good habit to display statement you'll run using dbms_output.put_line; once you make sure it is OK, remove that line):
SQL> CREATE OR REPLACE PROCEDURE GetLatestMonthAuditTable
2 AS
3 v_lastmonth DATE := INTERVAL '-1' MONTH + SYSTIMESTAMP;
4 v_year VARCHAR2 (4) := EXTRACT (YEAR FROM v_lastmonth);
5 v_month VARCHAR2 (2) := EXTRACT (MONTH FROM v_lastmonth);
6 v_day VARCHAR2 (2) := EXTRACT (DAY FROM LAST_DAY (v_lastMonth));
7 v_sql VARCHAR2 (200);
8 BEGIN
9 v_sql :=
10 'SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_'
11 || v_year
12 || v_month
13 || v_day;
14
15 DBMS_OUTPUT.put_line (v_sql);
16
17 EXECUTE IMMEDIATE v_sql;
18 END;
19 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> BEGIN
2 GetLatestMonthAuditTable;
3 END;
4 /
SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_20211031
PL/SQL procedure successfully completed.
SQL>
Now, your procedure doesn't do anything - it runs that select, but it isn't displayed anywhere on the screen.
If it were a function instead, you could return ref cursor and see something. For example:
SQL> CREATE OR REPLACE FUNCTION GetLatestMonthAuditTable
2 RETURN SYS_REFCURSOR
3 AS
4 v_lastmonth DATE := INTERVAL '-1' MONTH + SYSTIMESTAMP;
5 v_year VARCHAR2 (4) := EXTRACT (YEAR FROM v_lastmonth);
6 v_month VARCHAR2 (2) := EXTRACT (MONTH FROM v_lastmonth);
7 v_day VARCHAR2 (2) := EXTRACT (DAY FROM LAST_DAY (v_lastMonth));
8 v_sql VARCHAR2 (200);
9 rc SYS_REFCURSOR;
10 BEGIN
11 v_sql :=
12 'SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_'
13 || v_year
14 || v_month
15 || v_day;
16
17 OPEN rc FOR v_sql;
18
19 RETURN rc;
20 END;
21 /
Function created.
Testing the function:
SQL> SELECT GetLatestMonthAuditTable FROM DUAL;
GETLATESTMONTHAUDITT
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
ACT_CODE CHANGE_BY CHANGE_DATE
---------- ---------- -------------------
1 Littlefoot 16.11.2021 12:14:18
SQL>

Related

Error passing an Oracle cursor when trying to test print its contents

We're using Oracle 12c.
Passing a cursor from a procedure. Trying to test print its contents.
I get the following error message:
ORA-01001: invalid cursor
ORA-06512: at line 25
Why is the cursor invalid?
Here's the code.
SET SERVEROUTPUT ON
--VARIABLE X REFCURSOR;
DECLARE
RUN_DATE VARCHAR2 (10);
D_XMAS_NY VARCHAR2 (10);
PO_ERROR_CODE_N NUMBER;
PO_ERROR_MESSAGE_C VARCHAR2 (32767);
PO_REF_CUR SLD_COMMON_PKG.PG_COMMON_REFCUR;
V_VAL SLDPROC.t_sld_gic_repo_nonrepo_rec%ROWTYPE;
BEGIN
RUN_DATE := '2022-07-27';
D_XMAS_NY := '9999-12-30';
PO_ERROR_CODE_N := NULL;
PO_ERROR_MESSAGE_C := NULL;
-- PO_REF_CUR := NULL;
SLDPROC.SP_SLD_GEN_GIC_REINV_DET (RUN_DATE,
D_XMAS_NY,
PO_ERROR_CODE_N ,
PO_ERROR_MESSAGE_C,
PO_REF_CUR);
LOOP
FETCH PO_REF_CUR INTO V_VAL;
EXIT WHEN PO_REF_CUR%NOTFOUND;
/*Notice the DBMS_OUTPUT line is commented out. So at this point, Oracle is just running through the cursor.*/
--DBMS_OUTPUT.PUT_LINE( V_VAL.d_inc_dt );
END LOOP;
CLOSE PO_REF_CUR;
END;
I get the following error message:
ORA-01001: invalid cursor
ORA-06512: at line 25
The Procedure SLDPROC.SP_SLD_GEN_GIC_REINV_DET compiles correctly and the cursor inside the procedure is correct. Finally when I run this procedure without any trace of the Cursor Loop it finishes correctly. It's when I try to list out the contents of the cursor.
The contents of the cursor consist of 1 giant column with all the columns from a table concatenated together like this.
OPEN po_ref_cur FOR
SELECT c_run_type
|| ','
|| TO_CHAR(d_inc_dt, 'DD/MM/YYYY')
|| ','
|| lend_agnt
|| ','
|| trim(ACCNT)
|| ','
|| NVL(trim(DAY_CT), '<NULL>') -- DAY_CT
|| ','
|| NVL(trim(REPOCP_LEI_CODE), '<NULL>') -- REPOCP_LEI_CODE
|| ','
|| NVL(trim(REPOCP_BR_DESC), '<NULL>')
FROM t_sld_gic_repo_nonrepo_rec
ORDER BY c_run_type,d_inc_dt, NVL(issuer_repocp, 'ZZ');
SP_SLD_GEN_GIC_REINV_DET procedure's last parameter should be OUT, returning a ref cursor. Is it?
Because, if I try to mimic what you did, that code works.
Sample procedure:
SQL> CREATE OR REPLACE PROCEDURE SP_SLD_GEN_GIC_REINV_DET (
2 par_rc OUT SYS_REFCURSOR
3 ) IS
4 BEGIN
5 OPEN par_rc FOR SELECT dname FROM dept;
6 END;
7 /
Procedure created.
SQL>
Your anonymous PL/SQL block (slightly modified):
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 run_date VARCHAR2(10);
3 d_xmas_ny VARCHAR2(10);
4 po_error_code_n NUMBER;
5 po_error_message_c VARCHAR2(32767);
6 po_ref_cur SYS_REFCURSOR;-- SLD_COMMON_PKG.PG_COMMON_REFCUR;
7 v_val VARCHAR2(200); --SLDPROC.t_sld_gic_repo_nonrepo_rec%ROWTYPE;
8 BEGIN
9 run_date := '2022-07-27';
10 d_xmas_ny := '9999-12-30';
11 po_error_code_n := NULL;
12 po_error_message_c := NULL;
13
14 -- PO_REF_CUR := NULL;
15 -- SLDPROC.SP_SLD_GEN_GIC_REINV_DET (RUN_DATE,
16 -- D_XMAS_NY,
17 -- PO_ERROR_CODE_N ,
18 -- PO_ERROR_MESSAGE_C,
19 -- PO_REF_CUR);
20 SP_SLD_GEN_GIC_REINV_DET (po_ref_cur);
21 LOOP
22 FETCH po_ref_cur INTO v_val;
23 EXIT WHEN po_ref_cur%notfound;
24 /*Notice the DBMS_OUTPUT line is commented out. So at this point, Oracle is just running through the cursor.*/
25 DBMS_OUTPUT.PUT_LINE(V_VAL);
26 END LOOP;
27 CLOSE po_ref_cur;
28 END;
29 /
ACCOUNTING
RESEARCH
SALES
OPERATIONS
PL/SQL procedure successfully completed.
SQL>

Need to fetch the table details using stored procedure when we give table name as input

CREATE TABLE test_table
(
col1 NUMBER(10),
col2 NUMBER(10)
);
INSERT INTO test_table
VALUES (1, 2);
I am writing a stored procedure wherein if I give a table name as an input, that should give me the table data and column details.
For example:
SELECT *
FROM <input_table_name>;
But this causes an error that the SQL command has not ended properly even though I have taken care of this.
My attempt:
CREATE OR REPLACE PROCEDURE sp_test(iv_table_name IN VARCHAR2,
p_out_cur OUT SYS_REFCURSOR)
AS
lv_str VARCHAR2(400);
lv_count NUMBER(1);
lv_table_name VARCHAR2(255):=UPPER(iv_table_name);
BEGIN
lv_str := 'SELECT * FROM '||lv_table_name;
SELECT COUNT(1) INTO lv_count FROM all_tables WHERE table_name = lv_table_name;
IF lv_count = 0 THEN
dbms_output.put_line('Table does not exist');
ELSE
OPEN p_out_cur FOR lv_str;
END IF;
END sp_test;
Tool used: SQL developer(18c)
In dynamic SQL, you do NOT terminate statement with a semicolon.
EXECUTE IMMEDIATE 'SELECT * FROM '||lv_table_name||';';
-----
remove this
Anyway, you won't get any result when you run that piece of code. If you really want to see table's contents, you'll have to switch to something else, e.g. create a function that returns ref cursor.
Sample data:
SQL> SELECT * FROM test_table;
COL1 COL2
---------- ----------
1 2
3 4
Procedure you wrote is now correct:
SQL> CREATE OR REPLACE PROCEDURE sp_test (iv_table_name IN VARCHAR2,
2 p_out_cur OUT SYS_REFCURSOR)
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 BEGIN
8 lv_str := 'SELECT * FROM ' || lv_table_name;
9
10 SELECT COUNT (1)
11 INTO lv_count
12 FROM all_tables
13 WHERE table_name = lv_table_name;
14
15 IF lv_count = 0
16 THEN
17 DBMS_OUTPUT.put_line ('Table does not exist');
18 ELSE
19 OPEN p_out_cur FOR lv_str;
20 END IF;
21 END sp_test;
22 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_rc SYS_REFCURSOR;
3 l_col1 NUMBER (10);
4 l_col2 NUMBER (10);
5 BEGIN
6 sp_test ('TEST_TABLE', l_rc);
7
8 LOOP
9 FETCH l_rc INTO l_col1, l_col2;
10
11 EXIT WHEN l_rc%NOTFOUND;
12
13 DBMS_OUTPUT.put_line (l_col1 || ', ' || l_col2);
14 END LOOP;
15 END;
16 /
1, 2 --> contents of the
3, 4 --> TEST_TABLE
PL/SQL procedure successfully completed.
SQL>
A function (instead of a procedure with the OUT parameter):
SQL> CREATE OR REPLACE FUNCTION sf_test (iv_table_name IN VARCHAR2)
2 RETURN SYS_REFCURSOR
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 l_rc SYS_REFCURSOR;
8 BEGIN
9 lv_str := 'SELECT * FROM ' || lv_table_name;
10
11 SELECT COUNT (1)
12 INTO lv_count
13 FROM all_tables
14 WHERE table_name = lv_table_name;
15
16 IF lv_count = 0
17 THEN
18 raise_application_error (-20000, 'Table does not exist');
19 ELSE
20 OPEN l_rc FOR lv_str;
21 END IF;
22
23 RETURN l_rc;
24 END sf_test;
25 /
Function created.
Testing:
SQL> SELECT sf_test ('liksajfla') FROM DUAL;
SELECT sf_test ('liksajfla') FROM DUAL
*
ERROR at line 1:
ORA-20000: Table does not exist
ORA-06512: at "SCOTT.SF_TEST", line 18
SQL> SELECT sf_test ('TEST_TABLE') FROM DUAL;
SF_TEST('TEST_TABLE'
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
COL1 COL2
---------- ----------
1 2
3 4
SQL>

Why "ORA-00940: invalid ALTER command" when i try alter my procedure MY_PRC using this procedure?

CREATE OR REPLACE PROCEDURE COMPILE_OBJECT(my_type VARCHAR2,
my_name VARCHAR2,
my_package_part VARCHAR2 := NULL) IS
v_text VARCHAR2(500) := 'ALTER :my_type :my_name COMPILE :my_package_part';
BEGIN
--try alter objects
EXECUTE IMMEDIATE v_text USING my_type, my_name, my_package_part;
END;
That would be, as Sayan commented, something like this:
SQL> CREATE OR REPLACE PROCEDURE compile_object (
2 my_type VARCHAR2,
3 my_name VARCHAR2,
4 my_package_part VARCHAR2 := NULL)
5 IS
6 v_text VARCHAR2 (500);
7 BEGIN
8 v_text :=
9 'alter '
10 || my_type
11 || ' '
12 || DBMS_ASSERT.sql_object_name (my_name)
13 || ' compile '
14 || my_package_part;
15
16 EXECUTE IMMEDIATE v_text;
17 END;
18 /
Procedure created.
SQL> EXEC compile_object('procedure', 'p_roles', null);
PL/SQL procedure successfully completed.
SQL>

Run query dynamically in stored procedure in Oracle

Select all the tables of database where column match than pass table name to next query using loop. If column name and column values matches than return true and exist for loop using a stored procedure:
CREATE OR REPLACE PROCEDURE TEST
(
NAME IN VARCHAR2 ,
ID IN NUMBER,
RE OUT SYS_REFCURSOR
) AS
BEGIN
OPEN RE FOR SELECT A.TABLE_NAME FROM
user_tables A JOIN user_tab_columns C
ON C.TABLE_NAME = A.TABLE_NAME
WHERE C.COLUMN_NAME = NAME;
FOR RE IN LOOP
v_Sql := 'SELECT COUNT(*) FROM '|| LOOP.TABLE_NAME || 'WHERE COLUMN_NAME =
ID';
EXECUTE IMMEDIATE v_Sql
IF v_Sql%ROWCOUNT > 0 THEN
return true;
EXIT
END LOOP;
END TEST;
For more understanding the problem
//Get all the tables of database where campus_id is exist in any table of
database
Campus, Class, Section (3 tables found)
Apply forloop on the records
Select count(campus_id) as total from (table name using loop) where campus_id = 1(value
pass)
if(total > 0){
Exist for loop and return true
}
else{
Again iterate the loop to next value
}
What you described doesn't make much sense. If there are several tables that contain a column you're checking and you exit the loop as soon as you find the first one, what about the rest of them?
Here's what I'd do, see if it helps. I'll create a function (not a procedure) that returns a table. In order to do that, I'll create type(s) first:
SQL> create or replace type t_record as object (tn varchar2(30), cnt number);
2 /
Type created.
SQL> create or replace type t_table as table of t_record;
2 /
Type created.
SQL>
The function:
in a cursor FOR loop I'm selecting tables that contain that column
L_STR is used to compose the SELECT statement
DBMS_OUTPUT.PUT_LINE is used to display it first, so that I could visually check whether it is correctly set or not.
if it is, I'm running it with the EXECUTE IMMEDIATE
the result is stored into a table type and returned to the caller
SQL> create or replace function f_colname
2 (par_column_name in varchar2,
3 par_column_value in varchar2
4 )
5 return t_table
6 is
7 retval t_table := t_table();
8 l_str varchar2(200);
9 l_cnt number;
10 begin
11 for cur_r in (select table_name
12 from user_tab_columns
13 where column_name = par_column_name
14 )
15 loop
16 l_str := 'select count(*) from ' || cur_r.table_name ||
17 ' where ' || par_column_name || ' = ' ||
18 chr(39) || par_column_value || chr(39);
19 -- Display l_str first, to make sure that it is OK:
20 -- dbms_output.put_line(l_str);
21 execute immediate l_str into l_cnt;
22 retval.extend;
23 retval(retval.count) := t_record(cur_r.table_name, l_cnt);
24 end loop;
25 return retval;
26 end;
27 /
Function created.
Testing:
SQL> select * from table (f_colname('DEPTNO', '10'));
TN CNT
------------------------------ ----------
TEST_201812 1
DEPT 1
EMP 3
SQL> select * from table (f_colname('ENAME', 'KING'));
TN CNT
------------------------------ ----------
EMP 1
BONUS 1
SQL>
That won't work properly for some datatypes (such as DATE) and will have to be adjusted, if necessary.
[EDIT: after you edited the question]
OK then, that's even simpler. It should still be a function (that returns a Boolean, as you said that - in case that something's being found - you want to return TRUE). Code is pretty much similar to the previous function.
SQL> create or replace function f_colname
2 (par_column_name in varchar2,
3 par_column_value in varchar2
4 )
5 return boolean
6 is
7 l_str varchar2(200);
8 l_cnt number;
9 retval boolean := false;
10 begin
11 for cur_r in (select table_name
12 from user_tab_columns
13 where column_name = par_column_name
14 )
15 loop
16 l_str := 'select count(*) from ' || cur_r.table_name ||
17 ' where ' || par_column_name || ' = ' ||
18 chr(39) || par_column_value || chr(39);
19 -- Display l_str first, to make sure that it is OK:
20 -- dbms_output.put_line(l_str);
21 execute immediate l_str into l_cnt;
22 if l_cnt > 0 then
23 retval := true;
24 exit;
25 end if;
26 end loop;
27 return retval;
28 end;
29 /
Function created.
Testing: as you can't return Boolean at SQL layer, you have to use an anonymous PL/SQL block, as follows:
SQL> declare
2 l_ret boolean;
3 begin
4 if f_colname('DEPTNO', '15') then
5 dbms_output.put_line('It exists');
6 else
7 dbms_output.put_line('It does not exist');
8 end if;
9 end;
10 /
It does not exist
PL/SQL procedure successfully completed.
SQL>

How to build dynamic SQL with using

I am trying to make dynamic sql like this 'select col1,col2 from '|| my_table ||' it works fine but I want to write like this sql_stmt:='select col1,col2 from :myTable'; execute immediate sql_stmt using my_table; but I have error I have same error when I want to do something like this tooo v_filter := my_proc(); sql_stmt:='select col1,col2 from my_table where :filter' execute immediate sql_stmt using v_filter; is it impossible to build dynamic sql like this with using ? If it is impossible what is another way to avoid sql injections ?
When you want to use table names in dynamic SQL, yes - you'll have to concatenate them. In order to avoid SQL injection, use DBMS_ASSERT.SQL_OBJECT_NAME.
Here's an example:
SQL> create or replace procedure p_test (par_table in varchar2) is
2 l_table varchar2(30);
3 l_str varchar2(200);
4 l_cnt number;
5 begin
6 l_table := dbms_assert.sql_object_name(par_table);
7
8 l_str := 'select count(*) from ' || par_table;
9 execute immediate (l_str) into l_cnt;
10 dbms_output.put_line('Table contains ' || l_cnt || ' rows');
11 end;
12 /
Procedure created.
SQL>
SQL> exec p_test('dept');
Table contains 4 rows
PL/SQL procedure successfully completed.
SQL> exec p_test('delete from emp');
BEGIN p_test('delete from emp'); END;
*
ERROR at line 1:
ORA-44002: invalid object name
ORA-06512: at "SYS.DBMS_ASSERT", line 316
ORA-06512: at "SCOTT.P_TEST", line 6
ORA-06512: at line 1
SQL>
[EDIT: WHERE clause]
This works:
SQL> create or replace procedure p_test (par_table in varchar2,
2 par_filter in varchar2) is
3 l_table varchar2(30);
4 l_str varchar2(200);
5 l_cnt number;
6 begin
7 l_table := dbms_assert.sql_object_name(par_table);
8
9 l_str := 'select count(*) from ' || par_table ||
10 ' where deptno = :filter';
11 execute immediate (l_str) into l_cnt using par_filter;
12 dbms_output.put_line('Table contains ' || l_cnt || ' rows');
13 end;
14 /
Procedure created.
SQL> exec p_test('emp', '10');
Table contains 3 rows
PL/SQL procedure successfully completed.
SQL>
WHERE clause, modified so that it contains only the WHERE keyword, while the rest is to be used as a parameter:
SQL> create or replace procedure p_test (par_table in varchar2,
2 par_filter in varchar2) is
3 l_table varchar2(30);
4 l_str varchar2(200);
5 l_cnt number;
6 begin
7 l_table := dbms_assert.sql_object_name(par_table);
8
9 l_str := 'select count(*) from ' || par_table ||
10 ' where :filter';
11 execute immediate (l_str) into l_cnt using par_filter;
12 dbms_output.put_line('Table contains ' || l_cnt || ' rows');
13 end;
14 /
Procedure created.
SQL> exec p_test('emp', 'deptno = 10');
BEGIN p_test('emp', 'deptno = 10'); END;
*
ERROR at line 1:
ORA-00920: invalid relational operator
ORA-06512: at "SCOTT.P_TEST", line 11
ORA-06512: at line 1
SQL>
It won't work; is that what you're asking?
Some more reading about dynamic SQL on Oracle, as well as here, on Stack overflow (How can I create a dynamic WHERE clause.

Resources