Storing an entire column in a variable inside a stored procedure - oracle

I have a complex stored procedure inside a package. Inside this SP, I need to query a table to get all the data pertaining one column and then use this to check some other condition inside an "IF" statement.
Here is what I am doing:
--declare a variable to store the holidays
l_holidays MySchema. HolidayTable.DateColumn%TYPE
-- populate this variable
Select a.DateColumn into l_holidays
from MySchema. HolidayTable a;
-- using this variable inside an "IF" statement
IF (current_Date IN l_holidays)
THEN
-- do something
ELSE
-- do something
END IF;
Every time I run this, I get the following error
ORA-01422: exact fetch returns more than requested number of rows
I know this is because I am trying to populate the entire column using the "INTO" clause. But I don't know any other way of doing it.

Create a collection and use BULK COLLECT INTO:
CREATE PROCEDURE my_proc (
current_date IN MySchema.HolidayTable.DateColumn%TYPE
)
AS
TYPE date_tab IS TABLE OF MySchema.HolidayTable.DateColumn%TYPE;
l_holidays date_tab;
BEGIN
SELECT DateColumn
BULK COLLECT INTO l_holidays
FROM MySchema.HolidayTable;
IF (current_Date MEMBER OF l_holidays)
THEN
NULL; -- do something
ELSE
NULL; -- do something
END IF;
END;
Otherwise you can just test in the select:
CREATE PROCEDURE my_proc (
current_date IN MySchema.HolidayTable.DateColumn%TYPE
)
AS
has_date NUMBER(1,0);
BEGIN
SELECT CASE WHEN EXISTS ( SELECT 'X'
FROM MySchema.HolidayTable
WHERE DateColumn = Current_Date )
THEN 1
ELSE 0
END
INTO has_date
FROM DUAL;
IF has_date = 1
THEN
NULL; -- do something
ELSE
NULL; -- do something
END IF;
END;

Hello similarly you can use this query to fulfill your requirements
SET serveroutput ON;
SET sqlbl ON;
DECLARE
type l_holiday
IS
TABLE OF DATE;
tab_holiday l_holiday;
BEGIN
SELECT a.dt BULK COLLECT
INTO tab_holiday
FROM
(SELECT SYSDATE dt FROM DUAL
UNION
SELECT SYSDATE+1 dt FROM DUAL
UNION
SELECT SYSDATE+2 dt FROM DUAL
UNION
SELECT SYSDATE+3 FROM DUAL
)a;
IF tab_holiday.COUNT > 0 THEN
IF SYSDATE MEMBER OF tab_holiday THEN
dbms_output.put_line('yes working');
ELSE
dbms_output.put_line('awsme working');
END IF;
END IF;
END;

Related

PL/SQL - pass query in for loop stored in variable

i have a scenario where i need to create a dynamic query with some if/else statements, i have prepared the query but unable to use this inside loop below is the snippet of what i am trying to acheive.
Query :
select user_name into username from table1 where user ='user1';
for(query)
Loop
Dbms_output.put_line('user name ' || user_name);
END Loop;
is this possible to use the vaiable in the for loop ?
Not really. Notice in the 19c PL/SQL Reference it says:
cursor
Name of an explicit cursor (not a cursor variable) that is not open when the cursor FOR LOOP is entered.
You have to code this the long way, by explicitly fetching the cursor into a record until you hit cursorname%notfound, e.g.
create or replace procedure cheese_report
( cheese_cur in sys_refcursor )
as
type cheese_detail is record
( name varchar2(15)
, region varchar2(30) );
cheese cheese_detail;
begin
loop
fetch cheese_cur into cheese;
dbms_output.put_line(cheese.name || ', ' || cheese.region);
exit when cheese_cur%notfound;
end loop;
close cheese_cur;
end;
Test:
declare
cheese sys_refcursor;
begin
open cheese for
select 'Cheddar', 'UK' from dual union all
select 'Gruyere', 'France' from dual union all
select 'Ossau Iraty', 'Spain' from dual union all
select 'Yarg', 'UK' from dual;
cheese_report(cheese);
end;
/
Cheddar, UK
Gruyere, France
Ossau Iraty, Spain
Yarg, UK
In Oracle 21c you can simplify this somewhat, though you still have to know the structure of the result set:
create or replace procedure cheese_report
( cheese_cur in sys_refcursor )
as
type cheese_detail is record
( cheese varchar2(15)
, region varchar2(30) );
begin
for r cheese_detail in values of cheese_cur
loop
dbms_output.put_line(r.cheese || ', ' || r.region);
end loop;
end;
You could parse an unknown ref cursor using dbms_sql to find the column names and types, but it's not straightforward as you have to do every processing step yourself. For an example of something similar, see www.williamrobertson.net/documents/refcursor-to-csv.shtml.

