How do I fix auto increment in oracle after a data refresh - sql

Every time my team does a data refresh to our UAT environment we have issues with the 'auto incremented' columns in oracle They hold onto the old value and therefore cause errors when a new insert happens. The only solution I have found is to use
select test_SEQ.nextval from test_table;
Until the next sequence is bigger then the max seq number in the table. I have over 200 tables to update, is there an easier why to do this?
Thanks
Erin

One better way to do this would be to drop the sequences and create new ones with the desired START WITH value. You could generate the DDL to do this dynamically.

Check the following sqlfiddle http://sqlfiddle.com/#!4/17345/1
It doesn't completely work due to limitations in sqlfiddle, but here's the function that makes it happen:
create or replace function
reset_sequence(p_sequence_name varchar,
p_table_name varchar,
p_column_name varchar)
return integer is
v_temp integer;
v_sql varchar(2000);
begin
v_sql := 'select nvl(max('||p_column_name||'),0)+1 col_name from '||p_table_name;
execute immediate v_sql INTO v_temp;
v_sql := 'drop sequence '||p_sequence_name;
execute immediate v_sql;
v_sql := 'create sequence '||p_sequence_name||' start with '||v_temp;
execute immediate v_sql;
return v_temp;
end;
Basically you call this function and pass a schema name, table name and column name and it will set the function to the correct value. You can put a begin/exception/end block to ignore errors when dropping the sequence, in case it doesn't exist, but all that is just icing. You could also have it detect the column that is the primary key if you wanted, but no real way in Oracle to detect the sequence name.
You could also make a procedure version of this, but I tend to prefer functions for whatever reason.

Related

How to send cursor response from Oracle stored procedure

I have a stored procedure which I want to return cursor as the OUT parameter. I see the output in
dbms_output.put_line(as_return_val); like
Loan Purpose can not be Limited || /loanPurpose
THE PURPOSE OF PURCHASE IS NOT VALID || /loanPurpose
I need to read this response as a array of Objects(each ro one Object) in my service layer. Sorry I am not sure I need to send it as a cursor or like a varchar from here. I added as_return_val this variable to see whether I am getting correct response .
Procedure
create or replace PROCEDURE PR_LOGIC_CHECK_TEST
(in_loan_id IN NUMBER,
in_trans_id IN NUMBER)AS
BEGIN
DECLARE
as_errm VARCHAR2(2000);
curr_cursor_out SYS_REFCURSOR;
temp_cursor_row temp_cor_ll_cursor%rowtype;
as_return_val VARCHAR2(500);
BEGIN
pr_loan_logic_check(in_loan_id, in_trans_id, as_errm, curr_cursor_out);
LOOP
FETCH curr_cursor_out INTO temp_cursor_row;
EXIT WHEN curr_cursor_out%notfound;
as_return_val := temp_cursor_row.ret_value
|| ' || '
|| '/'||temp_cursor_row.xpath_name;
--dbms_output.put_line(as_return_val);
END LOOP;
close curr_cursor_out;
ROLLBACK;
END;
END PR_LOGIC_CHECK_TEST;
I'm still guessing at what you really want to happen since you haven't actually given us a reproducible test case (i.e. something that we can run locally or on dbfiddle or liveSQL that produces a particular output given a particular input).
Architecturally, it seems problematic to have a stored procedure whose only purpose is to format the output of a different procedure for human consumption. Formatting results would more properly be done in the display layer (the view of an MVC application but view means something else when we're talking about databases so I'm using "display layer") of the application not in a stored procedure. It would make more sense for your application to call pr_loan_logic_check directly and to let your display layer decide to concatenate the values from multiple columns together. If someone wants to change how the output is formatted later, you'd then just be changing code in the display layer not in your backend database.
My guess is that you want to return a collection like this. Note that I'm creating the collection type at the SQL level. You could create it in a PL/SQL package as well.
create or replace type varchar2_tbl
is table of varchar2(500);
create or replace PROCEDURE PR_LOGIC_CHECK_TEST (
in_loan_id IN NUMBER,
in_trans_id IN NUMBER,
out_strings OUT varchar2_tbl
)
AS
as_errm VARCHAR2(2000);
curr_cursor_out SYS_REFCURSOR;
temp_cursor_row temp_cor_ll_cursor%rowtype;
BEGIN
pr_loan_logic_check(in_loan_id, in_trans_id, as_errm, curr_cursor_out);
out_strings := out_strings();
LOOP
FETCH curr_cursor_out INTO temp_cursor_row;
EXIT WHEN curr_cursor_out%notfound;
out_strings.extend();
out_strings( out_strings.count ) :=
temp_cursor_row.ret_value
|| ' || '
|| '/'||temp_cursor_row.xpath_name;
END LOOP;
close curr_cursor_out;
-- I have trouble imagining why you'd put a `rollback` here
ROLLBACK;
END PR_LOGIC_CHECK_TEST;

