PL/SQL via APEX Refresh Primary Column - oracle

I have a query to refresh the data in the table; i.e. purge completely all the data, and update the data.
I noticed that the Primary Column number continue to increase.
How do I rebuild this Column so that the number starts from 1 every time I refresh the data.

If you are not on 12c, assuming that your primary key is populated via sequence using a trigger. What you can do is :
Create a trigger with the logic to reset the sequence back to normal,
i.e. after every time you purge the table, the sequence would START
WITH 1 and INCREMENT BY 1. using ALTER SEQUENCE.
The sequence logic part using alter statement (Thanks to Tom Kyte for
this) :
create or replace
procedure reset_sequence(p_seq in varchar2)
is
l_value number;
begin
-- Select the next value of the sequence
execute immediate
'select ' || p_seq ||
'.nextval from dual' INTO l_value;
-- Set a negative increment for the sequence,
-- with value = the current value of the sequence
execute immediate
'alter sequence ' || p_seq ||
' increment by -' || l_value || ' minvalue 0';
-- Select once from the sequence, to
-- take its current value back to 0
execute immediate
'select ' || p_seq ||
'.nextval from dual' INTO l_value;
-- Set the increment back to 1
execute immediate
'alter sequence ' || p_seq ||
' increment by 1 minvalue 0';
end;
/
It is just to answer your question. However, IMHO, I would never like do that in any production system.

Related

How to Reset Oracle Sequence Safely?

I want to reset my Oracle Sequence to 0 everyday my code like this:
create or replace procedure reset_seq(p_seq_name in varchar2) is
l_val number;
begin
execute immediate 'select ' || p_seq_name || '.nextval from dual'
INTO l_val; --1
execute immediate 'alter sequence ' || p_seq_name || ' increment by -' ||
l_val || ' minvalue 0'; --2
execute immediate 'select ' || p_seq_name || '.nextval from dual'
INTO l_val; --3
execute immediate 'alter sequence ' || p_seq_name ||
' increment by 1 minvalue 0'; --4
end;
but after run about 2 years later , suddenly I get an error, the increment is an negative int like -16 and the start value also is -16. So anyone can help me to explain this issue.
What I think is that.
the procedure run to step 3 ,after that the current increment is -16 and nextval is 0.
other client request the sequence ,but the current increment is -16 so the next value is -16
the exception occured when the procedure run to step 4.
but I’m not sure about this, can anyone explain it to me. Thanks.
The issue is that both your job-submitted procedure and your external program could both call nextval between steps 2 and 4; and could call in either order; and potentially the external program could make multiple calls.
You could try to mitigate that by changing both the procedure and the external call.
At the moment if step 3 errors the procedure terminates and step 4 is never reached, so all future calls to nextval will continue to error. So you could trap and ignore the ORA-08004 error, on the basis that you'll only get that if there has been an external call between steps 2 and 3, the sequence has therefore been incremented back to zero by that call and thus step 3 is effectively redundant in that scenario:
create or replace procedure reset_seq(p_seq_name in varchar2) is
l_val number;
e_8004 exception;
pragma exception_init(e_8004, -8004);
begin
execute immediate 'select ' || p_seq_name || '.nextval from dual'
INTO l_val; --1
execute immediate 'alter sequence ' || p_seq_name || ' increment by -' ||
l_val || ' minvalue 0'; --2
begin
execute immediate 'select ' || p_seq_name || '.nextval from dual'
INTO l_val; --3
exception
when e_8004 then
-- nextval has already been called by someone else
null;
end;
execute immediate 'alter sequence ' || p_seq_name ||
' increment by 1 minvalue 0'; --4
end;
Now if an external program calls nextval between steps 2 and 3 then you'll get the error at step 3 but ignore it, and step 4 will still happen.
Your external program will get a nextval value of zero if it makes its call between steps 2 and 3; and will get the ORA-08004 if it calls between steps 3 and 4 - or if it makes multiple calls in the window between steps 2 and 4. So, assuming you don't expect zero to be a valid result (which seems reasonable as you wouldn't normally ever get that), you can make repeated calls until you get a non-zero answer and no error. In pseudocode something like:
loop
val = seq.nextval;
if error == -8004 then continue;
if val == 0 then continue;
break;
end loop
You could consider putting that logic into a PL/SQL function and have your program call the function instead of accessing the sequence directly, both to hide that complexity and to avoid having to repeat it if you have more than one program potentially affected.

