How to extract value inside with statement? - oracle

I want to initialize variable in pl/sql In the following way. But the value of the variable is null. how can I modify this code?
CREATE OR REPLACE PROCEDURE P_TEMP
(
pList0 OUT SYS_REFCURSOR
)
AS
TEMP_COL1 VARCHAR2(100);
TEMP_COL2 VARCHAR2(100);
BEGIN
OPEN cv_1 FOR
WITH TEMP_TABLE(COLUMN_1, COLUMN_2 ,...., COLUMN_N)
AS (SELECT COL_1, COL_2, ... , COL_N)
SELECT COL_1, COL_2
INTO TEMP_COL1, TEMP_COL2
FROM TEMP_TABLE;
DBMS_OUTPUT.PUT_LINE(TEMP_COL1); -- null!!
DBMS_OUTPUT.PUT_LINE(TEMP_COL2); -- null!!
END;

Don't open a cursor; just use the SELECT:
CREATE OR REPLACE PROCEDURE P_TEMP
(
pList0 OUT SYS_REFCURSOR
)
AS
TEMP_COL1 VARCHAR2(100);
TEMP_COL2 VARCHAR2(100);
BEGIN
WITH TEMP_TABLE(COLUMN_1, COLUMN_2 ,...., COLUMN_N) AS (
SELECT COL_1, COL_2, ... , COL_N
FROM your_table -- Need to select from a table.
)
SELECT COLUMN_1, COLUMN_2 -- Use the aliases you set in the sub-query factoring clause.
INTO TEMP_COL1, TEMP_COL2
FROM TEMP_TABLE
WHERE ROWNUM = 1 -- Only want a single row returned into the variables;
-- Or simpler
SELECT COL_1, COL_2
INTO TEMP_COL1, TEMP_COL2
FROM your_table
WHERE ROWNUM = 1 -- Only want a single row returned into the variables;
DBMS_OUTPUT.PUT_LINE(TEMP_COL1);
DBMS_OUTPUT.PUT_LINE(TEMP_COL2);
END;
/

You are missing the FETCH loop from the cursor. The easiest way to do this to use an implicit cursor:
CREATE OR REPLACE PROCEDURE P_TEMP AS
BEGIN
FOR rec IN
(
WITH temp_table(column_1, column_2 ,...., column_n) AS
(SELECT col_1, col_2, ... , col_n)
SELECT col_1, col_2
FROM temp_table
) LOOP
DBMS_OUTPUT.PUT_LINE(rec.col1);
DBMS_OUTPUT.PUT_LINE(rec.col2);
END LOOP;
END;
If, on the other hand, you only want to return the opened cursor, then you must not fetch from it yourself.
If you want to do both, i.e. fetch data yourself and return the cursor, then you must open the cursor twice for the same query. Once to process it yourself, once to give it to the caller to process it.
CREATE OR REPLACE PROCEDURE P_TEMP
(
out_cursor OUT SYS_REFCURSOR
)
AS
v_col1 VARCHAR2(100);
v_col2 VARCHAR2(100);
v_sql VARCHAR2(1000) := 'WITH temp_table ... SELECT col_1, col_2 FROM temp_table';
BEGIN
OPEN out_cursor FOR v_sql;
LOOP
FETCH out_cursor INTO v_col1, v_col2;
EXIT WHEN out_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('col_1: ' || v_col1);
DBMS_OUTPUT.PUT_LINE('col_2: ' || v_col2);
END LOOP;
OPEN out_cursor FOR v_sql;
END;

Related

How to pass string of comma-separated numbers to stored procedure in condition for numeric field?