Error using Oracle Ref Cursor

I'm trying to do something fairly simple, I am trying to automate the removal and back up of tables from my personal table space. I have about 100 tables and want to get rid of all of them (except a table that I'm using to store the table names), but want to keep the data from the tables in case I need them sometime in the future. Below is the code I am trying to use to accomplish this. I am getting an error on the ref cursor, which I'll include below my code. I half expect somebody to tell me I'm an idiot and explain an easier way to do this. If not, please tell me what I'm doing wrong with the way that I am doing it, thanks.
DECLARE
v_folder_name VARCHAR2(100) := 'MY_FOLDER';
TYPE QRY_CURSOR IS REF CURSOR;
v_qry_cursor QRY_CURSOR;
v_file_name VARCHAR2(320);
v_file sys.utl_file.file_type;
v_max_buffer_length CONSTANT BINARY_INTEGER := 32767;
v_qry_str VARCHAR2(4000); --I've tried this with 32767, made no difference
v_drop_string VARCHAR2(4000);
v_dynamic_record VARCHAR2(4000); --tried this with 32767 also
CURSOR GET_TABLE_NAMES IS
SELECT * FROM TEMP_BACKUP_TABLE WHERE TABLE_NAME <> 'TEMP_BACKUP_TABLE';
FUNCTION startFile(file_name VARCHAR2)
--working function, used with many procedures, left out for brevity
END startFile;
FUNCTION closeFile(file_name VARCHAR2)
--working function, used with many procedures, left out for brevity
END closeFile;
BEGIN
INSERT INTO TEMP_BACKUP_TABLE SELECT DISTINCT TABLE_NAME FROM ALL_TAB_COLS WHERE OWNER = 'ME';
COMMIT;
FOR REC IN GET_TABLE_NAMES LOOP
v_file_name := REC.TABLE_NAME;
v_file := startFile(v_file_name);
v_qry_str := 'SELECT * FROM ' || v_file_name;
v_drop_string := 'DROP TABLE ' || v_file_name;
OPEN v_qry_cursor FOR v_qry_str; -- this is the line that returns an error
LOOP
FETCH v_qry_cursor INTO v_dynamic_record;
EXIT WHEN v_qry_cursor%NOTFOUND;
sys.utl_file.put_line(v_file, v_dynamic_record);
END LOOP;
CLOSE v_qry_cursor;
EXECUTE IMMEDIATE v_drop_string;
COMMIT;
v_file := closeFile(v_file_name);
END LOOP;
DELETE FROM TEMP_BACKUP_TABLE;
END;
The error I'm getting is as follows:
Error report:
ORA-00932: inconsistent datatypes: expected - got -
ORA-06512: at line 73
00932. 00000 - "inconsistent datatypes: expected %s got %s"
*cause:
*action:
Thanks for any help.
At a minimum, utl_file.put_line does not take arbitrary record and you can't fetch an arbitrary list of columns into a varchar2.
You could iterate over each column and construct a SQL statement that concatenates the values from each column into a single string. That would include doing things like putting a to_char with an explicit format mask on your date or timestamp columns, adding a delimiter, escaping any delimiters that exist in your data, etc. This is generally a rather tedious and error-prone process. And then you'll need to write a SQL*Loader control file to load the data back in the future.
It sounds like you'd be better off exporting the table using the Oracle export utility. That's a command-line utility (exp or expdp depending on whether you want to use the classic version or the DataPump version) that lets you export the table definition and data to a file that you can load later using the Oracle import utility.

