This question already has answers here:
Auto-increment in Oracle without using a trigger
(9 answers)
Closed 8 years ago.
I want the first column of a table in PLSQL to be auto-increment.That column will be the primary key for that table. I heard something called serialize but i didn't get proper description about that. i was working in SQL Server. I am new to Oracle(PLSQL). Please help me to find out a proper solution.
Create a sequence
CREATE SEQUENCE name_of_sequence
START WITH 1
INCREMENT BY 1
CACHE 100;
Create a trigger
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
SELECT name_of_sequence.nextval
INTO :new.name_of_primary_key_column
FROM dual;
END;
The syntax of the trigger gets a little simpler in 11g since you can do a direct assignment to the :new.name_of_primary_key_column rather than selecting from dual. And I understand that there is some additional syntactic sugar in 12c that makes this even easier though I haven't played around with that.
Related
I am doing data insert into a table in Oracle which is having a sequence set to it in one of the columns say Id column. I would like to know how to do data loads into such tables.
I followed the below link -
It's possible to use OleDbConnections with the Script Component?
and tried to create a function to get the .nextval from the Oracle table but I am getting the following error -
Error while trying to retrieve text for error ORA-01019
I realized that manually setting the value via the package i.e. by using the Script task to enumerate the values but is not incrementing the sequence and that is causing the problem. How do we deal with it? Any links that can help me solve it?
I am using SSIS-2014 but I am not able to tag it as I don't due to paucity of reputation points.
I created a workaround to cater to this problem. I have created staging tables of the destination without the column that takes the Sequence Id. After the data gets inserted, I am then calling SQL statement to get the data into the main tables from staging table and using the .nextval function. Finally truncating/dropping the table depending on the need. It would still be interesting to know how this same thing can be handled via script rather having this workaround.
For instance something like below -
insert into table_main
select table_main_sequence_name.nextval
,*
from (
select *
from table_stg
)
ORA-01019 may be related to fact you have multiple Oracle clients installed. Please check ORACLE_HOME variable if it contains only one client.
One workaround I'm thinking about is creating two procedures for handling sequence. One to get value you start with:
create or replace function get_first from seq as return number
seqid number;
begin
select seq_name.nexval into seqid from dual;
return seqid;
end;
/
Then do your incrementation in script. And after that call second procedure to increment sequence:
create or replace procedure setseq(val number) as
begin
execute immediate 'ALTER SEQUENCE seq_name INCREMENT BY ' || val;
end;
/
This is not good approach but maybe it will solve your problem
Lets say I have a table as follows--
create table employees
(
eno number(4) not null primary key,
ename varchar2(30),
zip number(5) references zipcodes,
hdate date
);
I've created a trigger using the following code block
create or replace TRIGGER COPY_LAST_ONO
AFTER INSERT ON ORDERS
FOR EACH ROW
DECLARE
ID_FROM_ORDER_TABLE VARCHAR2(10);
BEGIN
SELECT MAX(ORDERS.ONO)INTO ID_FROM_ORDER_TABLE from ORDERS ;
DBMS_OUTPUT.PUT_LINE(ID_FROM_ORDER_TABLE);
INSERT INTO BACKUP_ONO VALUES( VALUE1, VALUE2,VALUE3, ID_FROM_ORDER_TABLE);
END;
The trigger fires after insertion and attempts to read from the table that fired it(logically duhh!) but oracle is giving me an error and asking me to modify the trigger so that it doesnt read the table. Error code-
Error report -
SQL Error: ORA-04091: table TEST1.ORDERS is mutating, trigger/function may not see it
ORA-06512: at "TEST1.COPY_LAST_ONO", line 8
ORA-04088: error during execution of trigger 'TEST1.LOG_INSERT'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
What I'm trying to achieve with this trigger is to copy the last INSERTED ONO (which is a primary key for the ORDER table) immediately to a different table after being INSERTED. What I don't get is, why oracle complaining? The trigger is attempting to read AFTER the insertion!
Ideas? Solution?
MANY THANKS
If you are trying to log the ONO you just inserted, use :new.ono and skip the select altogether:
INSERT INTO BACKUP_ONO VALUES( VALUE1, VALUE2,VALUE3, :new.ono);
I don't believe you can select from the table you are in the middle of inserting into as the commit has not been issued yet, hence the mutating table error.
P.S. Consider not abbreviating. Make it clear for the next developer and call it ORDER_NUMBER or at least a generally accepted abbreviation like ORDER_NBR, whatever your company's naming standards are. :-)
FYI - If you are updating, you can access :OLD.column as well, the value before the update (of course if the column is not a primary key column).
Amplifying #Gary_W's answer:
Oracle does not allow a row trigger (one with FOR EACH ROW in it) to access the table on which the trigger is defined in any way - you can't issue a SELECT, INSERT, UPDATE, or DELETE against that table from within the trigger or anything it calls (so, no, you can't dodge around this by calling a stored procedure which does the dirty work for you - but good thinking! :-). My understanding is that this is done to prevent what you might call a "trigger loop" - that is, the triggering condition is satisfied and the trigger's PL/SQL block is executed; that block then does something which causes the trigger to be fired again; the trigger's PL/SQL block is invoked; the trigger's code modifies another row; etc, ad infinitum. Generally, this should be taken as a warning that your logic is either really ugly, or you're implementing it in the wrong place. (See here for info on the evil of business logic in triggers). If you find that you really seriously need to do this (I've worked with Oracle and other databases for years - I've really had to do it once - and may Cthulhu have mercy upon my soul :-) you can use a compound trigger which allows you to work around these issues - but seriously, if you're in a hole like this your best option is to re-work the data so you don't have to do this.
Best of luck.
Modify your trigger to use PRAGMA AUTONOMOUS_TRANSACTION
create or replace TRIGGER COPY_LAST_ONO
AFTER INSERT ON ORDERS
FOR EACH ROW
DECLARE
ID_FROM_ORDER_TABLE VARCHAR2(10);
PRAGMA AUTONOMOUS_TRANSACTION; -- Modification
BEGIN
.
.
.
I get this error when ever I try to fire a trigger after insert on passengers table. this trigger is supposed to call a procedure that takes two parameters of the newly inserted values and based on that it updates another table which is the booking table. however, i am getting this error:
ORA-04091: table AIRLINESYSTEM.PASSENGER is mutating, trigger/function may not see it
ORA-06512: at "AIRLINESYSTEM.CALCULATE_FLIGHT_PRICE", line 11 ORA-06512: at
"AIRLINESYSTEM.CALCULATE_FLIGHT_PRICE", line 15 ORA-06512: at
"AIRLINESYSTEM.CALCULATE_FLIGHT_PRICE_T1", line 3 ORA-04088: error during execution of
trigger 'AIRLINESYSTEM.CALCULATE_FLIGHT_PRICE_T1' (Row 3)
I complied and tested the procedure in the SQL command line and it works fine. The problem seems to be with the trigger. This is the trigger code:
create or replace trigger "CALCULATE_FLIGHT_PRICE_T1"
AFTER
insert on "PASSENGER"
for each row
begin
CALCULATE_FLIGHT_PRICE(:NEW.BOOKING_ID);
end;
Why is the trigger isn't calling the procedure?
You are using database triggers in a way they are not supposed to be used. The database trigger tries to read the table it is currently modifying. If Oracle would allow you to do so, you'd be performing dirty reads.
Fortunately, Oracle warns you for your behaviour, and you can modify your design.
The best solution would be to create an API. A procedure, preferably in a package, that allows you to insert passengers in exactly the way you would like it. In pseudo-PL/SQL-code:
procedure insert_passenger
( p_passenger_nr in number
, p_passenger_name in varchar2
, ...
, p_booking_id in number
, p_dob in number
)
is
begin
insert into passenger (...)
values
( p_passenger_nr
, p_passenger_name
, ...
, p_booking_id
, p_dob
);
calculate_flight_price
( p_booking_id
, p_dob
);
end insert_passenger;
/
Instead of your insert statement, you would now call this procedure. And your mutating table problem will disappear.
If you insist on using a database trigger, then you would need to avoid the select statement in cursor c_passengers. This doesn't make any sense: you have just inserted a row into table passengers and know all the column values. Then you call calculate_flight_price to retrieve the column DOB, which you already know.
Just add a parameter P_DOB to your calculate_flight_price procedure and call it with :new.dob, like this:
create or replace trigger calculate_flight_price_t1
after insert on passenger
for each row
begin
calculate_flight_price
( :new.booking_id
, :new.dob
);
end;
Oh my goodness... You are trying a Dirty Read in the cursor. This is a bad design.
If you allow a dirty read, it return the wrong answer, but also it returns an answer that never existed in the table. In a multiuser database, a dirty read can be a dangerous feature.
The point here is that dirty read is not a feature; rather, it's a liability. In Oracle Database, it's just not needed. You get all of the advantages of a dirty read—no blocking—without any of the incorrect results.
Read more on "READ UNCOMMITTED isolation level" which allows dirty reads. It provides a standards-based definition that allows for nonblocking reads.
Other way round
You are misusing the trigger. I mean wrong trigger used.
you insert / update a row in table A and a trigger on table A (for each row) executes a query on table A (through a procedure)??!!!
Oracle throws an ORA-04091 which is an expected and normal behavior, Oracle wants to protect you from yourself since it guarantees that each statement is atomic (i.e will either fail or succeed completely) and also that each statement sees a consistent view of the data
You would expect the query (2) not to see the row inserted on (1). This would be in contradiction
Solution: -- use before instead of after
CREATE OR REPLACE TRIGGER SOMENAME
BEFORE INSERT OR UPDATE ON SOMETABLE
Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I have finished my first real PL/SQL stored proc, this stored proc works as expected. I am new to PL/SQL, could you please point anything wrong or bad coding ?
This code is assuming a naming convention, for example, 't_company' table will use 'companyId' as its primary key and its type is number.
Thank you very much.
create or replace
package body test_erp AS
procedure init_data is
begin
logMessage('procedure init_data');
SAVEPOINT do_insert;
insert into t_company(companyId, companyName) values(gen_key('t_company'), 'IBM');
COMMIT;
exception
WHEN OTHERS THEN
rollback to do_insert;
logMessage('roll back , due to '|| SQLERRM);
end init_data;
end test_erp;
It will call this function
create or replace
function gen_key(tblName varchar2)
return number is
l_key number := 1000;
l_tmpStr varchar(2000); -- not good, how to fix it ?
begin
l_tmpStr := substr(tblName, 3, length(tblName));
EXECUTE IMMEDIATE ' SELECT CASE WHEN MAX('||l_tmpStr||'Id) IS NULL THEN 1000 ELSE MAX('||l_tmpStr||'Id)+1 END FROM '|| tblName into l_key;
logmessage('gen primary key '|| tblName ||' '||l_key);
return l_key;
end;
Your key_gen procedure is rather problematic. Generating keys by doing a MAX(key)+1 is slow and will not work in a multiuser environment. Assuming you have two users, it is relatively easy for both users to see the same MAX(key) and try to insert rows with the same primary key.
Oracle provides sequences in order to efficiently generate primary keys in a multi-user environment. You would be much better served using sequences to generate your keys. Conventionally, you would create one sequence per table, i.e.
CREATE SEQUENCE company_seq;
Your INSERT statement would then be something like
insert into t_company(companyId, companyName) values(company_seq.nextval, 'IBM');
Or you could create a trigger on the table to automatically populate the primary key.
Additionally, while it is fine to catch exceptions in order to log them, you really want to re-raise that exception so that the caller is aware that the INSERT failed.
Using function in your case gen_key is very slow and it's incorrect database-written and also very inefficiently.
So my advice is to create SEQUENCE that is generally used for this.Then you should create TRIGGER for generating new PK for each INSERT or directly add it with NEXTVAL.
So, your SEQUENCE can looks like this:
CREATE SEQUENCE YOUR_COMP_SEQ
MINVALUE 1
MAXVALUE 999999
START WITH 1
INCREMENT BY 1
NOCACHE
;
Then i recommend to you use meant TRIGGER:
CREATE OR REPLACE TRIGGER AUTOSET_ID_COMP
BEFORE INSERT ON t_company
FOR EACH ROW
BEGIN
SELECT YOUR_COMP_SEQ.NEXTVAL INTO :NEW.companyId FROM DUAL;
END;
And finally just call query:
INSERT INTO t_company(companyName) VALUES('SomeValue');
If you don't want to create TRIGGER so you can do it directly like this:
INSERT INTO t_company(companyId, companyName)
VALUES(YOUR_COMP_SEQ.NEXTVAL, 'SomeValue');
Note: Of course, you can create for every TABLE its own SEQUENCE and then use TRIGGERS for each TABLE.
Note 2: Sequences are very good but there is some problem that for example you added to table 20 rows, so IDs are 1,2,3, ... etc. and for example you will delete 15. row and since this ID 15 you can't use, anymore.
Update:
Answer and Solution is updated after a little discussion with #Ben, thanks.
This question already has answers here:
Does Oracle have an equivalent of SQL Server's table variables?
(4 answers)
Closed 8 years ago.
I've recently started a new position as a developer and I'm having a bit of trouble with PL/SQL. I've used MS SQL for a number of years but I'm finding PL/SQL a bit trickier.
One of the things I used to do when writing functions and stored procedures in MS SQL was to put reoccuring result sets into a table variable so I wouldn't have to requery them throughout my procedure.
Something like this:
declare #badPeople table(recordPointer int)
insert into #badPeople
select BP_Record_Pointer
from People
where BP_Bad = 1
I'm looking at doing something similar in PL/SQL but I'm not quite sure how to get started. Any ideas if this is even possible?
declare
type t_number is table of number;
v_numbers t_number;
begin
select BP_Record_Pointer
bulk collect into v_numbers
from People
where BP_Bad = 1;
end;
This will create a nested table containing the values from your table. It's important to note that the table will go out-of-scope at the end of the anonymous block, so anything you want to do with it needs to be done inside that block.
In Oracle unlike MS SQL cursors are not considered the devil and something to avoid. So typically in Oracle you will make use of cursors much more often than you would in MS SQL. Of course if you can do it with only SQL and no cursor that is best, but don't be afraid to test and use cursors in Oracle. Get a good book on PL/SQL, this is a good one http://www.amazon.com/Oracle-PL-SQL-Programming-4th/dp/0596009771. The post by Gratzy is another option you can use. So get a good book that you can use to figure out when to use what. PL/SQL is a very rich environment compared to T-SQL. While it has more of a learning curve once you get over the initial curve it's a really easy and power full language. And on top of that it's a lot of fun.