I want to insert looping with procedure, but when it's execute the error message is "ORA-01555: snapshot too old: rollback segment number with name "" too small". Is that a problem to do looping with sysdate in ref cursor clause?
here is my procedure
PROCEDURE AUTO_REGISTER_EXPIRED_POLICY
(
P_STATUS OUT VARCHAR2
,P_ERROR_MESSAGE OUT VARCHAR2
)
IS
P_USER_ID varchar2(3);
CURSOR C_GET_DTL_POLIS IS
SELECT pm.OFFICE, pm.SUBCLASS, pm.RESV, pm.YEAR, pm.MONTH, pm.SEQUENCE, pm.END_NO
FROM POLICY_MAIN pm
where to_char(trunc(exp_date), 'yyyymm') = to_char(add_months(trunc(sysdate)+0, 3),'yyyymm')
and not exists
;
BEGIN
p_user_id := 'adm';
FOR REC IN C_GET_DTL_POLIS
LOOP
PACKAGE_POLICY_RENEWAL_REVIEW.INSERT_RENEW
(
rec.office,
rec.subclass,
rec.resv,
rec.year,
rec.month,
rec.sequence,
rec.end_no
p_status,
p_error_message
);
END LOOP;
insert into log_renewal_review
select office, subclass, resv, year, month, sequence, end_no,
'LAPSED BY SYSTEM' REMARKS,
sysdate from renewal_review
where to_char(exp_date, 'yyyymm') = to_char(add_months(trunc(sysdate)+0, 3),'yyyymm')
and review_sts = '1'
;
P_STATUS := '1';
P_ERROR_MESSAGE := 'OK';
EXCEPTION WHEN OTHERS THEN
P_STATUS := '0';
P_ERROR_MESSAGE := SUBSTR(SQLERRM, 1, 100)
ROLLBACK;
END AUTO_REGISTER_EXPIRED_POLICY;```
when I look for solution mostly said id because sysdate is change time by time and it changed every time it insert data one by one.
Please Help me to find what is the problem? is it ok to use sysdate in ref cursor for looping?
Related
am trying to set up a test CASE, which calls a pipelined function that returns dates based on a DATE range. My goal is to have a row for each date.
I am getting the following error (see below) when I am trying to create the procedure. I know I have to JOIN the passed in p_id with the results returned back from the function but I can't seem to figure out how since there is no link to JOIN them with.
Can someone provide the correct code to fix this issue and explain what I did wrong. Thanks in advance to all who answer and your expertise.
Errors: PROCEDURE CREATE_DATA
Line/Col: 8/10 PL/SQL: SQL Statement ignored
Line/Col: 11/13 PL/SQL: ORA-00936: missing expression
CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
CREATE OR REPLACE FUNCTION generate_dates_pipelined(
p_from IN DATE,
p_to IN DATE
)
RETURN nt_date PIPELINED DETERMINISTIC
IS
v_start DATE := TRUNC(LEAST(p_from, p_to));
v_end DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
LOOP
PIPE ROW (v_start);
EXIT WHEN v_start >= v_end;
v_start := v_start + INTERVAL '1' DAY;
END LOOP;
RETURN;
END generate_dates_pipelined;
CREATE TABLE data(
d_id NUMBER(6),
d_date DATE
);
CREATE OR REPLACE PROCEDURE create_data (
p_id IN NUMBER,
p_start_date IN DATE,
p_end_date IN DATE
)
IS
BEGIN
INSERT INTO data (d_id, d_date)
VALUES
(p_id,
TABLE( generate_dates_pipelined(p_start_date, p_end_date)
) c
);
END;
EXEC create_data (1, DATE '2021-08-21', DATE '2021-08-30');
Your table expects a single DATE per row; you are trying to provide a collection of dates per row.
You need to INSERT for each row of the collection and can do that with a SELECT statement:
CREATE OR REPLACE PROCEDURE create_data (
p_id IN NUMBER,
p_start_date IN DATE,
p_end_date IN DATE
)
IS
BEGIN
INSERT INTO data (d_id, d_date)
SELECT p_id,
COLUMN_VALUE
FROM TABLE(generate_dates_pipelined(p_start_date, p_end_date));
END;
/
I am trying to write a code block where record insert if record already exist then update
table. i am trying If (sql%rowcount = 0) then but it's not working in cursor and more one records.
What I tried so far as the code block is
declare
remp_id varchar2(60);
remp_name varchar2(100);
rdesig varchar2(100);
rdept_no number;
rdesig_no number;
rdept_name varchar2(60);
cursor alfa is
select emp_code, emp_name, desig, dept_name, dept_no, desig_no
from emp
where emp_code between :first_code and :second_code;
begin
open alfa;
loop
fetch alfa
into remp_id, remp_name, rdesig, rdept_name, rdept_no, rdesig_no;
exit when alfa%notfound;
update att_reg_mo
set emp_code = remp_id,
emp_name = remp_name,
desig = rdesig,
dept_name = rdept_name,
dept_no = rdept_no,
desig_no = rdesig_no,
att_date = :att_date,
emp_att = :emp_att,
att_type = 'MA',
reg_date = :reg_date
where emp_code between :first_code and :second_code
and reg_date = :reg_date
and att_date = :att_date;
commit;
if (sql%rowcount = 0) then
insert into att_reg_mo
(emp_code,
emp_name,
desig,
dept_name,
att_date,
emp_att,
att_type,
reg_date,
dept_no,
desig_no)
values
(remp_id,
remp_name,
rdesig,
rdept_name,
:att_date,
:emp_att,
'MA',
:reg_date,
rdept_no,
rdesig_no);
end if;
commit;
end loop;
close alfa;
end;
when i am fire the trigger then record is insert but where need to update record it's update with null values
Or you could use something like that:
DECLARE
cursor test is
select 1 as v from dual
union
select 2 as v from dual;
n_var NUMBER;
BEGIN
for rec in test loop
BEGIN
select 1 into n_var from dual where rec.v=2;
DBMS_OUTPUT.PUT_LINE('Please Update Any Table');
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('Please Insert Any Table');
END;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Unexpected error');
END;
SQL%attribute always refers to the most recently run SELECT or DML statement. It refreshes as to start from zero after any of transaction statement such as COMMIT, ROLLBACK or SAVEPOINT is issued, for a session in which AUTOCOMMIT is presumed to be OFF by default. Therefore, you always get zero from SQL%ROWCOUNT which is just before the INSERT statement, and keeping inserting to the concerned table during every run of code block.
So, remove the f i r s t COMMIT, removing also keeps the atomicity of the whole transaction, from your code block.
Demo
Im trying to create a procedure which takes sys refcursor as in out parameter and modifies it based on the logic explained in comments in the below code
TYPE t_params IS
TABLE OF VARCHAR2(32767 CHAR);
/
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
/*
p_cursor IN OUT SYS_REFCURSOR
-- contains a single row {empId:123, ename:"king", mgr:"Porter",deptNo:200}
p_array_binary IN t_params
-- contains one binary value corresponding to each column in above cursor ["1","0","1","1"]
p_values IN t_params
-- contains one binary value corresponding to each column in above cursor ["123","king2","new manager","200"]
*/
IS
BEGIN
/*
Based on p_array_binary
if binary value 0 then take cursor should retain value as it is fro corresponding column
if binary value 1 then cusrsor should have the correspondoing column value from p_values
In short, the out cursor should be {empId:123, ename:"king", mgr:"new manager", deptNo:200}
*/
END;
/
Any help in this regard will be highly appreciated.
If you knew the ref cursor structure - it was always four columns of the data types shown - then this would be relatively simple:
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
IS
l_empid number;
l_ename varchar2(30);
l_mgr varchar2(30);
l_deptNo number;
BEGIN
-- get original values into local variables
fetch p_cursor into l_empId, l_ename, l_mgr, l_deptNo;
-- re-open cursor using either local variables of p_values depending on p_binary flag
open p_cursor for
select
case when p_array_binary(1) = '1' then to_number(p_values(1)) else l_empId end as empId,
case when p_array_binary(2) = '1' then p_values(2) else l_ename end as ename,
case when p_array_binary(3) = '1' then p_values(3) else l_mgr end as mgr,
case when p_array_binary(4) = '1' then to_number(p_values(4)) else l_deptNo end as deptNo
from dual;
END;
/
Demo using your sample data, via SQL*Plus/SQL Developer/SQLcl bind variables:
var rc refcursor;
begin
open :rc for
select 123 as empId, 'king' as ename, 'Porter' as mgr, 200 as deptNo
from dual;
modify_cursor(:rc, t_params('1', '0', '1', '1'), t_params('123', 'king2', 'new manager', '200'));
end;
/
print rc
EMPID ENAME MGR DEPTNO
---------- -------------------------------- -------------------------------- ----------
123 king new manager 200
db<>fiddle
Since you don't know the structure in advance, you will have to use dynamic SQL, which is bit more complicated. Here's an outline:
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
IS
l_c integer;
l_col_cnt integer;
l_desc_t dbms_sql.desc_tab3;
l_varchar2 varchar2(32767 char);
l_values t_params := new t_params();
l_result integer;
BEGIN
-- convert ref cursor to dbms_sql cursor
l_c := dbms_sql.to_cursor_number(rc => p_cursor);
-- analyse the cursor (columns, data types)
dbms_sql.describe_columns3(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
-- optionally check l_col_cnt matches sise of t_params arguments?
l_values.extend(l_col_cnt);
-- define each column for fetch; here you're treating everything as strings,
-- which will cause issues with some other data types
for i in 1..l_col_cnt loop
dbms_sql.define_column(c => l_c, position => i, column => l_varchar2, column_size => 32767);
end loop;
-- fetch original values - only one row to worry about so no loop
l_result := dbms_sql.fetch_rows(c => l_c);
for i in 1..l_col_cnt loop
-- depending on p_array_binary, set l_values from either fetched data or p_values
if p_array_binary(i) = '1' then
l_values(i) := p_values(i);
else
-- this forces everything to varchar2, which is OK (ish) for your sample data;
-- if you have other data types e.g. dates then you will probably want type-specific
-- handling so you can control the conversions - which affects this, define_column
-- and the final cursor to retrieve the values. But you have the same issue with p_values.
dbms_sql.column_value(c => l_c, position => i, value => l_values(i));
end if;
end loop;
-- finished with original cursor, so close it
dbms_sql.close_cursor(c => l_c);
-- re-open ref cursor using l_values data, with another dynamic SQL statement
l_varchar2 := 'select ';
for i in 1..l_col_cnt loop
if i > 1 then
l_varchar2 := l_varchar2 || ', ';
end if;
if l_desc_t(i).col_type = 2 then
l_varchar2 := l_varchar2 || l_values(i);
else
l_varchar2 := l_varchar2 || '''' || l_values(i) || '''';
end if;
l_varchar2 := l_varchar2 || ' as "' || l_desc_t(i).col_name || '"';
end loop;
l_varchar2 := l_varchar2 || ' from dual';
open p_cursor for l_varchar2;
END;
/
Running exactly the same demo block gives:
EMPID ENAM MGR DEPTNO
---------- ---- ----------- ----------
123 king new manager 200
db<>fiddle
You can add handling for other data types if needed, error handling etc.
Read more about dbms_sql.
declare
str varchar2(4000);
i int;
begin
for i in 1 ..31 loop
str:= str || 'col' || i || ' varchar2(2)';
if i < 31 then
str := str || ',';
end if;
end loop;
execute immediate 'create table t1 ('|| str ||')';
end;
/
I'm newbie in pl/sql
this procedure creates t1 table with 31 columns. 31 is day of month (say May). I can't create the procedure which have condition like this :
if col(i) i in ('Sat','Sun') ...
insert into t1 value ('r')
for example, this procedure must insert 'r' into col5,col6,col12,col13 .. columns
because 5th,6th,12th,13th of may in Sun or Sat
BEGIN
FOR i IN 1..31 LOOP
IF( to_char(sysdate-1+i,'DAY') IN ('SAT', 'SUN') )
THEN
INSERT INTO t1 (col(i))
VALUES ('r');
END IF;
END LOOP;
END;
/
I tried with this procedure but there are several errors
where must I correct my mistakes
thanks in advance
I agree with Bob Jarvis that you ought to have a better data model. But just for grins let's assume you have to work with what you've got.
This procedure takes a month and year as parameters, and generates a range of days from them. I have given the MON_T table two columns, MON and YR as a primary key, because I can't help myself.
create or replace procedure gen_month_rec
( p_mon in mon_t.mon%type
, p_yr in mon_t.yr%type )
is
lrec mon_t%rowtype;
empty_rec mon_t%rowtype;
first_dt date;
last_d pls_integer;
begin
lrec := empty_rec;
lrec.mon := p_mon;
lrec.yr := p_yr;
first_dt := to_date('01-'||p_mon||'-'||p_yr, 'dd-mon-yyyy');
last_d := to_number(to_char(last_day(first_dt),'dd'));
for i in 1 .. last_d
loop
if to_char(first_dt-+ (i-1),'DAY') in ('SAT', 'SUN')
then
execute immediate 'begin lrec.col'
||trim(to_char(i))
||' := ''r''; end;';
end if;
end loop;
insert into mon_t values lrec;
end;
I suggest that you read up on the rules of data normalization. It appears that this table suffers from a major problem in that it has what are known as "repeating groups" - in this case, 31 fields for information about each day. You probably need a table with the full date you're interested in, and then the fields which describe that date. Perhaps something like the following:
CREATE TABLE CALENDAR_DAY
(CAL_DATE DATE PRIMARY KEY,
COL NUMBER,
<definitions of other fields needed for each day>);
Given the above your code becomes
DECLARE
dtMonth_of_interest DATE := TRUNC(SYSDATE, 'MONTH');
BEGIN
FOR i IN 0..30 LOOP
IF( to_char(dtMonth_of_interest+i,'DAY') IN ('SAT', 'SUN') )
THEN
INSERT INTO CALENDAR_DAY (CAL_DATE, COL)
VALUES (dtMonth_of_interest + i, 'r');
END IF;
END LOOP;
END;
Hopefully this gives you some ideas.
Share and enjoy.
I have one table that holds a record for each customer (main table). I then have a table with additional detail for some customers. The additional detail table sometimes has no records for a record in the main table. Sometimes the detail table has multiple records for a record in the main table & if this is the case I need the most recent record (hence the max subselect).
The trouble is my function only returns values for the few records in the detail table. If I comment out the portion of the function that looks at the detail table and just return the STAT3 value it seems to work. How do I make the second select statment below only apply if there is a result for that query?
create or replace FUNCTION "F_RETURN_STAT" (
N_UNIQUE IN NUMBER)
RETURN VARCHAR2
IS
V_STAT3 varchar2(20);
V_STAT varchar2(20);
V_STAT2 varchar2(20);
D_ACTDATE date;
D_STARTDATE date;
BEGIN
select expire into D_ACTDATE
from main_table a
where a.uniquefield = N_UNIQUE;
IF
D_ACTDATE > SYSDATE
or
D_ACTDATE is null
then
V_STAT :='TRUE';
else
v_STAT :='FALSE';
end if;
select b.startdate into D_STARTDATE
from main_table a, detail_table b
where a.uniquefield= b.main_table_id(+) and
b.main_table_id = N_UNIQUE and
b.uniquefield in
(select max(c.uniquefield) from detail_table c group by main_table_id);
if
D_STARTDATE is not null
then
V_STAT2 :='FALSE';
end if;
if
V_STAT2 ='FALSE'
then
V_STAT3 :='FALSE';
ELSE
V_STAT3 := V_STAT;
end if ;
RETURN(V_STAT3);
end;
I think this version of your function will solve your problem:
CREATE OR REPLACE FUNCTION f_return_stat(n_unique IN NUMBER)
RETURN VARCHAR2 IS
v_stat3 VARCHAR2(20);
v_stat VARCHAR2(20);
v_stat2 VARCHAR2(20);
d_actdate DATE;
d_startdate DATE;
BEGIN
--First Query
SELECT expire
INTO d_actdate
FROM main_table a
WHERE a.uniquefield = n_unique;
IF d_actdate > SYSDATE OR d_actdate IS NULL THEN
v_stat := 'TRUE';
ELSE
v_stat := 'FALSE';
END IF;
BEGIN
--Second Query
SELECT b.startdate
INTO d_startdate
FROM detail_table b
WHERE b.main_table_id = n_unique
AND b.uniquefield IN (SELECT MAX(c.uniquefield)
FROM detail_table c
GROUP BY main_table_id);
EXCEPTION
WHEN NO_DATA_FOUND THEN
d_startdate := NULL;
END;
IF d_startdate IS NOT NULL THEN
v_stat2 := 'FALSE';
END IF;
IF v_stat2 = 'FALSE' THEN
v_stat3 := 'FALSE';
ELSE
v_stat3 := v_stat;
END IF;
RETURN (v_stat3);
END;
In your version of the second query, your join (a.uniquefield= b.main_table_id) and your filter (b.main_table_id = N_UNIQUE) are equivalent, so main_table a can be removed altogether. The only reason to leave it in is to make sure that your query always returns a row. If you use exception handling to catch the NO_DATA_FOUND exception, that need goes away and you can simplify your query to just select from detail_table b.
I believe there could be a more efficient way however this might do the job:
SELECT b.startdate
INTO d_startdate
FROM detail_table b
WHERE b.main_table_id = n_unique
and
(b.uniquefield in
(select max(c.uniquefield) from detail_table c group by main_table_id)
or b.uniquefield is null);