Trigger pl/sql insert data on table

i'm new in pl/sql. I'm trying to create a trigger that insert datas in specific tables.
I have datas that arrives in real-time on my table EV_48h. To know on which table I have to insert the data i have to know it Ref_equip (Ref_equip is on an other table named C_Equip).
I've made quickly this littre merise to be more understandable:
merise
As I said I have data that arrives on real-time on the table EV_48H and I have to put them automatically on the tables that are named 'EVV_'+Ref_equip.
So, here is my code. I don't have any error but it don't work. I know i missed of forget something but i don't know what.
TRIGGER "SIVO"."NEWtrigger3EV_48H"
BEFORE INSERT
ON SIVO.EV_48H
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
clef_var number(4,0);
ref_equip varchar2(40);
V_Nom_table varchar2(1000) ;
V_nom_seq Varchar2(2000) ;
stmt varchar2(200);
begin
SELECT clef_var
INTO :New.Clef_Var
FROM sivo.c_variable
WHERE Ref_Var= :new.Ref_Var;
-- Conversion des formats Date-Heure en DateHeure oracle
:New.EV_DATEAUTO := to_date(:New.EV_DATE || ' ' || :New.EV_HEURE, 'DD/MM/YY HH24:MI:SS');
stmt:='begin select clef_var into :New.Clef_Var From sivo.C_variable; end';
EXECUTE IMMEDIATE stmt using out clef_var;
IF clef_var is not null then
stmt :='begin select Ref_equip into :New.Ref_Equip FROM sivo.C_Equip WHERE Ref_var= :New.Ref_Var; end';
EXECUTE IMMEDIATE Stmt USING OUT Ref_Equip;
V_nom_table := 'EVV_'||Ref_Equip;
stmt :='insert into' ||V_nom_table || '(:New.Clef_Var, :New.Ev_DateAuto, :New.Ev_Valeur )';
EXECUTE IMMEDIATE stmt USING Ref_Equip;
ELSE
INSERT INTO SIVO.EV_48H_VAR_INCONNUES (REF_VAR, EV_DATE, EV_HEURE, EV_VALEUR)
VALUES ( :New.REF_VAR, :New.EV_DATE, :New.EV_HEURE, :New.EV_VALEUR);
end if;
END;
If someone can help me or put me on the right way. I don't know if I give all informations so tell me if I missed something.
Thanks
In your execute immediate you are missing the end of statement indicator colon after END
this should be
begin select clef_var into :New.Clef_Var From sivo.C_variable; end;
However there are other design choices that you should be aware of:
using execute immediate is handy but if you don't have to use it you shouldn't. The work can be done by a cursor or even a simple select statement if only one value will come back. In fact it appears you do the work twice. First you insert into the :new.clef_var then you do the same thing again with the execute immediate. Try commenting out the execute immediate.
by using execute immediate any errors are harder to track
using a trigger means the real time data source cannot end the transaction until the trigger completes. Why not run a scheduled job every minute to check for new data and process it? This breaks the transaction into two parts: data entry and data processing
is there any update of records that your process needs to capture?

PL/SQL Dynamic Loop Value