When does user_tab_columns get updated?

In another question I tried to create a hist table, which keeps a log from the given table. With the answers in that question, I tried to create something new.
Since it is not possible to create a system trigger on tables or views, I created a DDL trigger like this:
create or replace trigger ident_hist_trig after alter on schema
declare
v_table varchar2(30);
begin
select upper(ora_dict_obj_name) into v_table from dual;
if (v_table = 'Z_IDENT') then
prc_create_hist_tabel('z_ident_hist', 'z_ident');
elsif (v_table = 'D_IDENT') then
prc_create_hist_tabel('d_ident_hist', 'd_ident');
elsif (v_table = 'X_IDENT') then
prc_create_hist_tabel('x_ident_hist', 'x_ident');
else
null;
end if;
end;
/
The procedure prc_create_hist_tabel looks like this:
create or replace procedure prc_create_hist_tabel(p_naam_hist_tabel in varchar2, p_naam_tabel in varchar2) is
cursor c is
select 'alter table ' || p_naam_hist_tabel || ' add ' || column_name || ' ' || data_type || case when data_type = 'DATE' then null else '(' || data_length || ')' end lijn
from user_tab_columns
where TABLE_NAME = upper(p_naam_tabel)
and column_name not in (select column_name from user_tab_columns where table_name = upper(p_naam_hist_tabel));
v_dummy number(1);
cursor trig is
select column_name || ',' kolom, ':old.' || column_name || ',' old
from user_tab_columns
where table_name = upper(p_naam_tabel);
v_trigger_sql varchar2(32767);
begin
begin
select 1 into v_dummy
from user_tab_columns
where TABLE_NAME = upper(p_naam_hist_tabel)
group by 1;
exception when no_data_found then
execute immediate 'create table ' || p_naam_hist_tabel || ' (wijziger varchar2(60) default user, wijzigdatum date default sysdate, constraint pk_' || p_naam_hist_tabel || ' primary key (wijziger, wijzigdatum))';
end;
dbms_output.put_line('BBB');
for i in c
loop
begin
dbms_output.put_line(i.lijn);
execute immediate i.lijn;
exception when others then
dbms_output.put_line(i.lijn);
end;
end loop;
v_trigger_sql := 'create or replace trigger ' || p_naam_tabel || '_hist_trig after update on ' || p_naam_tabel || ' for each row begin insert into ' || p_naam_hist_tabel || ' (';
for v_lijn in trig
loop
v_trigger_sql := v_trigger_sql || v_lijn.kolom;
end loop;
v_trigger_sql := substr(v_trigger_sql, 1, length(v_trigger_sql) - 1);
v_trigger_sql := v_trigger_sql || ') values (';
for v_lijn in trig
loop
v_trigger_sql := v_trigger_sql || v_lijn.old;
end loop;
v_trigger_sql := substr(v_trigger_sql, 1, length(v_trigger_sql) - 1);
v_trigger_sql := v_trigger_sql || '); end;';
execute immediate v_trigger_sql;
end;
/
In short what that function does, is maintain the history table. If it doesn't exist, it will create one, and if it exists, it will add the new columns to it. The procedure also creates a new trigger which will write the old values into the history table after update.
But when I alter one of the tables x_ident, z_ident or d_ident, the cursor c will return nothing (I can check that with the print when I loop through it). Although when execute the select after I altered my table, then I do get results.
The results I get from altering the table d_ident are these:
BBB
d_ident: Table altered.
But I guess it should be the other way around, I think that the procedure prc_create_hist_tabel is executed before the alter table actually goes off, and I guess I should get something like this:
d_ident: Table altered.
BBB
Any help would be apreciated. I tried to create a trigger on insert on user_tab_columns, but that gave me ORA-25001: cannot create this trigger type on views.
I tried with a sleep command as well, but that didn't work either.
This won't work. Even if you were able to get the column that is being added to the table in your trigger, if you tried to actually do DDL in a trigger, you'd get an error that DDL isn't allowed in a trigger.
I'd expect that the right way to approach this would be to make the call to prc_create_hist_tabel as part of your promotion scripts. Reasonable systems don't add columns to tables willy-nilly. The DDL is part of a promotion that exists in source control and gets deployed after testing. If your promotion scripts failed to modify the history table, you'd find out during testing that you missed a step and the change would never go to production. Having changes happen automatically means that they're not in change control which makes it more difficult to do a build from change control.
If you are determined to do this automatically, your trigger would need to submit a job, realistically using dbms_job not the newer dbms_scheduler, that calls the procedure. That job would run after the transaction the DDL trigger is a part of committed. At that point, the column would be visible in dba_tab_columns. And your job is free to do DDL.

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;

