How to change the Oracle Sequence using loop? - oracle

Hope someone can help. When I tried to insert something into a table it give me error saying the primary key is already existed. So I need to reset my sequence so that it is always max(id)+1.
The table is called 'People' with 2 columns (ID, Name). The sequence is called SEQ.
I am thinking of doing a loop. To run select SEQ.nextval from dual for n times. this n= max(id)-SEQ.currval
Wwill this work? and how Can I put it into the syntax?
Thanks a lot.

declare
l_MaxVal pls_integer;
l_Currval pls_integer default - 1;
begin
select max(id)
into l_MaxVal
from people;
while l_Currval < l_Maxval
loop
select my_seq.nextval
into l_Currval
from dual;
end loop;
end;

If this is a one off, you can use the alter sequence
alter sequence sequenceName increment by val ;
whereas val is +1 to the maximum
then call get nextVal, then set the increment back to 1.
I threw the below together to show you how it can be done without looping.
create sequence changeValue start with 18 increment by 1 nocache ;
select changeValue.nextval from dual ;
/
NEXTVAL
----------------------
18
set serveroutput on
declare
maxVal number := 24 ;
curVal number ;
diffVal number ;
incrementVal number ;
procedure alterSequence(seqName in varchar2, incVal in number) as
s varchar2(500);
begin
s := 'alter sequence ' || seqName || ' increment by ' || incVal ;
dbms_output.put_line(s);
execute immediate s;
end alterSequence;
begin
--(done in 11gr2 so if in earlier version select into)
curVal := changeValue.currval ;
dbms_output.put_line('curValue=>' || curVal );
diffVal := maxVal - curVal ;
dbms_output.put_line('diffVal=>' || diffVal );
alterSequence ( 'changeValue' , diffVal + 1 );
incrementVal := changeValue.nextval ;
dbms_output.put_line('incrementVal=>' || incrementVal );
alterSequence ( 'changeValue' , 1 );
curVal := changeValue.currval ;
dbms_output.put_line('curValue=>' || curVal );
end ;
/
curValue=>18
diffVal=>6
alter sequence changeValue increment by 7
incrementVal=>25
alter sequence changeValue increment by 1
curValue=>25
or better yet, as #Dave suggests, just drop and recreate the sequence with the acceptable Start With value.

With this one you can synchronize the sequence whatever it is forward or behind the max of the ID.
Just need to change the parameters in the final of the code.
declare
procedure SYNC_SEQUENCE
( P_IN_SEQ in varchar2
, P_IN_TABLE in varchar2
, P_IN_ID in varchar2
)
is
LV_MAXVAL number := 0;
LV_CURRVAL number := -1;
LV_AUX NUMBER;
begin
execute immediate
'select max('||P_IN_ID||')
from '||P_IN_TABLE into LV_MAXVAL;
execute immediate
'select '||P_IN_SEQ||'.nextval
from dual ' into LV_CURRVAL;
if LV_MAXVAL < LV_CURRVAL then
LV_AUX := (LV_CURRVAL - LV_MAXVAL);
execute immediate
'ALTER SEQUENCE '||P_IN_SEQ||' INCREMENT BY -'||LV_AUX;
execute immediate
'SELECT '||P_IN_SEQ||'.NEXTVAL FROM dual' INTO LV_AUX;
execute immediate
'ALTER SEQUENCE '||P_IN_SEQ||' INCREMENT BY 1';
end if;
while LV_CURRVAL < LV_MAXVAL
loop
execute immediate
'select '||P_IN_SEQ||'.nextval
from dual ' into LV_CURRVAL;
end loop;
end SYNC_SEQUENCE;
begin
SYNC_SEQUENCE('MY_SEQUENCIE_NAME','MY_TABLE_NAME','MY_FIELD_ID_NAME');
end;
/

Related

arrays in pl sql | varray | associative