oracle plsql complex statement inside if condition

In the pl/sql code i have a variable that grabs the designation_code from a table of a specific employee.
STMT#1
select basic_designation into source_designation
from tbl_emp_basic_profile where basic_id=source_id;
now I need to check if source_designation is in a set of codes or not. the {set of codes} can be generated by the following sql:
STMT#2
select distinct(BASIC_DESIGNATION) as "SET_OF_CODES"
from TBL_EMP_BASIC_PROFILE
where BASIC_DESIGNATION in (select to_number(SD_DESIGNATION_CODE)
from TBL_SETTINGS_DESIGNATION
where lower(SD_DESIGNATION_NAME) like '%professor%'
or lower(SD_DESIGNATION_NAME) like '%lecturer%');
how do I do it? Could I simply write an IF statement like the following?
IF(source_designation in (STMT#2)) then
--do somtehing
END IF;
I would have written it this way , using the exists to avoid extra scannings, and a count to avoid exception handling.
select count(1)
into designation_is_in_set
from dual
where exists (select 1 from TBL_SETTINGS_DESIGNATION
where to_number(SD_DESIGNATION_CODE)=source_designation
and (
lower(SD_DESIGNATION_NAME) like '%professor%'
or lower(SD_DESIGNATION_NAME) like '%lecturer%'
)
);
if designation_is_in_set=1 then
-- the des is in the set
else
-- the des is not in the set
end if;
You can do it that way.
1) declare collection
2) fetch all values into collection from sql.
3) Check if your value is in collection.
Example.
declare
type T_SET_OF_CODES is table of varchar2(xxx); -- where xxx is appropriate size of varchar2 for size_of_codes
V_SET_OF_CODES T_SET_OF_CODE;
begin
select distinct(BASIC_DESIGNATION) as "SET_OF_CODES" bulk collect into V_SET_OF_CODES
from TBL_EMP_BASIC_PROFILE
where BASIC_DESIGNATION in (select to_number(SD_DESIGNATION_CODE)
from TBL_SETTINGS_DESIGNATION
where lower(SD_DESIGNATION_NAME) like '%professor%'
or lower(SD_DESIGNATION_NAME) like '%lecturer%');
IF source_designation member of V_SET_OF_CODES then
--do somtehing
END IF;
end;

PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list