How can I alter a sequence in dynamic SQL?

I'm trying to create a script to migrate data from one DB to another. One thing I'm not currently able to do is set the nextval of a sequence to the nextval of a sequence in another DB.
I got the difference in values from user_sequences and generated the following dynamic SQL statements:
execute immediate 'alter sequence myseq increment by 100';
execute immediate 'select myseq.nextval from dual';
execute immediate 'alter sequence myseq increment by 1';
commit;
But nothing happens. What am I missing? If I run the same statements outside the procedure, they work fine:
alter sequence myseq increment by 100;
select myseq.nextval from dual;
alter sequence myseq increment by 1;
commit;
EDIT: Apologies to all for not being clear. I'm actually altering the sequence in the same DB. I'm only getting the value to be set from a remote DB. Perhaps it was unnecessary to mention the remote DB as it doesn't affect things. I only mentioned it to explain what my goals were.
Step 1. I get the nextval of the sequence from a remote DB.
select (select last_number
from dba_sequences#remoteDB
where upper(sequence_name) = upper(v_sequence_name)) - (select last_number
from user_sequences
where upper(sequence_name) = upper(v_sequence_name)) increment_by
from dual;
Step 2. I generate dynamic SQL statements with this value:
execute immediate 'alter sequence myseq increment by 100';
execute immediate 'select myseq.nextval from dual';
execute immediate 'alter sequence myseq increment by 1';
commit;
No error was raised, but nothing happened. When I wrote the SQL statements with DBMS_OUTPUT.PUT_LINE and ran them outside they worked.
Here is some code which dynamically sets a sequence to a new (higher) value. I have written this so it will work for any sequence in your schema.
create or replace procedure resync_seq
(p_seq_name in user_sequences.sequence_name%type)
is
local_val pls_integer;
remote_val pls_integer;
diff pls_integer;
begin
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
select last_number into remote_val
from user_sequences#remote_db
where sequence_name = p_seq_name ;
diff := remote_val - local_val;
if diff > 0
then
execute immediate 'alter sequence '|| p_seq_name ||' increment by ' ||to_char(diff);
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
execute immediate 'alter sequence '|| p_seq_name ||' increment by 1';
end if;
end;
The procedure doesn't need a COMMIT because DDL statements issue an implicit commit (two in fact).
You can execute it and see the synced value like this (in SQL*PLus):
exec resync_seq('MYSEQ')
select myseq.currval
from dual
Incidentally, the only way to reset a sequence (to its original starting value or a different lower value) is dropping and re-creating the sequence.
In 18c Oracle added a RESTART capability to ALTER SEQUENCE. The straightforward option ...
alter sequence myseq restart;
...resets the sequence to the value specified by the START WITH clause in the original CREATE SEQUENCE statement. The other option allows us to specify a new starting point:
alter sequence myseq restart start with 23000;
Excitingly this new starting point can be ahead or behind the current value (within the usual bounds of a sequence).
The one snag is that this new capability is undocumented (only for Oracle's internal usage) and so we're not supposed use it. Still true in 20c. The only approved mechanism for changing a sequence's value is what I outlined above.
I wasn't quite able to understand what you mean, but here is a wild guess:
I don't see it in your code, but you're talking about executing DDL (CREATE, ALTER etc.) on another database, so I assume you are using Database Links. It is not possible to use Database Links to execute DDL on another database. You might want to consider that.
After the information you provided, this might be what you need. And if you want to set the current value of the sequence, you can't, according to this documentation, you need to drop/create:
declare
ln_lastNumber DBA_SEQUENCES.LAST_NUMBER%type;
lv_sequenceName DBA_SEQUENCES.SEQUENCE_NAME%type := 'MYSEQ';
begin
select LAST_NUMBER
into ln_lastNumber
from DBA_SEQUENCES--or #remote_db;
where
--Your predicates;
execute immediate 'drop sequence ' || lv_sequenceName;
execute immediate 'create sequence ' || lv_sequenceName || ' starts with ' || ln_lastNumber;
exception
when no_data_found then
dbms_output.put_line('No sequence found!'); -- Or log somehow.
raise;
when others then
raise;
end;
Also, DDL in dynamic SQL pacakges requires
AUTHID CURRENT_USER
when declaring the package specification, if you want it to have the credentials of the current user
I took the code provided by APC and modified as below:
create or replace procedure resync_seq
(p_seq_name in user_sequences.sequence_name%type,
p_table_name in user_tables.table_name%type,
p_pk in user_cons_columns.column_name%type)
is
local_val pls_integer;
remote_val pls_integer;
diff pls_integer;
begin
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
execute immediate 'select max('||p_pk||') from '||p_table_name ||' '
into remote_val ;
diff := remote_val - local_val;
if diff > 0
then
execute immediate 'alter sequence '|| p_seq_name ||' increment by ' ||to_char(diff);
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
execute immediate 'alter sequence '|| p_seq_name ||' increment by 1';
end if;
end;

Reset auto increment sequence pl-sql

How can i reset auto increment primary key ?
I have a doc_id_seq and a doc_pk_trg trigger like that:
CREATE SEQUENCE doc_id_seq START WITH 1;
CREATE OR REPLACE TRIGGER doc_pk_trg
BEFORE INSERT ON TFIDF FOR EACH ROW
BEGIN
IF :NEW.doc_id IS NULL THEN
SELECT doc_id_seq.NEXTVAL INTO :NEW.doc_id FROM DUAL;
END IF;
END;
/
I want to learn reset the sequence . How can I do this ?
You could use the ALTER SEQUENCE syntax.
Tom Kyte explains how to do exactly this here:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1119633817597
Simply drop and then recreate the sequence.
This is how I would reset it
CREATE OR REPLACE PROCEDURE reset_sequence (
p_sequence_name IN VARCHAR2,
p_new_value IN NUMBER
)
AS
l_current_value NUMBER;
v_sequence_exists NUMBER;
BEGIN
SELECT 1
INTO v_sequence_exists
FROM user_sequences
WHERE sequence_name = p_sequence_name;
EXECUTE IMMEDIATE 'SELECT ' || p_sequence_name || '.nextval FROM dual'
INTO l_current_value;
/*Not possible to increment by 0 !*/
IF (p_new_value - l_current_value - 1) != 0
THEN
EXECUTE IMMEDIATE 'ALTER SEQUENCE '
|| p_sequence_name
|| ' INCREMENT BY '
|| (p_new_value - l_current_value - 1)
|| ' MINVALUE 0';
END IF;
EXECUTE IMMEDIATE 'SELECT ' || p_sequence_name || '.nextval FROM dual'
INTO l_current_value;
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || p_sequence_name || ' INCREMENT BY 1 ';
EXCEPTION
WHEN NO_DATA_FOUND
THEN
raise_application_error (-20001, 'Sequence does not exist');
END;
This has the added benefit over drop & recreate of not invalidating any dependant schema objects.
try this
alter table "table_name"
modify "id" generated always as identity restart start with 1;

Resources