My goal is to keep a table which contains bind values and arguments, which will later be used by dbms_sql. The below pl/sql example is basic, it's purpose is to illustrate the issue I am having with recalling values from prior loop objects.
The table account_table holds acccount information
CREATE TABLE account_table (account number, name varchar2(100)));
INSERT INTO mytest
(account, name)
VALUES
(1 ,'Test');
COMMIT;
The table MYTEST holds bind information
CREATE TABLE mytest (bind_value varchar2(100));
INSERT INTO mytest (bind_value) VALUES ('i.account');
COMMIT;
DECLARE
v_sql VARCHAR2(4000) := NULL;
v_ret VARCHAR2(4000) := NULL;
BEGIN
FOR I IN (
SELECT account
FROM account_table
WHERE ROWNUM = 1
) LOOP
FOR REC IN (
SELECT *
FROM mytest
) LOOP
v_sql := 'SELECT ' || rec.bind_value || ' FROM dual';
EXECUTE IMMEDIATE v_sql INTO v_ret;
dbms_output.put_line ('Account: ' || v_ret);
END LOOP;
END LOOP;
END;
/
I cannot store the name i.account and later use the value that object. My idea was to use NDS; but, while the value of v_sql looks ok (it will read "Select i.account from dual"), an exception of invalid identifier would be raised on i.account. Is there a way to get the value of the object? We are using Oracle 11g2.
Thanks!
Dynamic SQL will not have the context of your PL/SQL block. You cannot use identifiers that are defined in the PL/SQL block in your dynamic SQL statement. So you cannot reference i.account and reference the loop that you have defined outside of the dynamic SQL statement. You could, of course, dynamically generate the entire PL/SQL block but dynamic PL/SQL is generally a pretty terrible approach-- it is very hard to get that sort of thing right.
If you are trying to use the value of i.account, however, you can do something like
v_sql := 'SELECT :1 FROM dual';
EXECUTE IMMEDIATE v_sql
INTO v_ret
USING i.account;
That doesn't appear to help you, though, if you want to get the string i.account from a table.
Are you always trying to access the same table with the same where clause, but just looking for a different column each time? If so, you could do this:
p_what_I_want := 'ACCOUNT';
--
SELECT decode(p_what_I_want
,'ACCOUNT', i.account
, 'OTHER_THING_1', i.other_thing_1
, 'OTHER_THING_2', i.other_thing_2
, 'OTHER_THING_1', i.default_thing) out_thing
INTO l_thing
FROM table;
The above statement is not dynamic but returns a different column on demand...

Best way to generate unique and consecutive numbers in Oracle