I'm planning to write a pl sql
pseudo code
procedure a gets all records from emp and is passed to procedure b
procedure b getting called inside from a , is transforming the data from emp , each emp record is traversed 6 times and storing the data in an array
inside procedure b , i used varray (size of 2000 to 2 mil) and also associative array , but it is always failing after handling 10 -15 records .i.e. after handling it for 60 -90 records
i am reusing the same index for storing my data but nothing worked i.e. 1 to 6
I am not sure what i am doing wrong , please assist .
create or replace PROCEDURE query_emp ( bill_cycle_ip in number,
no_of_recs in number ) AS
TYPE billAcnt IS REF CURSOR RETURN emp%ROWTYPE;
ba_rec emp%ROWTYPE;
b_a billAcnt;
total_num_of_rec number ;
BEGIN
OPEN b_a FOR
select * from
(select * from emp where bill_cycle =1 and ban_status in ('O' ,'S')
union
select * from emp where bill_cycle =1 and ban_status in ('N')
and STATUS_LAST_DATE >= sysdate -30
)
where ban not in (Select ban from temp_ba)
and rownum <no_of_recs;
LOOP
FETCH b_a INTO ba_rec;
EXIT WHEN b_a%NOTFOUND;
create_final_snapshot (ba_rec);
END LOOP;
CLOSE b_a;
END;
create or replace procedure create_final_snapshot ( ba_rec IN emp%ROWTYPE ) AS
cur_bl_seq_number number ;
bl_seq_number_minus_six number ;
type total_due_amt is table of number INDEX BY PLS_INTEGER ;
nt total_due_amt := total_due_amt();
b number := 1 ;
BEGIN
cur_bl_seq_number :=ba_rec.BL_CUR_BILL_SEQ_NO ;
if ba_rec.BL_CUR_BILL_SEQ_NO-5 <=1 then
bl_seq_number_minus_six:=1;
else
bl_seq_number_minus_six:=ba_rec.BL_CUR_BILL_SEQ_NO-5;
end if ;
for a in bl_seq_number_minus_six..cur_bl_seq_number
LOOP
--nt.extend();
select xyz into nt(b) from emp_2 where ban =ba_rec.ban and bill_seq_no =a ;
dbms_output.put_line('total_due_amt_list sun= ' ||ba_rec.ban ||' ' || nt(b) || ' bill seq no ' ||a ||' b ' || b );
b := b +1;
END loop;
b:=1;
insert into temp_ba
values (nt(1) ,nt(2), nt(3),nt(4),nt(5),nt(6) );
END;

Pass a table name and time stamp variable to a PL/SQL