I have created a procure to display the data in two table using BULK COLLECT, but i keep getting this error.
PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
However it works if i remove the BULK COLLECT and include a where clause in the statement.
create or replace PROCEDURE sktReport IS
TYPE inventory_table_type is RECORD (
v_WH_ID INVENTORY.WH_ID%TYPE,
v_wa_Product_quantity_id Product_quantity.ST_ID%TYPE);
v_inventory_table inventory_table_type;
BEGIN
SELECT INVENTORY.WH_ID, Product_quantity.ST_ID,
BULK COLLECT INTO
v_inventory_table.v_WH_ID,
v_inventory_table.v_wa_Product_quantity_id,
FROM INVENTORY
INNER JOIN Product_quantity
ON Product_quantity.ST_ID = INVENTORY.ST_ID;
FOR i IN v_inventory_table.v_WH_ID..v_inventory_table.v_WH_ID
LOOP
DBMS_OUTPUT.PUT_LINE ('ID : ' || v_inventory_table.v_WH_ID
|| ' quantity ID : ' || v_inventory_table.v_in_Product_quantity_id);
END LOOP;
END;
Here's a simple showcase, I've just written that might help you :)
SET SERVEROUTPUT ON;
DECLARE
TYPE t_some_type IS RECORD (
the_id NUMBER
,the_name VARCHAR2(1)
);
TYPE t_some_type_tab IS TABLE OF t_some_type;
lt_some_record t_some_type;
lt_some_array t_some_type_tab := NEW t_some_type_tab();
BEGIN
WITH some_values AS (
SELECT
DECODE(LEVEL,1,1,2,2,3,3,4,4) AS the_id
,DECODE(LEVEL,1,'A',2,'B',3,'C',4,'D') AS the_name
FROM
dual
CONNECT BY LEVEL < 5
)
SELECT
sv.the_id
,sv.the_name
BULK COLLECT INTO -- use this to select into an array/collection type variable
lt_some_array
FROM
some_values sv;
DBMS_OUTPUT.PUT_LINE(lt_some_array.COUNT);
WITH some_values AS (
SELECT
DECODE(LEVEL,1,1,2,2,3,3,4,4) AS the_id
,DECODE(LEVEL,1,'A',2,'B',3,'C',4,'D') AS the_name
FROM
dual
CONNECT BY LEVEL < 5
)
SELECT
sv.the_id
,sv.the_name
INTO -- use this to select into a regular variables
lt_some_record.the_id
,lt_some_record.the_name
FROM
some_values sv
WHERE
sv.the_id = 1;
DBMS_OUTPUT.PUT_LINE(lt_some_record.the_id||': '||lt_some_record.the_name);
-- you can also insert such record into your array type variable
lt_some_array := NEW t_some_type_tab();
lt_some_array.EXTEND; -- extend the array type variable (so it could store one more element, than now - which was 0)
lt_some_array(lt_some_array.LAST) := lt_some_record; -- assign the first element of array type variable
DBMS_OUTPUT.PUT_LINE(lt_some_array.COUNT||' '||lt_some_array(lt_some_array.LAST).the_id||': '||lt_some_array(lt_some_array.LAST).the_name);
END;
/
Also, since you want to iterate through your results, you can just use cursor (implicit or explicit) e.g.
DECLARE
-- cursor declaration
CURSOR c_some_cursor IS
WITH some_values AS (
SELECT
DECODE(LEVEL,1,1,2,2,3,3,4,4) AS the_id
,DECODE(LEVEL,1,'A',2,'B',3,'C',4,'D') AS the_name
FROM
dual
CONNECT BY LEVEL < 5
)
SELECT
sv.the_id
,sv.the_name
FROM
some_values sv;
BEGIN
-- using explicit, earlier declared cursor
FOR c_val IN c_some_cursor
LOOP
DBMS_OUTPUT.PUT_LINE(c_val.the_id||': '||c_val.the_name);
END LOOP;
-- using implicit, not declared cursor
FOR c_val IN (
WITH some_values AS (
SELECT
DECODE(LEVEL,1,1,2,2,3,3,4,4) AS the_id
,DECODE(LEVEL,1,'A',2,'B',3,'C',4,'D') AS the_name
FROM
dual
CONNECT BY LEVEL < 5
)
SELECT
sv.the_id
,sv.the_name
FROM
some_values sv
)
LOOP
DBMS_OUTPUT.PUT_LINE(c_val.the_id||': '||c_val.the_name);
END LOOP;
END;
/
Here i have demonstrated a simple example to replicate your scenario. Please see below code. This may help you out.
SET serveroutput ON;
DECLARE
TYPE AV_TEST
IS
RECORD
(
lv_att1 PLS_INTEGER,
lv_att2 PLS_INTEGER );
type av_test_tab
IS
TABLE OF av_test;
av_test_tab_av av_test_tab;
BEGIN
NULL;
SELECT LEVEL,
LEVEL+1 BULK COLLECT
INTO av_test_tab_av
FROM DUAL
CONNECT BY LEVEL < 10;
dbms_output.put_line(av_test_tab_av.count);
FOR I IN av_test_tab_av.FIRST..av_test_tab_av.LAST
LOOP
dbms_output.put_line('working fine '||av_test_tab_av(i).lv_att1||' '||av_test_tab_av(i).lv_att2);
END LOOP;
END;

Using Rownum in Cursor Bulk Collect Oracle

