Behaviour of insertion trigger when defining autoincrement in Oracle - sql

I have been looking for a way to define an autoincrement data type in Oracle and have found these questions on Stack Overflow:
Autoincrement in Oracle
Autoincrement Primary key in Oracle database
The way to use autoincrement types consists in defining a sequence and a trigger to make insertion transparent, where the insertion trigger looks so:
create trigger mytable_trg
before insert on mytable
for each row
when (new.id is null)
begin
select myseq.nextval into :new.id from dual;
end;
I have some doubts about the behaviour of this trigger:
What does this trigger do when the supplied value of "id" is different from NULL?
What does the colon before "new" mean?
I want the trigger to insert the new row with the next value of the sequence as ID whatever the supplied value of "new.id" is. I imagine that the WHEN statement makes the trigger to only insert the new row if the supplied ID is NULL (and it will not insert, or will fail, otherwise).
Could I just remove the WHEN statement in order for the trigger to always insert using the next value of the sequence?

The WHEN condition specifies a condition that must be TRUE for the trigger to fire. In this exampple, the trigger will only fire if the new row has a NULL IS. When the ID is not null, the trigger will not fire and so whatever value ID has been given in the insert statement will be left alone.
Yes, if you simply remove the WHEN condition then the trigger will always fire and so will always provide a sequence value for ID.

Nothing. That allows to specify a value manually.
It's a placeholder for the new value of the column.

You have 2 methods you can do:
if the table looks like this:
create table my_test (
id number,
my_test data varchar2(255)
);
and your sequence is this:
create sequence test_seq
start with 1
increment by 1
nomaxvalue;
you can create a trigger like this (with no When statement like Tony Andrews said)
create trigger test_trigger
before insert on my_test
for each row
begin
select test_seq.nextval into :new.id from dual;
end;
or you could just simply use this then you don't need a trigger:
insert into my_test values(test_seq.nextval, 'voila!');

Related

how to make sure that trigger generated value is being returned?

I have this INSERT query, which purpose is to insert the one row in my database.
Similarly I also have a INSERT query which insert multiple rows.
One of the columns in the table is generated after the values has been generated, since it combines a set of column values to construct a name. The name itself it generated from a Trigger, and its triggered After insert, since the column values has to exist for me to generate the name.
my problem now is when I insert one row or multiple rows, I want to know the the generated column value, but when I return it, it states its null?
#$"INSERT INTO registration_table (id, ...,)
VALUES (1,...,)
RETURNING row_id, name;";
which in return gives me an id the one I inserted, but the not actual name but instead I get null..
The trigger is pretty straight forward
CREATE TRIGGER name_insert_trigger
AFTER INSERT
ON registration_table
REFERENCING NEW TABLE AS new_inserts
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE registration_entry_name();
CREATE OR REPLACE FUNCTION registration_entry_name()
RETURNS trigger AS
$$
DECLARE
BEGIN
UPDATE registration_table
SET name = |Pattern| -- This one being the actual name generated..
FROM new_inserts
WHERE new_inserts.row_id = registration_table.row_id;
RETURN null;
END;
$$
LANGUAGE plpgsql;
but the insert query above does not return the name?
why not?
You actually need a BEFORE trigger, your data values will be there. The designation of Before and After very often causes misconceptions especially of row level triggers. The terms do not indicate their timing in relation to the DML. I have found it useful to think of them as "before final data values are set" and "after final data values are set" but both run before the invoking DML completes (for now we will bypass deferred triggers). Lets look at inserts. When the before row trigger fires the NEW row contains the values at that point for every column in the row, any value not specified in the statement will be null or contain the specified default if any. Before row triggers can can change any column. After row triggers cannot change columns, if present any change is ignored.
Your description and code imply you need to combine a couple columns to generate the content of another. Since you did not specify exactly that I will build an example and demo.
create table users ( usr_id integer generated always as identity
, lname text not null
, fname text not null
, full_name text not null
) ;
create or replace
function users_bir()
returns trigger
language plpgsql
as $$
begin
if new.full_name is null
then
new.full_name = trim(new.fname) || ' ' || trim(new.lname);
end if;
return new;
end;
$$;
create trigger users_bir_trg
before insert on users
for each row
execute procedure users_bir();
insert into users(fname, lname)
values ( 'George', 'Henery')
, ( 'Samatha', 'van Horm');
insert into users(fname, lname, full_name)
values ( 'Wacky', 'Warriors','Not so tough guys');
This setup allows the full_name to be specified or generated. If only generation is desired remove the IF leaving only the assignment statement. Even better if you have Postgres 12 or higher just define the the column as a generated column. This is also in the demo.

PLS-00049: bad bind variable 'NEW.REQUEST_DATETIME' Issue

