Why this stored procedure in oracle not working? - oracle

I am new to oracle.
I have this stored procedure
create or replace PROCEDURE MJ_GetDepartments
(
mycursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN mycursor FOR
select rd.DEPT_PK, rd.long_fname from
ref_department rd
WHERE rd.DEPT_PK between
(
select rur.dept_idf , rur.dept_idt
from ref_user_role RUR
WHERE rur.user_fk = 10005941
)
END;

Did you mean something like this?
create or replace PROCEDURE MJ_GetDepartments
(mycursor OUT SYS_REFCURSOR)
AS
BEGIN
OPEN mycursor FOR
select rd.DEPT_PK, rd.long_fname
from ref_department rd join ref_user_role rur
on rd.DEPT_PK between rur.dept_idf and rur.dept_idt
where rur.user_fk = 10005941;
END;

My guess is you're trying to query using values for BETWEEN that come from a query?
You could do:
select rd.DEPT_PK, rd.long_fname from
ref_department rd
WHERE rd.DEPT_PK between
(
select rur.dept_idf
from ref_user_role RUR
WHERE rur.user_fk = 10005941
) and
(
select rur.dept_idt
from ref_user_role RUR
WHERE rur.user_fk = 10005941
)
But I think I'd do:
select rd.DEPT_PK, rd.long_fname
from
ref_department rd
INNER JOIN
ref_user_role RUR
ON
rd.DEPT_PK BETWEEN rur.dept_idf AND rur.dept_idt
WHERE rur.user_fk = 10005941
Or
select rd.DEPT_PK, rd.long_fname
from
ref_department rd
INNER JOIN
(
select rur.dept_idf, rur.dept_idt
from ref_user_role RUR
WHERE rur.user_fk = 10005941
) x ON rd.DEPT_PK between x.dept_idf AND x.dept_idf
Or select your from/to into two variables and then between those..
Whichever makes more sense to you..

Related

Update a table with dynamic query using bulk collect

I want update a table using a dynamic query, cursor and bulk collect. But I don't know the syntax:
declare
my_cursor ?;
-- other objects;
begin
execute immediate
"select s.col1, s.col2, rowid, d.rowid
from source_table s, destination_table d
where s.id = d.id "
BULK COLLECT INTO my_cursor;
FORALL i IN my_cursor.FIRST..my_cursor.LAST
UPDATE destination_table set col_a=my_cursor(i).col1 , col_b=my_cursor(i).col2
WHERE rowid = my_cursor(i).rowid;
commit;
end;
what would be the correct syntax and oracle objects
please help.
You can use something like this:
declare
type REC_TYPE is record (
ID SOURCE_TABLE.ID%type,
COL1 SOURCE_TABLE.COL1%type,
COL2 SOURCE_TABLE.COL2%type
);
type REC_CURSOR is ref cursor;
ref_cursor REC_CURSOR;
rec REC_TYPE;
sql_query VARCHAR2(4000);
begin
sql_query := 'select s.ID, COL1, COL2 from SOURCE_TABLE s, DESTINATION_TABLE d where s.ID = d.ID';
open ref_cursor for sql_query;
loop
fetch ref_cursor into rec;
exit when ref_cursor%NOTFOUND;
update DESTINATION_TABLE
set COL_A = rec.COL1, COL_B = rec.COL2
WHERE ID = rec.ID;
end loop;
close ref_cursor;
commit;
end;
/
or with bulk collect:
declare
type REC_TYPE is record (
ID SOURCE_TABLE.ID%type,
COL1 SOURCE_TABLE.COL1%type,
COL2 SOURCE_TABLE.COL2%type
);
type REC_TYPES is table of REC_TYPE;
type REC_CURSOR is ref cursor;
ref_cursor REC_CURSOR;
recs REC_TYPES;
sql_query VARCHAR2(4000);
begin
sql_query := 'select s.ID, COL1, COL2 from SOURCE_TABLE s, DESTINATION_TABLE d where s.ID = d.ID';
open ref_cursor for sql_query;
fetch ref_cursor bulk collect into recs;
close ref_cursor;
FOR ind IN recs.FIRST .. recs.LAST
loop
update DESTINATION_TABLE
set COL_A = recs(ind).COL1, COL_B = recs(ind).COL2
WHERE ID = recs(ind).ID;
end loop;
commit;
end;
/

Check if Exists PLS-00405: subquery not allowed in this context

I have cursor it selects from TableA then Fetch Loop that inserts into TableB.
I want to check if the value already exists in the TableB.
If it exists then I want to skip the insert.
create or replace
PROCEDURE DAILY_RPT (
v_start IN DATE,
v_end IN DATE)
IS
ao_out_no out_pair.out_no%type;
cursor get is
SELECT ao_out_no from tableA;
BEGIN
open get;
LOOP
fetch get into ao_out_no;
EXIT WHEN get%NOTFOUND;
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
--DO NOTHING
else
INSERT INTO TABLEB(OUT_NO) VALUES (ao_out_no);
end if;
END LOOP;
close get;
END;
I used IF CONDITION however, I used variable into if condition & I am getting below.
PLS-00405: subquery not allowed in this context
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
You don't need cursor or PL/SQL at all:
INSERT INTO TABLEB(OUT_NO)
SELECT ao_out_no
FROM tableA ta
WHERE ... -- filtering rows
AND NOT EXISTS (SELECT * From TableB tb WHERE tb.OUT_NO = ta.ao_out_no);
Use the following :
for i in (
select out_no from TableA where out_no
)
loop
if i.out_no = ao_out_no
then
-- DO NOTHING
else
...
or
create a new variable named x, and then assign a value to it by
select out_no into x from TableA where out_no = ao_out_no;
and check returning value for x.
With corrected syntax, it would be something like this:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no, 0 as exists_check
from tablea
)
loop
select count(*) into exists_check
from tablea
where out_no = r.ao_out_no
and rownum = 1;
if r.exists_check > 0 then
--DO NOTHING
else
insert into tableb (out_no) values (r.ao_out_no);
end if;
end loop;
end;
However, it's inefficient to query all of the rows and then do an additional lookup for each row to decide whether you want to use it, as SQL can do that kind of thing for you. So version 2 might be something like:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no
from tablea
where not exists
( select count(*)
from tablea
where out_no = r.ao_out_no
and rownum = 1 )
)
loop
insert into tableb (out_no) values (r.ao_out_no);
end loop;
end;
at which point you might replace the whole loop with an insert ... where not exists (...) statement.