I have a stored procedure like below where multiple employee IDs will be passed as comma-separated value (multiple IDs). It is throwing error as "ORA-01722: invalid number". I know it's because of passing varchar2 variable for the numeric ID column. But is there any way we can achieve this simply?
create or replace PROCEDURE Fetch_Emp_Name(Emp_id in varchar2)
IS
BEGIN
select Name from EMP where id in (emp_id);
END;
You can use dynamic sql.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
execute immediate
'select Name from EMP where id in (' || 'emp_id' || ')'
into
v_result;
end;
Also you can use package dbms_sql for dynamic sql.
Update
Another approach. I think may be better.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
select
Name
from
EMP
where
id in
(
select
to_number(regexp_substr(emp_id, '[^,]+', 1, level))
from
dual
connect by regexp_substr(emp_id, '[^,]+', 1, level) is not null
);
exception
when no_data_found then
-- error1;
when too_many_rows then
-- error2;
end;
Sorry for before, I did not get the question in the right way. If you get a lot of IDs as different parameters, you could retrieve the list of names as an string split by comma as well. I put this code where I handled by regexp_substr the name of different emp_ids you might enter in the input parameter.
Example ( I am assuming that the IDs are split by comma )
create or replace PROCEDURE Fetch_Emp_Name(p_empid in varchar2) IS
v_result varchar2(4000);
v_append emp.name%type;
v_emp emp.emp_id%type;
counter pls_integer;
i pls_integer;
begin
-- loop over the ids
counter := REGEXP_COUNT(p_empid ,'[,]') ;
--dbms_output.put_line(p_empid);
if counter > 0
then
i := 0;
for r in ( SELECT to_number(regexp_substr(p_empid,'[^,]+',1,level)) as mycol FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(p_empid ,'[,]')+1 )
loop
--dbms_output.put_line(r.mycol);
v_emp := r.mycol ;
select name into v_append from emp where emp_id = v_emp;
if i < 1
then
v_result := v_append ;
else
v_result := v_result ||','|| v_append ;
end if;
i := i + 1;
end loop;
else
v_emp := to_number(p_empid);
select name into v_result from emp where emp_id = v_emp;
end if;
dbms_output.put_line(v_result);
exception
when no_data_found
then
raise_application_error(-20001,'Not Employee found for '||v_emp||' ');
when too_many_rows
then
raise_application_error(-20002,'Too many employees for id '||v_emp||' ');
end;
Test
SQL> create table emp ( emp_id number, name varchar2(2) ) ;
Table created.
SQL> insert into emp values ( 1 , 'AA' );
1 row created.
SQL> insert into emp values ( 2 , 'BB' ) ;
1 row created.
SQL> commit;
SQL> insert into emp values ( 3 , 'CC' ) ;
1 row created.
SQL> select * from emp ;
EMP_ID NA
---------- --
1 AA
2 BB
3 CC
SQL> exec Fetch_Emp_Name('1') ;
AA
PL/SQL procedure successfully completed.
SQL> exec Fetch_Emp_Name('1,2,3') ;
AA,BB,CC
PL/SQL procedure successfully completed.
SQL>

How to insert multiple row result of dynamic sql to another Table?