I am trying to write a Trigger which basically updates one table when an entry is made on another table.
CREATE OR REPLACE TRIGGER "DTISCDB_OWNER"."REQUEST_CONTEXT_TR"
AFTER INSERT OR UPDATE ON REQUEST_CONTEXT
FOR EACH ROW
BEGIN
:NEW.REQUEST_DATETIME := SYSDATE;
:NEW.ID := TRUNC(DBMS_RANDOM.VALUE(100000000000000000000000000000000000,999999999999999999999999999999999999));
SELECT bre_conditions_seq.NEXTVAL INTO :OLD.seq_number FROM dual;
SELECT REQUEST_CONTEXT.CURRENT_STATE INTO :NEW.STATE FROM REQUEST_CONTEXT;
SELECT REQUEST_CONTEXT.REQUEST_ID INTO :NEW.REQUEST_ID FROM REQUEST_CONTEXT;
INSERT INTO REQUEST_LIFECYCLES(ID,SEQ_NUMBER,STATE,REQUEST_ID,REQUEST_DATETIME)
VALUES(:NEW.ID,:NEW.seq_number,:NEW.STATE,:NEW.REQUEST_ID,:NEW.REQUEST_DATETIME);
END;
You seem to be confused about what the correlation pseudorows are for and what they hold and can do. It looks like you're treating :NEW as if it is related to the REQUEST_LIFECYCLES table you are inserting into inside the trigger, and :OLD as if it is related to the REQUEST_CONTEXT row that has been inserted or updated and caused the trigger to fire.
Both OLD and NEW refer to the table the trigger is against, REQUEST_CONTEXT. If the trigger was fired by an update then OLD has the pre-update values for the affected row; if it was fired by an insert then it is empty as there is no old state. Either way NEW has the current state, with the newly-inserted or post-update values. You can't change the OLD values, and it doesn't make sense to change the NEW values in an 'after' trigger. You also don't need to query the table the trigger is fired against, as the NEW pseudorow already makes that information available.
So if you are trying to use the inserted/updated values in REQUEST_CONTEXT to create a row in REQUEST_LIFECYCLES, you would do something like:
CREATE OR REPLACE TRIGGER "DTISCDB_OWNER"."REQUEST_CONTEXT_TR"
AFTER INSERT OR UPDATE ON REQUEST_CONTEXT
FOR EACH ROW
BEGIN
INSERT INTO REQUEST_LIFECYCLES(ID, SEQ_NUMBER, STATE, REQUEST_ID,
REQUEST_DATETIME)
VALUES(TRUNC(DBMS_RANDOM.VALUE(100000000000000000000000000000000000, 999999999999999999999999999999999999)),
bre_conditions_seq.NEXTVAL, :NEW.CURRENT_STATE, :NEW.REQUEST_ID,SYSDATE);
END;
/
I'm assuming you wanted to set the 'lifecycle' SEQ_NUMBER value from the trigger, despite you trying to set the :OLD value - hopefully the old reference was a mistake. If you were trying to set that value in both REQUEST_CONTEXT and REQUEST_LIFECYCLES you would need a before insert/update trigger instead, and would set :NEW.SEQ_NUMBER rather than the :OLD value, before using it in the values clause.
As Justin said using a random value for the ID is rather strange, not least because it won't be unique, and a sequence is much more common. You may actually want the ID from the inserted/updated row, in which case you can just refer to :NEW.ID in the values clause instead of generating a new value. (It's also possible you are trying to set that ID in both REQUEST_CONTEXT and REQUEST_LIFECYCLES too, but that would be even stranger, and you'd need a before-insert/update trigger to do that anyway).

Trigger to update a different table

