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;
Related
I need to assign a value to variable from select query output and call the variable into sql commands
For eg: I get PDB_NAME from v$pdbs and assign value to v_pdb
I want to use v_pdb in multiple sql commands to run against PDBs
I tried to assign value from SELECT query to v_pdb and call the v_pdb in 'alter session set container=v_pdb';, it looks like working, but i get ORA-00922: missing or invalid option error
set serveroutput on;
declare
v_sql varchar2(80);
v_pdb varchar2(30);
BEGIN
FOR pdb IN (select name from v$pdbs where con_id=3 and OPEN_MODE='READ WRITE')
LOOP
v_sql := 'alter session set container='||pdb.name;
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
--execute immediate 'alter session set container='||pdb.name||';';
execute immediate v_sql;
--v_sql := 'show con_name';
--execute immediate 'show con_name';
--execute immediate v_sql;
v_sql := 'create tablespace APPDATA datafile '+DATA' size 1G autoextend on next 100M maxsize 5G ENCRYPTION USING 'AES256' DEFAULT STORAGE (ENCRYPT)';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'drop user bigschema cascade';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
--execute immediate 'drop user bigschema cascade';
execute immediate v_sql;
v_sql := 'create user bigschema identified by B67_kuca_ecdf default tablespace APPDATA temporary tablespace TEMP profile DEFAULT account unlock';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'alter user bigschema quota unlimited on APPDATA';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'grant dba to bigschema';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'conn bigschema/"B67_kuca_ecdf"#'||pdb.name;
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'drop table MV2OCI';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'create table MV2OCI tablespace APPDATA as select * from dba_objects';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'alter table MV2OCI nologging';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'show user';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'insert into MV2OCI select * from dba_objects';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'insert into MV2OCI select * from MV2OCI';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
END LOOP;
END;
/
I simply want to get the value for variable v_pdb from "select name from v$pdbs where con_id=3 and OPEN_MODE='READ WRITE'"
And call the v_pdb as follows:
alter session set container=v_pdb;
run other sql commands
...
......
I believe the problem is in the trailing semi-colon in your dynamic SQL. Dynamic SQL does not include a trailing semi-colon -- since the dynamic SQL is a single statement, no statement-separator is required.
After dropping the trailing semi-colon (and the "show" command (a client command)) this works ok. But I don't know of a good way to get DBMS_OUTPUT going unless you are already in a given PDB. That has been dropped in this example.
declare
v_sql varchar2(80);
BEGIN
FOR pdb IN (select name from v$pdbs where con_id=3 and OPEN_MODE='READ WRITE')
LOOP
v_sql := 'alter session set container='||pdb.name;
execute immediate V_SQL;
DBMS_OUTPUT.ENABLE;
v_sql := 'CREATE TABLE TEST_TABLE(LOREM_IPSUM NUMBER)';
execute immediate V_SQL;
END LOOP;
END;
/
Result:
PL/SQL procedure successfully completed.
Navigating over to the PDB, TEST_TABLE now exists there.
I do not think that it actually has anything to do with your pdb variable...
When you use execute immediate you can not have a ; in the string
So for each of your execute immediate statments remove the ; eg
execute immediate 'alter session set container='||pdb.name||';';
becomes
execute immediate 'alter session set container='||pdb.name;
There are several ways to improve the code and the coding process:
Exclude statement terminators from dynamic SQL: As others have mentioned, remove the ; from the end of SQL statements used in dynamic SQL.
Escape strings: Strings in strings need to be escaped. The string 'DATA' should be ''DATA''.
Pay attention to the full error message: Always display the entire error message, including the line number and column number. That information points exactly to the problem.
Use the smallest possible example: A smaller example would have less errors, making it easier to find the real problem. And in the process of simplifying the example you will likely find the answer yourself.
I've number of tables that i want to drop some columns and add some another columns again. (oracle database)
All of tables are empty.
does it work??
DECLARE
CURSOR cursor_name
IS
SELECT TABLE_NAME
FROM SYS.ALL_TABLES
WHERE OWNER = 'username';
TN NVARCHAR2 (30);
TABLE_COUNT NUMBER (3);
TCDROP NVARCHAR2 (1000);
TCADD NVARCHAR2 (1000);
BEGIN
SELECT COUNT (1)
INTO TABLE_COUNT
FROM SYS.ALL_TABLES
WHERE OWNER = 'username';
OPEN cursor_name;
FOR i IN 1 .. TABLE_COUNT
LOOP
FETCH cursor_name INTO TN;
TCDROP := 'ALTER TABLE ' || TN || ' DROP (*columns list*);';
EXECUTE IMMEDIATE TCDROP;
TCADD :=
'ALTER TABLE ' || TN || ' ADD (*columns and datatype list*);';
EXECUTE IMMEDIATE TCADD;
EXIT WHEN cursor_name%NOTFOUND;
END LOOP;
CLOSE cursor_name;
END;
/
Yes, this will certainly work. I'd recommend to put in the owner/schema of the tables, though. Besides, it is unusual or unsafe to query the number of tables and the loop through the list. I'd put it in a simple for loop:
DECLARE
stmt VARCHAR2(1000);
BEGIN
FOR t IN (SELECT table_name FROM all_tables WHERE owner='XYZ') LOOP
stmt := 'ALTER TABLE '||owner||'.'||table_name||' DROP (*columns list*);';
DBMS_OUTPUT.PUT_LINE(stmt);
EXECUTE IMMEDIATE (stmt);
stmt:= 'ALTER TABLE '||owner||'.'||table_name||' ADD (*columns and datatype list*);';
DBMS_OUTPUT.PUT_LINE(stmt);
EXECUTE IMMEDIATE (stmt);
END LOOP;
END;
/
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.
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.
I need a help on this.
I am trying to create a pl/sql anonymous block and when I run that it shows its completed but it don't run the code. It should give me an error saying name is already used by an existing object. Can someone help me on this.
I am actually creating procedures but just trying this code as a sample.
DECLARE
V_REF_TBL VARCHAR2(100);
V_SQL LONG;
begin
V_REF_TBL :='My_TABLE';
v_SQL :='truncate table '||V_REF_TBL ;
EXECUTE IMMEDIATE V_SQL;
EXECUTE IMMEDIATE 'CREATE TABLE '|| V_REF_TBL ||' parallel 9 nologging pctfree 0 as
select * from dual';
End;
Possibly you're looking for this:
<<my_block>>
Declare
table_name varchar2(30);
counter number;
Begin
table_name := 'my_table';
select count(*)
into counter
from user_tables
where table_name = Upper(Trim(my_block.table_name)) and
dropped = 'NO';
if counter = 0
then
execute immediate 'create table '||table_name||' as ... etc';
else
execute immediate 'truncate table '||table_name;
execute immediate 'insert into '||table_name' select ... etc';
end if;
end my_block;