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.
Related
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?
I need to loop over about 10 strings, which are all known to me in advance -- I don't need to SELECT them from anywhere.
I attempted:
BEGIN
FOR tab IN ('one', 'two', 'three')
LOOP
EXECUTE IMMEDIATE 'DROP TABLE ' || tab;
END LOOP;
END;
But that does not work (LOOP is unexpected)...
Is there a way to do this?
declare
type t_strings is table of varchar2(100);
strings t_strings:=t_strings('one','two','three');
begin
for i in 1..strings.count loop
dbms_output.put_line(strings(i));
end loop;
end;
/
Results:
one
two
three
Or you can use your own short-cut functions:
http://orasql.org/2017/10/02/plsql-functions-iterate-and-keys-for-associative-arrays/
Btw, Oracle 21 has some new features for FOR-LOOP:
1.1.1 PL/SQL Extended Iterators
FOR LOOP Iteration Enhancements in Oracle Database 21c
So it can be rewritten as:
declare
type t_strings is table of varchar2(100);
begin
for str in values of t_strings('one','two','three') loop
dbms_output.put_line(str);
end loop;
end;
/
"almost real" foreach loop:
declare
type tabList is table of varchar2(32);
tabs tabList := tabList ('one', 'two', 'three');
curr varchar2(32) := tabs.first;
begin
<<foreach>> loop exit foreach when curr is null;
execute immediate 'create table '||tabs(curr)||' (col char(1))';
execute immediate 'drop table '||tabs(curr)||' purge';
curr := tabs.next (curr);
end loop;
end;
/
PL/SQL procedure successfully completed.
begin
for tab_rec in ( select 'abc' val from dual
union
select '123' val from dual )
loop
EXECUTE IMMEDIATE 'DROP TABLE ' || tab_rec.val;
end loop;
end;
I have a procedure which returns result set from query which is dynamically generated in oracle. It do returns the result set but what I want is to process from information from the generated result set and save it in a table.
This is my query..
PROCEDURE GetItem(pitem_list in varchar2,
PGetData OUT SYS_REFCURSOR)
is
strsql2 long;
BEGIN
strsql2 :='SELECT val, val1, val2 from table1'; ----- This is a sample query as the main query is complex so just for simplicity I wrote this here.
open PGetData for strsql2; ----- This do returns me the result set generated from the query;
Now what I want is to execute the query stored in "strsql2" variable and read the result and process some information..
I thought of executing it from FOR LOOP.
strsql2 :='SELECT val, val1, val2 from table1';
for log in (select strsql2 from dual) ---- I even tried execute immediate
loop
if(log.val = 'TEST')
then
insert into table ----
else
update table --
end if;
end loop;
open PGetData for strsql2; --- After saving the result in table then return the result set..
Where I m going wrong here, or is there any other way to do it?
I m stuck here.
In that case, you can simply fetch from the cursor into local variables. In this case, I know that my query returns three columns, one an integer, one a string, and one a date so I'm declaring three local variables to hold the results.
declare
l_sql varchar2(1000);
l_rc sys_refcursor;
l_integer pls_integer;
l_string varchar2(100);
l_date date;
begin
l_sql := q'{select 1 int, 'foo' str, date '2020-12-21' dt from dual}';
open l_rc for l_sql;
loop
fetch l_rc
into l_integer, l_string, l_date;
exit when l_rc%notfound;
dbms_output.put_line( 'l_string = ' || l_string ||
' l_integer = ' || l_integer ||
' l_date = ' || to_char( l_date, 'dd-mon-yyyy' ) );
end loop;
end;
/
A liveSQL link
Could you help me to pass the input values (at execution time: i mean to enter multiple values for single variable at once).
Here is my code for which i am giving one input at a time either hard coded input or single input at time.
declare
type TEmpRec is record (
EmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
LastName EMPLOYEES.LAST_NAME%TYPE
);
type TEmpList is table of TEmpRec;
vEmpList TEmpList;
---------
function EmpRec(pEmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
pLastName EMPLOYEES.LAST_NAME%TYPE default null) return TEmpRec is
-- Effective "Record constructor"
vResult TEmpRec;
begin
vResult.EmployeeID := pEmployeeID;
vResult.LastName := pLastName;
return vResult;
end;
---------
procedure SearchRecs(pEmpList in out nocopy TEmpList) is -- Nocopy is a hint to pass by reference (pointer, so small) rather than value (actual contents, so big)
vIndex PLS_integer;
begin
if pEmpList is not null then
vIndex := pEmpList.First;
while vIndex is not null -- The "while" approach can be used on sparse collections (where items have been deleted)
loop
begin
select LAST_NAME
into pEmpList(vIndex).LastName
from EMPLOYEES
where EMPLOYEE_ID = pEmpList(vIndex).EmployeeID;
exception
when NO_DATA_FOUND then
pEmpList(vIndex).LastName := 'F'||pEmpList(vIndex).EmployeeID;
end;
vIndex := pEmpList.Next(vIndex);
end loop;
end if;
end;
---------
procedure OutputRecs(pEmpList TEmpList) is
vIndex PLS_integer;
begin
if pEmpList is not null then
vIndex := pEmpList.First;
while vIndex is not null
loop
DBMS_OUTPUT.PUT_LINE ( 'pEmpList(' || vIndex ||') = '|| pEmpList(vIndex).EmployeeID||', '|| pEmpList(vIndex).LastName);
vIndex := pEmpList.Next(vIndex);
end loop;
end if;
end;
begin
vEmpList := TEmpList(EmpRec(100),
EmpRec( 34),
EmpRec(104),
EmpRec(110));
SearchRecs(vEmpList);
OutputRecs(vEmpList);
end;
/
Above program takes input value one at time.
However, i tried as below but unable to succeed.
i tried to give input from console at once like (100,34,104,100) in place of either hard coding the input (or) giving one input at time.
Snippet in DECLARE section:
declare
type TEmpRec is record (
EmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
LastName EMPLOYEES.LAST_NAME%TYPE
);
type TEmpList is table of TEmpRec;
v_input TEmpList := TEmpList(&v_input); -- to read multiple input at once
vEmpList TEmpList;
In the final BEGIN section:
BEGIN
FOR j IN v_input.FIRST .. v_input.LAST LOOP
vEmpList := TEmpList(EmpRec(v_input(j).EmployeeID)); --to assign input values to vEmptList
SearchRecs(vEmpList);
OutputRecs(vEmpList);
end loop;
end;
/
Error in DECLARE section:
PLS-00306: wrong number or types of arguments in call to 'TEMPLIST'
Error in LAST BEGIN section:
PLS-00320: the declaration of the type of this expression is incomplete or malformed
As an example: at time, i am able to read multiple input values for same variable but i am unable to pass this as an input but unable to figure out how can make this as an input my main program.
DECLARE
TYPE t IS TABLE OF VARCHAR2(100);
ORDERS t := t(&ORDERS);
BEGIN
FOR j IN ORDERS.FIRST .. ORDERS.LAST LOOP
dbms_output.put_line(ORDERS(j));
END LOOP;
END;
/
Output:
PL/SQL procedure successfully completed.
Enter value for orders: 321,153,678
321
153
678
Thank You.
Since You have a collection of record variable, you need to pass employee_ids and employee last_names separately. How are you planning to pass them in a single shot?.
Here is a sample script which accomplishes something you want with 2 inputs for 3 collection elements.
First, create a collection TYPE and a PIPELINED function to convert comma separated values into Collections - f_convert2.
CREATE TYPE test_type AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE FUNCTION f_convert2(p_list IN VARCHAR2)
RETURN test_type
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN;
END f_convert2;
/
Then in your anonymous blocks pass values for employee_ids and last_name separately.
SET SERVEROUTPUT ON
DECLARE
TYPE temprec IS RECORD ( employeeid employees.employee_id%TYPE,
lastname employees.last_name%TYPE );
TYPE templist IS
TABLE OF temprec;
vemplist templist;
v_no_of_rec NUMBER := 10;
v_empl_ids VARCHAR2(100) := '&empl_ids';
v_empl_lnames VARCHAR2(100) := '&empl_lnames';
BEGIN
SELECT employee_id,last_name
BULK COLLECT
INTO
vemplist
FROM
(
SELECT
ROWNUM rn,
column_value employee_id
FROM
TABLE ( f_convert2(v_empl_ids) )
) a
JOIN (
SELECT
ROWNUM rn,
column_value last_name
FROM
TABLE ( f_convert2(v_empl_lnames) )
) b ON a.rn = b.rn;
FOR i in 1..vemplist.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(vemplist(i).employeeid || ' ' ||vemplist(i).lastname);
END LOOP;
END;
/
Instead of simple JOIN above if you use OUTER JOIN ( FULL or LEFT ), you can handle missing values without writing logic to check each value.
I have this query and it's not updating into the database. The given "where" clause is valid. When I run the query independently, it works fine but in this Procedure it's not working. There is no exception or no error. Could you guys help me in figuring out where the problem is?
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
I didn't write the commit code before. Just to clarity.
I suppose that col_id is the primary key. So in the update statement
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
you are always updating at most one row and thus the condition
SQL%ROWCOUNT > 1
is never true ( 1 is not > 1 )
So if you don't have any other commit statement in your procedure, you will never commit those updates.
By the way: what is the purpose of this
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
why don't you just commit at the end of your work?
The following code works okay (ie updates the row).
I suspect your error is elsewhere.
For example, if you don't initialise COUNTER, the increment will still leave it as null and it will never commit.
Or, l_vc_ColId may be the wrong datatype and suffering from an invalid conversion.
declare
v_emp_id number := 7839;
v_name varchar2(4) := 'DING';
v_tab varchar2(3) := 'EMP';
begin
execute immediate 'update '||v_tab||
' set ename = :v_name Where empno = :v_emp_id'
using v_name, v_emp_id;
dbms_output.put_line('C:'||sql%rowcount);
end;
you may want to reconsider your design if your using dynamic sql to change the "dest" table in thousands of updates.
Much better to know your dest and use bind variables for the where conditions. then you can commit every x rows using mod or similar:
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
But for your example, Marcin is correct, if you are updating only 1 row at a time, then
if SQL%ROWCOUNT > 1
will never be true;
EDIT:
A simple example knowing your "dest" table:
declare
cursor sel_cur is
select col1, col2, from sourceTable where col3 = 'X';
v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
v_ctr := v_ctr + 1;
-- implicit bind variables used
update destTable
set col1 = rec.col1,
col2 = rec.col2
where col3 = 'Z';
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
end loop;
exception
when others then rollback;
raise;
end;
If using dynamic SQL, a simple example using explicit bind variables (USING clause) from Oracle docs:
CREATE OR REPLACE PROCEDURE raise_emp_salary (column_value NUMBER,
emp_column VARCHAR2, amount NUMBER) IS
v_column VARCHAR2(30);
sql_stmt VARCHAR2(200);
BEGIN
-- determine if a valid column name has been given as input
SELECT COLUMN_NAME INTO v_column FROM USER_TAB_COLS
WHERE TABLE_NAME = 'EMPLOYEES' AND COLUMN_NAME = emp_column;
sql_stmt := 'UPDATE employees SET salary = salary + :1 WHERE '
|| v_column || ' = :2';
EXECUTE IMMEDIATE sql_stmt USING amount, column_value;
IF SQL%ROWCOUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('Salaries have been updated for: ' || emp_column
|| ' = ' || column_value);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Invalid Column: ' || emp_column);
END raise_emp_salary;
/
For more reading, see here.
Hope this helps, happy coding
Execute immediate needs explicit commit. I guess you checked that ?