PL/SQL - Use "List" Variable in Where In Clause

In PL/SQL, how do I declare variable MyListOfValues that contains multiple values (MyValue1, MyValue2, etc.)
SELECT *
FROM DatabaseTable
WHERE DatabaseTable.Field in MyListOfValues
I am using Oracle SQL Developer
Create the SQL type like this:
CREATE TYPE MyListOfValuesType AS TABLE OF VARCHAR2(4000);
And then use it in a SQL statement
DECLARE
MyListOfValues MyListOfValuesType;
BEGIN
MyListOfValues := MyListOfValuesType('MyValue1', 'MyValue2');
FOR rec IN (
SELECT *
FROM DatabaseTable
WHERE DatabaseTable.Field in (
SELECT * FROM TABLE(MyListOfValues)
)
)
LOOP
...
END LOOP;
END;
Up until Oracle 11g, this only works with a SQL TABLE type, not with a PL/SQL TABLE type. With Oracle 12c, you could also use PL/SQL types.
Use a collection:
CREATE TYPE Varchar2TableType AS TABLE OF VARCHAR2(200);
Or use a built-in type like SYS.ODCIVARCHAR2LIST or SYS.ODCINUMBERLIST:
VARIABLE cursor REFCURSOR;
DECLARE
your_collection SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
BEGIN
your_collection.EXTEND( 100 );
your_collection( 1) := 'Some value';
your_collection( 2) := 'Some other value';
-- ...
your_collection(100) := DBMS_RANDOM.STRING( 'x', 20 );
OPEN :cursor FOR
SELECT t.*
FROM your_table t
INNER JOIN
TABLE( your_collection ) c
ON t.id = c.COLUMN_VALUE;
END;
/
PRINT cursor;
How about using a WITH clause which basically builds a temp table? Not real reusable. You could use an array or I would argue joining to a lookup table would be better.
WITH MyListOfValues(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column in (
select col1
from MyListOfValues);

A way to log sql text when inserted

I Have the below trigger, the job of this trigger is to track the SQL Query of a developer when inserting on tab1, lets say if the dev executed this query :
update tab1 set col1 = 1;
the triger should insert that query in the TAB_LOGS.
however I am facing an error 'Exact fetch returns more than requested number of rows'
on this block
select a.SQL_TEXT into V_SQL
from v$session s
join v$sqlarea a on ( a.ADDRESS = s.SQL_ADDRESS )
where s.OSUSER = V_USERNAME;
this is the trigger
create or replace trigger TRG_test
after INSERT or update or delete
ON tab1
FOR EACH ROW
DECLARE
V_USERNAME VARCHAR2(100);
V_SQL varchar2(4000);
begin
SELECT SYS_CONTEXT('USERENV','OS_USER') into V_USERNAME FROM dual;
select a.SQL_TEXT into V_SQL
from v$session s
join v$sqlarea a on ( a.ADDRESS = s.SQL_ADDRESS )
where s.OSUSER = V_USERNAME;
insert into tab_logs (V_USERNAME,V_SQL);
end;
/
where s.OSUSER = V_USERNAME;
That would give you every SQL for every session with osuser V_USERNAME. There might be more than one such session (in fact, it is pretty common). What you actually want is to audit the SQL issued by the current session.
select a.SQL_TEXT into V_SQL
from v$session s
join v$sqlarea a on ( a.ADDRESS = s.SQL_ADDRESS )
where s.AUDSID = userenv('SESSIONID');

How to use list of selected values on the body of procedure

I need help with the structure of the procedure.
First of all, I should retrieve list of table names and owners.
At the body of the procedure I want to use this list for testing and comparisons.
I made some example that used cursor, I know it is not true.
Please advise how to implement this.
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
CURSOR C01 IS
(SELECT TABLE_NAME, OWNER
FROM TABLE1
UNION
SELECT TABLE_NAME, OWNER
FROM TABLE2);
BEGIN
SELECT MAX(SCORE)
INTO V_SCORE
FROM TABLE4 Q
WHERE EXISTS (SELECT 'Y'
FROM C01 T --- ?????????
WHERE Q.TABLE_NAME = T.TAB_NAME
AND Q.OWNER = T.OWNER);
END TEST_PROCEDURE;
The idea is good, but goes through some small misconceptions. Cursors are not intented to replace views or full resultsets, they are just allowing you to parse one by one (like a cursor) a give resultset.
You could use a common table expression (CTE) in this case:
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
BEGIN
WITH C01 AS (
SELECT
TABLE_NAME,
OWNER
FROM TABLE1
UNION
SELECT
TABLE_NAME,
OWNER
FROM TABLE2
)
SELECT MAX(SCORE) INTO V_SCORE
FROM TABLE4 Q
WHERE
EXISTS (
SELECT 'Y'
FROM C01 T
WHERE Q.TABLE_NAME = T.TAB_NAME
AND Q.OWNER = T.OWNER
);
END TEST_PROCEDURE;
If you need this CTE multiple times, then why not creating a view?
CREATE OR REPLACE VIEW C01 AS
SELECT
TABLE_NAME,
OWNER
FROM TABLE1
UNION
SELECT
TABLE_NAME,
OWNER
FROM TABLE2
;
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
BEGIN
SELECT MAX(SCORE) INTO V_SCORE
FROM TABLE4 Q
WHERE
EXISTS (
SELECT 'Y'
FROM C01 T
WHERE Q.TABLE_NAME = T.TAB_NAME
AND Q.OWNER = T.OWNER
);
END TEST_PROCEDURE;
Or even better, since you seem to just check that a set of values exist, create a deterministic function for this:
CREATE OR REPLACE FUNCTION EXISTS_IN_TABLES(I_OWNER IN VARCHAR2, I_TABLE IN VARCHAR2) RETURNS NUMBER DETERMINISTIC AS
BEGIN
SELECT 1
FROM (
SELECT TABLE_NAME, OWNER
FROM TABLE1
UNION
SELECT TABLE_NAME, OWNER
FROM TABLE2
) T
WHERE I_TABLE = T.TAB_NAME
AND I_OWNER = T.OWNER;
RETURN 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN RETURN 0;
WHEN OTHERS THEN RAISE;
END;
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
BEGIN
SELECT MAX(SCORE) INTO V_SCORE
FROM TABLE4 Q
WHERE
EXISTS_IN_TABLES(Q.OWNER, Q.TABLE_NAME) = 1
;
END TEST_PROCEDURE;
The deterministic option optimizes the performances, but then you have to be sure the content of TABLE1 and TABLE2 does not change during current session.

Resources