I am writing a below PL/SQL code in SQL developer to delete data from a table with a timestamp column in the where condition. How can I modify this code to pass the table name and the timestamp value to values that I want based on what table and time records I want to delete the data from and create a stored procedure that can be reused.
DBMS_OUTPUT.ENABLE;
DECLARE
counter INTEGER := 0;
stop INTEGER;
BEGIN
dbms_output.put_line('START');
LOOP
counter := counter + 1;
DELETE my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM'
AND ROWNUM <= 100000;
SELECT COUNT(*)
INTO stop
FROM my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM';
EXIT WHEN stop <= 0;
COMMIT;
END LOOP;
dbms_output.put_line('Counter: ' || counter);
dbms_output.put_line('Left: ' || stop);
COMMIT;
END;
Adapting your anonymous to a stored procedure will, as indicated, require converting it to dynamic SQL. Always more difficult. And subject to SQL injection. For this you should validate string replacement parameters. I have a couple other changes:
Pass the desired as a timestamp, not a string, this allows/forces the
calling routine to determine the format and necessary conversion, if
any.
Added a parameter for column name as well. This frees naming columns
from the requirement of the procedure.
There is no need to count remaining items. Your loop processes until
that value reaches 0, but this can be determined by the number of
rows deleted on the last pass. Delete sets sql%rowcount to the number
of rows deleted. When the pass deletes 0 rows the process is
complete.
Removed the results display and the commit from the procedure, again
offloading this to the caller.
create or replace
procedure delete_values_by_timestamp
( p_table_name in varchar2
, p_column_name in varchar2
, p_timestamp in timestamp
, p_result_msg out varchar2
)
IS
table_name_parameter_invalid exception;
pragma exception_init(table_name_parameter_invalid, -44002);
column_name_parameter_invalid exception;
pragma exception_init(column_name_parameter_invalid, -44003);
k_nl constant varchar2(1) := chr(10);
k_max_delete_per_interation constant integer := 100000;
k_base_delete varchar2(256) :=
'delete from <table_name>' ||
' where <column_name> <= :1' ||
' and rownum <= :2';
v_delete_sql varchar2 (256) ;
v_rows_deleted integer := 0;
begin
v_delete_sql := replace(replace(k_base_delete,'<table_name>', dbms_assert.sql_object_name(p_table_name))
,'<column_name>',dbms_assert.simple_sql_name(p_column_name));
dbms_output.put_line('Running SQL:' || k_nl || v_delete_sql);
loop
execute immediate v_delete_sql using p_timestamp, k_max_delete_per_interation;
exit when sql%rowcount = 0;
v_rows_deleted :=v_rows_deleted + sql%rowcount;
end loop;
if v_rows_deleted = 0
then
p_result_msg := 'No Data Found';
else
p_result_msg := 'Number of Rows Deleted ' || to_char(v_rows_deleted);
end if;
exception
when table_name_parameter_invalid then
raise_application_error(-20199,'Invalid Table Name (' || p_table_name || ') specified.');
when column_name_parameter_invalid then
raise_application_error(-20198,'Invalid Column Name (' || p_column_name || ') specified.');
end delete_values_by_timestamp;
See example: In the example I reduce the number of rows deleted on each iteration from 100000 to 20. An additionally enhancement would be to pass the number of rows for each iteration as a parameter.
I couldn't test it but you could create a function whcih takes the table name and the timestamp as parameter.
As long you want to delete every record with the given timestamp you don't need to loop for each record.
This function should be just an example.
FUNCTION delete_values_by_timestamp (p_table_name IN VARCHAR2 DEFAULT NULL,
p_timestamp IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2
IS
v_count NUMBER := 0;
v_query VARCHAR2 (500) := '';
BEGIN
IF p_table_name IS NOT NULL
THEN
IF p_timestamp IS NOT NULL
THEN
v_query := 'SELECT COUNT(*)
FROM my_schema.' || p_table_name | '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query INTO v_count;
IF v_count > 0
THEN
v_query := 'DELETE FROM my_schema.' || p_table_name || '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query;
ELSE
RETURN 'NO RECORDS FOUND!';
END IF;
ELSE
RETURN 'TIMESTAMP EMPTY!';
END IF;
ELSE
RETURN 'TABLE NAME EMPTY!';
END IF;
END;

ORA-01652 - unable to extend temp segment by 4096 in tablespace (oracle 10)

Problem:
I'm new to Oracle, and I think I'm missing some basic knowledge that is causing my temp tablespace to fill up.
I'm opening a connection to my database and running a pl/sql procedure multiple times to insert rows. Each time I run the procedure, the number of free blocks decreases in my TEMP tablespace. When the number of free blocks gets too low, the procedure will fail with the error "ORA-01652 - unable to extend temp segment by 4096 in tablespace". If I close the database connection, the free blocks in the TEMP tablespace resets to the total number of blocks, and I can continue rerunning the procedure. How do I free up the TEMP tablespace blocks without having to close and open the database? I thought I needed to add a commit statement, but that didn't help.
Thanks
Code:
Query to check free_MB (this decreases each time I run the procedure).
SELECT tablespace_name,
total_blocks,
used_blocks,
free_blocks,
total_blocks*16/1024 as total_MB,
used_blocks*16/1024 as used_MB,
free_blocks*16/1024 as free_MB
FROM v$sort_segment;
SQL I run multiple times until free_mb reduces to 0 and I get errors:
DECLARE
p_samples LOG_ENTRY_ARRAY;
longSample clob;
BEGIN
For v_COUNTER IN 1..32767 LOOP
longSample := longSample || 'a';
END loop;
-- initialize the input
p_samples := LOG_ENTRY_ARRAY(longSample, 'short sample');
for i in 1..100 LOOP
INSERT_SUMMARY_SAMPLES('TABLE1', 1000, 1, 2, p_samples);
END loop;
commit;
END;
The procedure being called which does a bunch of inserts into two tables:
create or replace
PROCEDURE INSERT_SUMMARY_SAMPLES
(
p_TABLE_NAME IN VARCHAR2
, p_TS IN NUMBER
, p_SIGNATURE_ID IN NUMBER
, p_COUNT IN NUMBER
, p_SAMPLES IN LOG_ENTRY_ARRAY
) AS
tbl_summary varchar2(30);
tbl_samples varchar2(30);
summary_id number(10,0);
sample varchar2(32767);
BEGIN
tbl_summary := 'TBL_' || p_TABLE_NAME || '_SUMMARIES';
tbl_samples := 'TBL_' || p_TABLE_NAME || '_SAMPLES';
-- insert summary and get the id
EXECUTE IMMEDIATE 'INSERT INTO ' || tbl_summary
|| ' (agg_start_ts, signature_id, count, num_samples) VALUES (:a,:b,:c,:d) returning id into :1'
using p_ts, p_signature_id, p_count, p_SAMPLES.count returning into summary_id;
dbms_output.put_line('new summary_id is : ' || summary_id);
-- insert samples
FOR i in 1..p_SAMPLES.count LOOP
-- convert clob to varchar2
CLOB_TO_VARCHAR(p_SAMPLES(i),sample);
EXECUTE IMMEDIATE 'INSERT INTO ' || tbl_samples || ' (summary_id, log_entry) VALUES (:a,:b)' using summary_id, sample;
-- dbms_output.put_line('insert sample : ' || TO_CHAR(p_SAMPLES(i)));
END LOOP;
END INSERT_SUMMARY_SAMPLES;
CLOB_TO_VARCHAR is another procedure:
create or replace
PROCEDURE CLOB_TO_VARCHAR (
p_clob IN CLOB,
p_varchar OUT VARCHAR2
)
AS
v_output varchar2(32767);
l_amount BINARY_INTEGER := 32767;
l_pos INTEGER := 1;
l_clob_len INTEGER := 0;
BEGIN
l_clob_len := DBMS_LOB.getlength (p_clob);
WHILE l_pos < l_clob_len
LOOP
dbms_lob.READ(p_clob, l_amount, l_pos, v_output);
l_pos := l_pos + l_amount;
END LOOP;
p_varchar := v_output;
END CLOB_TO_VARCHAR;
Your TEMP table space is getting filled fast. You might need to increase the tablespace manually. Possible duplicate of ORA-01652 Unable to extend temp segment by in tablespace and
ORA-01652: unable to extend temp segment by 128 in tablespace SYSTEM: How to extend?
I would guess that you've got a temporary lob somewhere but it's not being explicitly freed. Where is LOG_ENTRY_ARRAY defined?

Automatically setting Oracle's sequence start value

I have many existing tables, each with a column called 'id'. This column has an integer value starting at 1. So for example, the table MY_TABLE contains 3 records with ids 1, 2 and 3 (super basic).
I want to create a sequence for each table I have and set its start value with the maximun id of the table. In my example, I would need something like this:
CREATE SEQUENCE MY_TABLE_SEQ START WITH 3 INCREMENT BY 1;
I tried something like this, but it didn't work:
CREATE SEQUENCE MY_TABLE_SEQ START WITH (SELECT NVL(MAX(id),1) FROM MY_TABLE) INCREMENT BY 1;
Any idea what I might be able to do?
Thanks
DECLARE
MAXVAL MY_TABLE.ID%TYPE;
BEGIN
SELECT NVL(MAX(id),1) INTO MAXVAL FROM MY_TABLE;
EXECUTE IMMEDIATE 'CREATE SEQUENCE MY_TABLE_SEQ START WITH ' || MAXVAL || ' INCREMENT BY 1';
END
/
You could also ALTER the sequences once they are created.
Some readings about the subject: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:951269671592
You could generate the CREATE SEQUENCE commands:
CREATE TABLE test_tab1 (
id number
);
CREATE TABLE test_tab2 (
id number
);
INSERT INTO test_tab1 VALUES (20);
INSERT INTO test_tab2 VALUES (40);
COMMIT;
DECLARE
v_max_id NUMBER;
BEGIN
FOR v_table IN (SELECT table_name
FROM user_tables
WHERE table_name IN ('TEST_TAB1', 'TEST_TAB2'))
LOOP
EXECUTE IMMEDIATE 'SELECT NVL(MAX(id), 1) FROM ' || v_table.table_name
INTO v_max_id;
dbms_output.put_line(
'CREATE SEQUENCE ' || upper(v_table.table_name) || '_SEQ START WITH ' || v_max_id || ' INCREMENT BY 1;');
END LOOP;
END;
Output:
CREATE SEQUENCE TEST_TAB1_SEQ START WITH 20 INCREMENT BY 1;
CREATE SEQUENCE TEST_TAB2_SEQ START WITH 40 INCREMENT BY 1;

Need help in execute immediate update query

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 ?

Resources