I need to generate unique and consecutive numbers (for use on an invoice), in a fast and reliable way. I currently use an Oracle sequence, but in some cases, the generated numbers are not consecutive because of exceptions that may occur.
I thought a couple of solutions to manage this problem, but neither of them convincing me. What solution do you recommend?
Use a select max ()
SELECT MAX (NVL (doc_num, 0)) +1 FROM invoices
Use a table to store the last number generated for the invoice.
UPDATE docs_numbers
SET last_invoice = last_invoice + 1
Another Solution?
Option 1 can always be made to fail some way in an environment with concurrent
users.
Option 2 will work, but will limit scalability — obligatory Tom Kyte
reference:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1508205334476
As he recommends, you should really review the necessity for the "no gaps" requirement.
The gaps appear if a transaction uses a sequence number but is then rolled back.
Maybe the answer is not to assign the invoice number until the invoice can't be rolled back. This minimizes (but probably does not eliminate) the possibilities of gaps.
I'm not sure that there is any swift or easy way to ensure no gaps in the sequence - scanning for MAX, adding one, and inserting that is probably the closest to secure, but is not recommended for performance reasons (and difficulties with concurrency) and the technique won't detect if the latest invoice number is assigned, then deleted and reassigned.
Can you account for gaps somehow - by identifying which invoice numbers were 'used' but 'not made permanent' somehow? Could an autonomous transaction help in doing that?
Another possibility - assuming that gaps are relatively few and far between.
Create a table that records sequence numbers that must be reused before a new sequence value is grabbed. Normally, it would be empty, but some process that runs every ... minute, hour, day ... checks for gaps and inserts the missed values into this table. All processes first check the table of missed values, and if there are any present, use a value from there, going through the slow process of updating the table and removing the row that they use. If the table is empty, then grab the next sequence number.
Not very pleasant, but the decoupling of 'issuing invoice numbers' from 'scan for missed values' means that even if the invoicing process fails for some thread when it is using one of the missed values, that value will be rediscovered to be missing and re-reissued next time around - repeating until some process succeeds with it.
Keep the current sequence - you can use the following to reset the value to the maximum of what is currently stored in the table(s):
-- --------------------------------
-- Purpose..: Resets the sequences
-- --------------------------------
DECLARE
-- record of temp data table
TYPE data_rec_type IS RECORD(
sequence_name VARCHAR2(30),
table_name VARCHAR2(30),
column_name VARCHAR2(30));
-- temp data table
TYPE data_table_type IS TABLE OF data_rec_type INDEX BY BINARY_INTEGER;
v_data_table data_table_type;
v_index NUMBER;
v_tmp_id NUMBER;
-- add row to temp table for later processing
--
PROCEDURE map_seq_to_col(in_sequence_name VARCHAR2,
in_table_name VARCHAR2,
in_column_name VARCHAR2) IS
v_i_index NUMBER;
BEGIN
v_i_index := v_data_table.COUNT + 1;
v_data_table(v_i_index).sequence_name := in_sequence_name;
v_data_table(v_i_index).table_name := in_table_name;
v_data_table(v_i_index).column_name := in_column_name;
END;
/**************************************************************************
Resets a sequence to a given value
***************************************************************************/
PROCEDURE reset_seq(in_seq_name VARCHAR2, in_new_value NUMBER) IS
v_sql VARCHAR2(2000);
v_seq_name VARCHAR2(30) := in_seq_name;
v_reset_val NUMBER(10);
v_old_val NUMBER(10);
v_new_value NUMBER(10);
BEGIN
-- get current sequence value
v_sql := 'SELECT ' || v_seq_name || '.nextval FROM DUAL';
EXECUTE IMMEDIATE v_sql
INTO v_old_val;
-- handle empty value
v_new_value := in_new_value;
if v_new_value IS NULL then
v_new_value := 0;
END IF;
IF v_old_val <> v_new_value then
IF v_old_val > v_new_value then
-- roll backwards
v_reset_val := (v_old_val - v_new_value) * -1;
elsif v_old_val < v_new_value then
v_reset_val := (v_new_value - v_old_val);
end if;
-- make the sequence rollback to 0 on the next call
v_sql := 'alter sequence ' || v_seq_name || ' increment by ' ||
v_reset_val || ' minvalue 0';
EXECUTE IMMEDIATE (v_sql);
-- select from the sequence to make it roll back
v_sql := 'SELECT ' || v_seq_name || '.nextval FROM DUAL';
EXECUTE IMMEDIATE v_sql
INTO v_reset_val;
-- make it increment correctly again
v_sql := 'alter sequence ' || v_seq_name || ' increment by 1';
EXECUTE IMMEDIATE (v_sql);
-- select from it again to prove it reset correctly.
v_sql := 'SELECT ' || v_seq_name || '.currval FROM DUAL';
EXECUTE IMMEDIATE v_sql
INTO v_reset_val;
END IF;
DBMS_OUTPUT.PUT_LINE(v_seq_name || ': ' || v_old_val || ' to ' ||
v_new_value);
END;
/*********************************************************************************************
Retrieves a max value for a table and then calls RESET_SEQ.
*********************************************************************************************/
PROCEDURE reset_seq_to_table(in_sequence_name VARCHAR2,
in_table_name VARCHAR2,
in_column_name VARCHAR2) IS
v_sql_body VARCHAR2(2000);
v_max_value NUMBER;
BEGIN
-- get max value in the table
v_sql_body := 'SELECT MAX(' || in_column_name || '+0) FROM ' ||
in_table_name;
EXECUTE IMMEDIATE (v_sql_body)
INTO v_max_value;
if v_max_value is null then
-- handle empty tables
v_max_value := 0;
end if;
-- use max value to reset the sequence
RESET_SEQ(in_sequence_name, v_max_value);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Failed to reset ' || in_sequence_name ||
' from ' || in_table_name || '.' ||
in_column_name || ' - ' || sqlerrm);
END;
BEGIN
--DBMS_OUTPUT.ENABLE(1000000);
-- load sequence/table/column associations
/***** START SCHEMA CUSTOMIZATION *****/
map_seq_to_col('Your_SEQ',
'your_table',
'the_invoice_number_column');
/***** END SCHEMA CUSTOMIZATION *****/
-- iterate all sequences that require a reset
FOR v_index IN v_data_table.FIRST .. v_data_table.LAST LOOP
BEGIN
RESET_SEQ_TO_TABLE(v_data_table(v_index).sequence_name,
v_data_table(v_index).table_name,
v_data_table(v_index).column_name);
END;
END LOOP;
END;
/
-- -------------------------------------------------------------------------------------
-- End of Script.
-- -------------------------------------------------------------------------------------
The example is an anonymous sproc - change it to be proper procedures in a package, and call it prior to inserting a new invoice to keep the numbering consistent.
I think you'll find that using the MAX() of the existing numbers is prone to a new and exciting problem - duplicates can occur if multiple invoices are being created at the same time. (Don't ask me how I know...).
A possible solution is to derive the primary key on your INVOICE table from a sequence, but have this NOT be the invoice number. After correctly and properly creating your invoice, and after the point at which an exception or user's whim could cause the creation of the invoice to be terminated, you go to a second sequence to get the sequential number which is presented as "the" invoice number. This means you'll have two unique, non-repeating numbers on your INVOICE table, and the obvious one (INVOICE_NO) will not be the primary key (but it can and should be UNIQUE) so there's a bit of evil creeping in, but the alternative - which is to create the INVOICE row with one value in the primary key, then change the primary key after the INVOICE is created - is just too evil for words. :-)
Share and enjoy.
If you really want to have no gaps, you need to completely serialize access, otherwise there will always be gaps. The reasons for gaps are:
rollback
shutdown abort
It's not clear what you mean by 'because of exceptions that may occur'. If you want number NOT to be incremented if your transaction eventually rolls back then SEQUENCE is not going to work for you, because as far as I know, once NEXTVAL is requested from sequence the sequence position is incremented and rollback won't reverse it.
If this is indeed a requirements then you probably would have to resort of storing current counter in a separate table, but beware of concurrent updates - from both 'lost update' and scalability prospective.
I've come across this problem before. In one case, we were able to convince the business to accept that "real" invoices might have gaps, and we wrote a job that ran every day to "fill in" the gaps with "void" invoices for audit purposes.
In practice, if we put NOCACHE on the sequence, the number of gaps would be relatively low, so the auditors will usually be happy as long as their query on the "void" invoices don't return too many results.
You might have to re-think your process slighty and break it into more steps. Have one non-transactional step create the placeholder invoice (this not being in the transaction should eliminate gaps) and then within the transaction do the rest of your business. I think that was how we did it in a system I was stuck with years ago but I can't remember - I just remember it was "weird."
I'd say the sequence will guarantee unique/consecutive numbers but when you throw transactions in the mix that can't be guaranteed unless the sequence generation isn't within that transaction.
dpbradley's link in #2 sounds like your best bet. Tom keeps the transactionality with the caller, if you don't want that you could make it an autonomous transaction like so:
create or replace
function getNextInvoiceNumber()
return number is
l_invoicenum number;
pragma autonomous_transaction;
begin
update docs_numbers
set last_invoice = last_invoice + 1
returning last_invoice
into l_invoicenum;
commit;
return l_invoicenum;
exception
when others then
rollback;
raise;
end;
What we do is issue a sequence number to the transaction and then when the item we are processing is finalized we issue a permanent number (also a sequence).
Works well for us.
Regards
K