I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;

How to get the last executed SQL statement and bind variable values in oracle

I have written the following query to get the last executed SQL statement in the oracle database for a particular session. The SQL text does not contain the actual value of the bind variables. How to get the bind variable values along with the SQL text.
SELECT * FROM v$SQLTEXT_WITH_NEWLINES WHERE address =
(SELECT prev_sql_addr FROM v$session WHERE audsid = userenv('SESSIONID'))
ORDER BY piece;
To get the bind variables you will have to use the code below, you dont need to use tracing.
SELECT * FROM v$sql_bind_capture WHERE sql_id='';
or
SELECT NAME,POSITION,DATATYPE_STRING,VALUE_STRING
FROM v$sql_bind_capture WHERE sql_id='';
http://shaharear.blogspot.com/2009/02/find-bind-variable-value.html
I don't think the bind variables values are stored by default. Not considering the potential security problems (seeing other sessions actual work), the amount of data to store would be massive.
If you want to see the values of the bind variables, you should activate the trace for that session. You would do this by executing the following command in that session:
alter session set events '10046 trace name context forever, level 12';
More information on AskTom: 10046 tracing
if you are in sqlplus you can execute
select * from table
( dbms_xplan.display_cursor (null,null, 'ADVANCED'));
or if you are looking for SQL executed by someone else just put in their the SQL_ID and child cursor #:
select * from table
( dbms_xplan.display_cursor ('sql_id',child_cursor#, 'ADVANCED'));
as in
select * from table
( dbms_xplan.display_cursor ('a18asdr99x',0, 'ADVANCED'));
This method shows the only shows peeked bind variables. The only dependable way is tracing with bind variables
dbms_monitor.session_trace_enable(session_id => 127,
serial_num => 29,
waits => FALSE,
binds => TRUE)
but of course that has to be done before the query gets executed
Run the below query which takes the sql_id as the input parameter and will give the output with replaced bind variable values.
set serveroutput on;
DECLARE
v_fulltext CLOB;
v_sql_id VARCHAR2 (100);
CURSOR c1( v_sql_id varchar2)
IS
SELECT decode(substr(NAME,1,4),':SYS',replace(name,':',':"')||'"' ,NAME ) NAME, POSITION, datatype_string,nvl(VALUE_STRING,'NULL') value_string
FROM v$sql_bind_capture
WHERE sql_id = v_sql_id;
BEGIN
v_sql_id:= '&sql_id';
SELECT sql_fulltext
INTO v_fulltext
FROM v$sql
WHERE sql_id =v_sql_id AND ROWNUM = 1;
FOR rec IN c1(v_sql_id)
LOOP
IF substr(rec.datatype_string,1,8) = 'VARCHAR2'
THEN
SELECT REPLACE (v_fulltext,
rec.NAME,
'''' || rec.value_string || ''''
)
INTO v_fulltext
FROM DUAL;
END IF;
IF rec.datatype_string = 'NUMBER'
THEN
SELECT REPLACE (v_fulltext, rec.NAME, rec.value_string)
INTO v_fulltext
FROM DUAL;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_fulltext);
EXCEPTION
WHEN NO_DATA_FOUND
THEN DBMS_OUTPUT.PUT_LINE('NO SQL FOUND FOR THE SQL ID');
END;
/
Looking at BiPin's answer I modified it a bit to suit my needs.
I needed to figure out what parameters users were using when running a report in real time. Here's my solution which adds the childnumber to the query from v$sql_bind_capture.
declare
v_sql_id varchar(100);
v_fulltext clob;
v_childnumber number;
begin
v_sql_id := '&sql_id';
v_childnumber := '&childnumber';
SELECT LISTAGG(SQL_text, '') within group (order by piece)
INTO v_fulltext
FROM v$sqltext
WHERE sql_id =v_sql_id;
for I in (select name,VALUE_STRING from v$sql_bind_capture where sql_id = V_SQL_ID and child_number = V_CHILDNUMBER)LOOP
v_fulltext := regexp_replace(v_fulltext,i.name||' ',i.value_string);
end LOOP;
DBMS_OUTPUT.PUT_LINE(v_fulltext);
end;

Resources