I write one dynamic SQL which the result of it is a table with 2 columns and multiple rows, I want to insert it to another table with 4 columns that 2 of them will be filled by the result of dynamic SQL, I try to use collection but don't know how to insert result to another table
CREATE OR REPLACE PROCEDURE P_C_SM_Failure_error_Code_P2P AS
v_month VARCHAR2(16); -- to get Month for each table
v_day VARCHAR2(16); -- to get day for each table
v_ERRCODE t_c_rpt_resultmsg.code%TYPE;
v_ERRMSG t_c_rpt_resultmsg.MESSAGE%TYPE;
v_param VARCHAR2(16);
v_sql VARCHAR2(3000);
v_result number;
type t_c_result is record (Err_code varchar2(2000), Err_count number);
type v_t_result is table of t_c_result index by PLS_INTEGER;
v_t1_result v_t_result;
BEGIN
v_sql :='0';
v_param := 'Gateway_G';
v_result := '0';
select to_char(sysdate - 1,'MM') into v_month from dual;
select to_char(sysdate - 1,'DD') into v_day from dual;
-- Get count of P2P
v_sql := '(select count(*), error_code from (
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC01 where
orgaccount = '''||v_param||''' and destaccount = '''||v_param||''' and
sm_status <> 1 union all
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC02 where
orgaccount = '''||v_param||''' and destaccount = '''||v_param||''' and
sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql bulk collect into v_t1_result;
--insert into t_c_rpt_result2 values (trunc(sysdate, 'DD'), v_errcount,
v_err_code,'Failure_error_Code_P2P');
--for indx in 1 .. v_t1_result.COUNT
--loop
--dbms_output.put_line (v_t1_result (indx).Err_code);
--end loop;
You may append the constant values of date and the error message to the subquery and run a dynamic insert. It should also work if you remove the outer parentheses of your dynamic sql since constants can be included in group by. Always remember to pass values as bind variables rather than concatenating them (v_param). Also, specify the column names explicitly in an INSERT statement.
v_sql := '(select count(*) as cnt, error_code
from (
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC01
where orgaccount = :x and destaccount = :x and sm_status <> 1
union all
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC02
where orgaccount = :x and destaccount = :x and sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql bulk collect into v_t1_result using v_param;
EXECUTE IMMEDIATE 'insert into t_c_rpt_result2(err_dt,err_msg,errcount,error_code)
select :dt,:msg,cnt,error_code from '|| v_sql
USING trunc(sysdate, 'DD'),'Failure_error_Code_P2P',v_param;
I think you are looking at an excellent use case for FORALL. The collection you are populating needs to be done with execute immediate since you are dynamically constructing the table name. But the insert into t_c_rpt_result2 looks static to me.
BEGIN
v_sql :=
'(select count(*) as cnt, error_code
from (
select error_code from sm_histable'
|| v_month
|| ''
|| v_day
|| '#ORASMSC01
where orgaccount = :x and destaccount = :x and sm_status <> 1
union all
select error_code from sm_histable'
|| v_month
|| ''
|| v_day
|| '#ORASMSC02
where orgaccount = :x and destaccount = :x and sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_t1_result USING v_param;
FORALL indx IN 1 .. v_t1_result.COUNT
INSERT INTO t_c_rpt_result2 (err_dt,
err_msg,
errcount,
ERROR_CODE)
VALUES (TRUNC (SYSDATE, 'DD'),
'Failure_error_Code_P2P',
v_t1_result (indx).cnt,
v_t1_result (indx).ERROR_CODE);
END;
Find more examples of FORALL on LiveSQL here. Of course, even if your insert was dynamic, you can use FORALL - put the execute immediate directly "inside" the FORALL statement. But I don't think that complexity is justified here.
Hope that helps!

oracle out ref cursor to a loop

How to pass the 'values' of the output cursor o_cur to a further loop?
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
-- the question is related to this block:
for i in o_cur
loop
DBMS_OUTPUT.PUT_LINE(i.notnow);
end loop;
-----------------------
END;
FETCH is usually handled outside the procedure when the refcursor is an OUT variable
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
END;
/
SET SERVEROUTPUT ON
DECLARE
v_cur SYS_REFCURSOR;
v_notnow DATE;
v_today DATE;
BEGIN
dyn_cursor(v_cur);
LOOP
FETCH v_cur INTO
v_notnow,
v_today;
EXIT WHEN v_cur%notfound;
dbms_output.put_line(v_notnow);
END LOOP;
END;
/
02-10-18
04-10-18
PL/SQL procedure successfully completed.
This seems a strange construct. I would create a view you can reuse at different places.
CREATE OR REPLACE VIEW VW_Times AS
SELECT sysdate-1 notnow, sysdate today FROM dual
UNION ALL
SELECT sysdate+1 notnow, sysdate today FROM dual;
and
CREATE OR REPLACE PROCEDURE OutputTime ()
IS
CURSOR o_cur IS
SELECT * FROM VW_Times;
BEGIN
FOR i IN o_cur
LOOP
DBMS_OUTPUT.PUT_LINE(i.notnow);
END LOOP;
END;
Tanks to post of Kaushik Nayak, I found the acceptable solution:
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
type array is table of VARCHAR2(20) index by binary_integer;
l_datapoint1 array;
l_datapoint2 array;
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
LOOP
FETCH o_cur bulk collect into l_datapoint1, l_datapoint2;
for i in 1 .. l_datapoint1.count
loop
DBMS_OUTPUT.PUT_LINE( l_datapoint1(i) || ', ' || l_datapoint2(i) );
end loop;
exit when o_cur%notfound;
end loop;
close o_cur;
END;

How to fetch a ref_cursor into Oracle's WITH CLAUSE

Is it possible to use a ref_cursor with ORACLE WITH CLAUSE. For example, I have the following scenario. First I have a procedure which returns a ref_cursor
PROCEDURE p_return_cursor(p_id IN NUMBER, io_cursor OUT t_cursor)
AS
BEGIN
OPEN io_cursor FOR
SELECT col1, col2
FROM Table1 t
WHERE t.id = p_id;
END;
Second, I have another procedure on which I make a call to p_return_cursor:
PROCEDURE p_test(p_cid IN NUMBER)
AS
l_cursor t_cursor;
l_rec Table1%ROWTYPE;
BEGIN
p_return_cursor(p_id => p_cid, io_cursor => l_cursor);
-- CODE GOES HERE
...
Now, my question is, can I make a temp table using the Oracle's WITH CLAUSE using the cursor; something like:
...
WITH data AS (
LOOP
FETCH l_cursor INTO l_rec;
EXIT WHEN l_cursor%NOTFOUND;
SELECT l_rec.col1, l_rec.col2 FROM DUAL;
END LOOP;
CLOSE l_cursor;
)
You cannot do it directly. You can, however, BULK COLLECT your cursor into a PL/SQL table variable and use that in a WITH clause.
Be careful of memory usage if the cursor contains many rows.
Full example:
CREATE TABLE table1 ( col1 NUMBER, col2 NUMBER );
INSERT INTO table1 ( col1, col2 ) SELECT rownum, 100+rownum FROM DUAL CONNECT BY ROWNUM <= 15;
COMMIT;
CREATE OR REPLACE PACKAGE se_example AS
TYPE t_cursor IS REF CURSOR
RETURN table1%ROWTYPE;
TYPE l_rec_tab IS TABLE OF table1%ROWTYPE;
PROCEDURE p_test (p_cid IN NUMBER);
END se_example;
CREATE OR REPLACE PACKAGE BODY se_example AS
-- private
PROCEDURE p_return_cursor (p_id IN NUMBER, io_cursor OUT t_cursor) AS
BEGIN
OPEN io_cursor FOR
SELECT col1,
col2
FROM table1 t;
--WHERE t.id = p_id; -- I didn't put "id" column in my sample table, sorry...
END p_return_cursor;
PROCEDURE p_test (p_cid IN NUMBER) IS
l_cursor t_cursor;
l_tab l_rec_tab;
l_dummy NUMBER;
BEGIN
p_return_cursor (p_id => p_cid, io_cursor => l_cursor);
FETCH l_cursor BULK COLLECT INTO l_tab;
-- *** instead of this
-- WITH data AS (
-- LOOP
-- FETCH l_cursor INTO l_rec;
-- EXIT WHEN l_cursor%NOTFOUND;
-- SELECT l_rec.col1, l_rec.col2 FROM DUAL;
-- END LOOP;
-- CLOSE l_cursor;
-- )
-- '
--
-- *** do this
WITH data AS
( SELECT col1, col2 FROM TABLE(l_tab) )
SELECT sum(col1 * col2) INTO l_dummy
FROM data;
dbms_output.put_line('result is ' || l_dummy);
END p_test;
END se_example;
begin
se_example.p_test(100);
end;

Stored procedure for counting the row count of all the tables in database of a specific owner [duplicate]

This question already has answers here:
Get counts of all tables in a schema
(6 answers)
Closed 8 years ago.
I am creating a stored procedure in oracle to find the row count of all the tables. I have to run these procedure daily to find the daily data that is being loaded after ETL. I have created the below procedure.
create or replace Procedure Proc_RSE_TABLE_ROW_Count AS
TYPE c_table_list IS REF CURSOR ;
C_table_name c_table_list;
RSE_ROW_COUNT NUMBER;
SQL_11g VARCHAR2(500);
SQL_CURSOR VARCHAR2(500);
V_TABLE_NAME VARCHAR2(30);
BEGIN
SQL_CURSOR := 'SELECT
TRIM(table_name) table_name
FROM RSE_TABLE_COUNT ';
OPEN C_table_name FOR SQL_CURSOR;
LOOP
FETCH C_table_name INTO V_TABLE_NAME;
EXIT WHEN C_table_name%NOTFOUND;
SQL_11g := 'SELECT COUNT(1) FROM '||V_TABLE_NAME;
EXECUTE IMMEDIATE SQL_11g INTO RSE_ROW_COUNT;
INSERT into RSE_TABLE_COUNT (TABLE_NAME, ROW_COUNT, DATE_LAST_UPDATED)
values ('vtable_name', rse_row_count, sysdate)
/*UPDATE RSE_TABLE_COUNT SET ROW_COUNT=RSE_ROW_COUNT,DATE_LAST_UPDATED=sysdate
WHERE table_name=V_TABLE_NAME;
commit; */
END LOOP;
CLOSE C_table_name;
END;
but instead of which i am currently using i want to insert the daily data. With update the history is getting over ridden. And with insert i am getting error. I want to insert table_name, row_count, and sysdate in the table when i run the procedure without affecting previous data. My i am new to oracle syntax so please help..
See the following code snippet, quite similar to what you have and it insert rows correctly.
CREATE TABLE RSE_TABLE_COUNT
(
TABLE_NAME VARCHAR2(500 BYTE),
ROW_COUNT NUMBER,
DATE_LAST_UPDATED DATE
);
CREATE OR REPLACE PROCEDURE Proc_RSE_TABLE_ROW_Count
AS
TYPE c_table_list IS REF CURSOR;
C_table_name c_table_list;
RSE_ROW_COUNT NUMBER;
SQL_11g VARCHAR2 (500);
SQL_CURSOR VARCHAR2 (500);
V_TABLE_NAME VARCHAR2 (30);
BEGIN
SQL_CURSOR := 'SELECT
object_name
FROM user_objects
WHERE object_type=''TABLE''';
OPEN C_table_name FOR SQL_CURSOR;
LOOP
FETCH C_table_name INTO V_TABLE_NAME;
EXIT WHEN C_table_name%NOTFOUND;
SQL_11g := 'SELECT COUNT(1) FROM ' || V_TABLE_NAME;
EXECUTE IMMEDIATE SQL_11g INTO RSE_ROW_COUNT;
INSERT INTO RSE_TABLE_COUNT (TABLE_NAME, ROW_COUNT, DATE_LAST_UPDATED)
VALUES (V_TABLE_NAME, rse_row_count, SYSDATE);
END LOOP;
CLOSE C_table_name;
END;
Why don't you analyze the schema (estimate_percent=100) and select num_rows from user_tables? Probaly allready there is a gather_schema_stats in your ETL. After that:
insert into rse_table_count
select table_name,num_rows,sysdate
from user_tables;

Resources