Using Postgres 9.4, I have 2 tables streams and comment_replies. I am trying to do is update the streams.comments count each time a new comment_replies is inserted to keep track of the number of comments a particular stream has. I am not getting any errors but when I try to create a new comment it gets ignored.
This is how I am setting up my trigger. stream_id is a foreign key, so every stream_id will correspond to a streams.id which is the primary key of the streams table. I have been looking at this example: Postgres trigger function, but haven't been able to get it to work.
CREATE TABLE comment_replies (
id serial NOT NULL PRIMARY KEY,
created_on timestamp without time zone,
comments text,
profile_id integer,
stream_id integer
);
The trigger function:
CREATE OR REPLACE FUNCTION "Comment_Updates"()
RETURNS trigger AS
$BODY$BEGIN
update streams set streams.comments=streams.comments+1
where streams.id=comment_replies_streamid;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
And the trigger:
CREATE TRIGGER comment_add
BEFORE INSERT OR UPDATE
ON comment_replies
FOR EACH ROW
EXECUTE PROCEDURE "Comment_Updates"();
How can I do this?
There are multiple errors. Try instead:
CREATE OR REPLACE FUNCTION comment_update()
RETURNS trigger AS
$func$
BEGIN
UPDATE streams s
SET streams.comments = s.comments + 1
-- SET comments = COALESCE(s.comments, 0) + 1 -- if the column can be NULL
WHERE s.id = NEW.streamid;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER comment_add
BEFORE INSERT OR UPDATE ON comment_replies -- on UPDATE, too? Really?
FOR EACH ROW EXECUTE PROCEDURE comment_update();
You need to consider DELETE as well if that is possible. Also if UPDATE can change stream_id. But why increase the count for every UPDATE? This looks like another error to me.
It's a syntax error to table-qualify the target column in the SET clause of UPDATE.
You need to return NEW in a BEFORE trigger unless you want to cancel the INSERT / UPDATE.
Or you make it an AFTER trigger, which would work for this, too.
You need to reference NEW for the stream_id of the current row (which is automatically visible inside the trigger function.
If streams.comments can be NULL, use COALESCE.
And rather use unquoted, legal, lower-case identifiers.

PLS-00103: Encountered the symbol "¿"

I am trying to make sure that my primary key auto increments. The code below is what I have tried so far.
create or replace trigger field_null
before insert on table
for each row
begin
if :new.number_id is null then
select number_id_SEQ.nextval into :new.number_id from table;
end if;
end;​
Instead of table in select query,try to use dual. Try this General Trigger syntax to create trigger for Auto-increment column
CREATE OR REPLACE TRIGGER %triggername%
BEFORE INSERT ON %tablename% FOR EACH ROW
BEGIN
SELECT %seqname%.NEXTVAL
INTO :NEW.%columnname%
FROM DUAL;
END;
%seqname% will be replaced with the name of the sequence.
%triggername% will be replaced with the name of the trigger.
%columnname% will be replaced with the name of the associated column.
And to Create Sequence, You can use the following syntax:-
CREATE SEQUENCE %seqname%
START WITH 1
INCREMENT BY 1;
Refer Here

Oracle - Insert New Row with Auto Incremental ID

I have a workqueue table that has a workid column. The workID column has values that increment automatically. Is there a way I can run a query in the backend to insert a new row and have the workID column increment automatically?
When I try to insert a null, it throws error ORA01400 - Cannot insert null into workid.
insert into WORKQUEUE (facilitycode,workaction,description) values ('J', 'II', 'TESTVALUES')
What I have tried so far - I tried to look at the table details and didn't see any auto-increment. The table script is as follow
"WORKID" NUMBER NOT NULL ENABLE,
Database: Oracle 10g
Screenshot of some existing data.
ANSWER:
I have to thank each and everyone for the help. Today was a great learning experience and without your support, I couldn't have done. Bottom line is, I was trying to insert a row into a table that already has sequences and triggers. All I had to do was find the right sequence, for my question, and call that sequence into my query.
The links you all provided me helped me look these sequences up and find the one that is for this workid column. Thanks to you all, I gave everyone a thumbs up, I am able to tackle another dragon today and help patient care take a step forward!"
This is a simple way to do it without any triggers or sequences:
insert into WORKQUEUE (ID, facilitycode, workaction, description)
values ((select max(ID)+1 from WORKQUEUE), 'J', 'II', 'TESTVALUES')
It worked for me but would not work with an empty table, I guess.
To get an auto increment number you need to use a sequence in Oracle.
(See here and here).
CREATE SEQUENCE my_seq;
SELECT my_seq.NEXTVAL FROM DUAL; -- to get the next value
-- use in a trigger for your table demo
CREATE OR REPLACE TRIGGER demo_increment
BEFORE INSERT ON demo
FOR EACH ROW
BEGIN
SELECT my_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
There is no built-in auto_increment in Oracle.
You need to use sequences and triggers.
Read here how to do it right. (Step-by-step how-to for "Creating auto-increment columns in Oracle")
ELXAN#DB1> create table cedvel(id integer,ad varchar2(15));
Table created.
ELXAN#DB1> alter table cedvel add constraint pk_ad primary key(id);
Table altered.
ELXAN#DB1> create sequence test_seq start with 1 increment by 1;
Sequence created.
ELXAN#DB1> create or replace trigger ad_insert
before insert on cedvel
REFERENCING NEW AS NEW OLD AS OLD
for each row
begin
select test_seq.nextval into :new.id from dual;
end;
/ 2 3 4 5 6 7 8
Trigger created.
ELXAN#DB1> insert into cedvel (ad) values ('nese');
1 row created.
You can use either SEQUENCE or TRIGGER to increment automatically the value of a given column in your database table however the use of TRIGGERS would be more appropriate. See the following documentation of Oracle that contains major clauses used with triggers with suitable examples.
Use the CREATE TRIGGER statement to create and enable a database trigger, which is:
A stored PL/SQL block associated with a table, a schema, or the
database or
An anonymous PL/SQL block or a call to a procedure implemented in
PL/SQL or Java
Oracle Database automatically executes a trigger when specified conditions occur. See.
Following is a simple TRIGGER just as an example for you that inserts the primary key value in a specified table based on the maximum value of that column. You can modify the schema name, table name etc and use it. Just give it a try.
/*Create a database trigger that generates automatically primary key values on the CITY table using the max function.*/
CREATE OR REPLACE TRIGGER PROJECT.PK_MAX_TRIGGER_CITY
BEFORE INSERT ON PROJECT.CITY
FOR EACH ROW
DECLARE
CNT NUMBER;
PKV CITY.CITY_ID%TYPE;
NO NUMBER;
BEGIN
SELECT COUNT(*)INTO CNT FROM CITY;
IF CNT=0 THEN
PKV:='CT0001';
ELSE
SELECT 'CT'||LPAD(MAX(TO_NUMBER(SUBSTR(CITY_ID,3,LENGTH(CITY_ID)))+1),4,'0') INTO PKV
FROM CITY;
END IF;
:NEW.CITY_ID:=PKV;
END;
Would automatically generates values such as CT0001, CT0002, CT0002 and so on and inserts into the given column of the specified table.
SQL trigger for automatic date generation in oracle table:
CREATE OR REPLACE TRIGGER name_of_trigger
BEFORE INSERT
ON table_name
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT sysdate INTO :NEW.column_name FROM dual;
END;
/
the complete know how, i have included a example of the triggers and sequence
create table temasforo(
idtemasforo NUMBER(5) PRIMARY KEY,
autor VARCHAR2(50) NOT NULL,
fecha DATE DEFAULT (sysdate),
asunto LONG );
create sequence temasforo_seq
start with 1
increment by 1
nomaxvalue;
create or replace
trigger temasforo_trigger
before insert on temasforo
referencing OLD as old NEW as new
for each row
begin
:new.idtemasforo:=temasforo_seq.nextval;
end;
reference:
http://thenullpointerexceptionx.blogspot.mx/2013/06/llaves-primarias-auto-incrementales-en.html
For completeness, I'll mention that Oracle 12c does support this feature. Also it's supposedly faster than the triggers approach. For example:
CREATE TABLE foo
(
id NUMBER GENERATED BY DEFAULT AS IDENTITY (
START WITH 1 NOCACHE ORDER ) NOT NULL ,
name VARCHAR2 (50)
)
LOGGING ;
ALTER TABLE foo ADD CONSTRAINT foo_PK PRIMARY KEY ( id ) ;
Best approach: Get the next value from sequence
The nicest approach is getting the NEXTVAL from the SEQUENCE "associated" with the table. Since the sequence is not directly associated to any specific table,
we will need to manually refer the corresponding table from the sequence name convention.
The sequence name used on a table, if follow the sequence naming convention, will mention the table name inside its name. Something likes <table_name>_SEQ. You will immediately recognize it the moment you see it.
First, check within Oracle system if there is any sequence "associated" to the table
SELECT * FROM all_sequences
WHERE SEQUENCE_OWNER = '<schema_name>';
will present something like this
Grab that SEQUENCE_NAME and evaluate the NEXTVAL of it in your INSERT query
INSERT INTO workqueue(id, value) VALUES (workqueue_seq.NEXTVAL, 'A new value...')
Additional tip
In case you're unsure if this sequence is actually associated with the table, just quickly compare the LAST_NUMBER of the sequence (meaning the current value) with the maximum id of
that table. It's expected that the LAST_NUMBER is greater than or equals to the current maximum id value in the table, as long as the gap is not too suspiciously large.
SELECT LAST_NUMBER
FROM all_sequences
WHERE SEQUENCE_OWNER = '<schema_name>' AND SEQUENCE_NAME = 'workqueue_seq';
SELECT MAX(ID)
FROM workqueue;
Reference: Oracle CURRVAL and NEXTVAL
Alternative approach: Get the current max id from the table
The alternative approach is getting the max value from the table, please refer to Zsolt Sky answer in this same question
This is a simple way to do it without any triggers or sequences:
insert into WORKQUEUE (ID, facilitycode, workaction, description)
values ((select count(1)+1 from WORKQUEUE), 'J', 'II', 'TESTVALUES');
Note : here need to use count(1) in place of max(id) column
It perfectly works for an empty table also.