Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 8 years ago.
Improve this question
create or replace trigger TDB_TRIGGER1
before insert on KTOVOT
for each row
declare
begin
insert into TEMPORARY_DATA(MIS_ZEHUT,TA_RISHUM, SHEM_TAVLA)
values(:new.id, sysdate, user_tables.table_name);
end TDB_TRIGGER1
I get this error:
PL/SQL: ORA-00984: column not allowed here
What I want to do here is to write into table:
TEMPORARY_DATA mis_zehut, ta_rishum, user_tables.table_name
I want to write the table name ktovot to the third field of table TEMPORARY_DATA with no success.
If I do it hardcoded it will work:
values(:new.id, sysdate, 'ktovot' );
PL/SQL does not support reflection - or rather it supports an extremely limited level of reflection. Nothing like Java. But we can find out the name of the current program unit using the $$PLSQL_UNIT inquiry directive. Find out more.
Triggers are program units, so we can find out the name of the currently executing trigger. And with that piece of information we can look up the name of the table which owns the trigger:
create or replace function get_table_name
( i_trigger_name in varchar2)
return varchar2
is
return_value varchar2(30);
begin
select table_name
into return_value
from user_triggers
where trigger_name = i_trigger_name;
return return_value;
end;
/
So, here is a journal table:
create table jrnl1 (id number
, action varchar2(20)
, ts timestamp
, table_name varchar2(30));
Here is a trigger to populate that journal, getting the table name dynamically:
create or replace trigger t23_trg
before insert on t23 for each row
begin
insert into jrnl1 values
(:new.id
, 'INSERT'
, systimestamp
, get_table_name($$PLSQL_UNIT));
end;
/
And here is the proof of the pudding:
SQL> select * from jrnl1;
no rows selected
SQL> insert into t23 values ('TEST', 42);
1 row created.
SQL> select * from jrnl1;
ID ACTION TS TABLE_NAME
---------- -------- ---------------------------- ------------------------------
42 INSERT 17-AUG-14 10.14.30.688672 AM T23
SQL>
I'm afraid it's not the elegant solution you might have hoped for. And in fact, given that you have to write a separate trigger for each table I'm not sure it saves you much in the way of hard-coding.
But I think it makes an interesting toy which serves to illustrate how PL/SQL works.
Related
I have created a table with datatype smallint.
create table test(
A smallint,
constraints ACHECK check(A between 1 and 5));
I want to add constraint that only allow users to add integer value range between 1~5.
But, even with the constraints, I am still able to insert floating point value which gets round up automatically.
insert into test values(3.2);
How do I make this code to show an error?
I am not allow to change datatype.
You cannot do what you want easily. Oracle is converting the input value 3.2 to an integer. The integer meets the constraint. The value 3 is what gets inserted. The conversion happens behind the scenes. The developers of Oracle figured this conversion is a "good thing".
You could get around this by declaring the column as a number and then checking that it is an integer:
create table test (
A number,
constraints ACHECK check(A between 1 and 5 and mod(A, 1) = 0)
);
As far as the requirement is NOT TO CHANGE THE DATA TYPE, but it doesn't say anything regarding creating new objects, I came up with a very complicated solution which does the trick but I would have preferred by far to change the data type to number and use a normal constraint.
The main problem here is that the round up of the value is done after parsing the statement, but before executing. As is an internal mechanism , you can't do anything about it. You can easily see that happening if you use a trigger and display the value of :NEW before inserting or updating the column.
However, there is a trick. F.G.A. got the original value passed to the statement before parsing. So, using a policy with a handler and two triggers make the trick.
Let me go into details
SQL> create table testx ( xsmall smallint );
Table created.
SQL> create table tracex ( id_timestamp timestamp , who_was varchar2(50) , sqltext varchar2(4000) );
Table created.
SQL> create or replace procedure pr_handle_it (object_schema VARCHAR2, object_name VARCHAR2, policy_name VARCHAR2)
is
begin
-- dbms_output.put_line('SQL was: ' || SYS_CONTEXT('userenv','CURRENT_SQL'));
insert into tracex values ( systimestamp , sys_context('userenv','session_user') , sys_context('userenv','current_sql') );
commit;
end;
/
Procedure created.
SQL> BEGIN
DBMS_FGA.ADD_POLICY(
object_schema => 'MYSCHEMA',
object_name => 'TESTX',
policy_name => 'MY_NEW_POLICY',
audit_condition => null,
audit_column => 'XSMALL',
handler_schema => 'MYSCHEMA',
handler_module => 'PR_HANDLE_IT',
enable => true,
statement_types => 'INSERT, UPDATE, DELETE'
);
END;
/
PL/SQL procedure successfully completed.
SQL> create or replace trigger trg_testx before insert or update on testx
referencing new as new old as old
for each row
begin
if inserting or updating
then
dbms_output.put(' New value is: ' || :new.xsmall);
dbms_output.put_line('TRIGGER : The value for CURRENT_SQL is '||sys_context('userenv','current_sql'));
insert into tracex values ( systimestamp , sys_context('userenv','session_user') , sys_context('userenv','current_sql') );
end if;
end;
/
Trigger created.
SQL> create or replace trigger trg_testx2 after insert or update on cpl_rep.testx
referencing new as new old as old
for each row
declare
v_val pls_integer;
begin
if inserting or updating
then
select regexp_replace(sqltext,'[^0-9]+','') into v_val
from ( select upper(sqltext) as sqltext from tracex order by id_timestamp desc ) where rownum = 1 ;
if v_val > 5
then
raise_application_error(-20001,'Number greater than 5 or contains decimals');
end if;
end if;
end ;
/
Trigger created.
These are the elements:
-One trace table to get the query before parsing
-One FGA policy over update and insert
-One procedure for the handler
-Two triggers one before ( got the query and the original value ) and one after to evaluate the value from the statement not the one rounded up.
Due to the fact that the triggers are evaluating in order, the before one inserts the original value with the decimal and it does it before the round up, the after analyses the value stored in the trace table to raise the exception.
SQL> insert into testx values ( 1 ) ;
1 row created.
SQL> insert into testx values ( 5 ) ;
1 row created.
SQL> insert into testx values ( 2.1 ) ;
insert into testx values ( 2.1 )
*
ERROR at line 1:
ORA-20001: Number greater than 5 or it contains decimals
ORA-06512: at "CPL_REP.TRG_TESTX2", line 10
ORA-04088: error during execution of trigger 'CPL_REP.TRG_TESTX2'
SQL> insert into testx values ( 6 ) ;
insert into testx values ( 6 )
*
ERROR at line 1:
ORA-20001: Number greater than 5 or it contains decimals
ORA-06512: at "CPL_REP.TRG_TESTX2", line 10
ORA-04088: error during execution of trigger 'CPL_REP.TRG_TESTX2'
Summary: As #Gordon Linoff said there was no easy way to achieve what was asked. I believe the method is very complicated for the requirement. I just came up with it for the purpose to show that it was possible after all.
I am trying to use v('APP_USER') as default value for a column. I get null when I use it in select like
select v('APP_USER') from dual;
But when I use it as default in column, like below, I am getting error.
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')));
Error
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')))
Error report -
ORA-04044: procedure, function, package, or type is not allowed here
04044. 00000 - "procedure, function, package, or type is not allowed here"
*Cause: A procedure, function, or package was specified in an
inappropriate place in a statement.
*Action: Make sure the name is correct or remove it.
Can anyone explain this or have a turnaround for this ??
There are other options than V('APP_USER'). Since Apex 5, the APP_USER is stored in the sys_context and that is a lot more performant than the V() function. It is available as SYS_CONTEXT('APEX$SESSION','APP_USER').
It also works as a default value for tables:
create table test_table
(col_1 VARCHAR2(100) DEFAULT SYS_CONTEXT('APEX$SESSION','APP_USER'));
Table TEST_TABLE created.
That being said, the best practice for audit columns is a trigger that populates the the 4 audit columns (as #Littlefoot suggested). Have a look at quicksql (under SQL Workshop > Utilities or on livesql.oracle.com). You can have it generate the triggers for you if you set "include Audit columns" and "Apex Enabled". An example of such a generated trigger is:
create or replace trigger employees_biu
before insert or update
on employees
for each row
begin
if inserting then
:new.created := sysdate;
:new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end employees_biu;
/
One option is to use a database trigger, e.g.
CREATE OR REPLACE TRIGGER trg_biu_test
BEFORE INSERT OR UPDATE ON test
FOR EACH ROW
BEGIN
:new.col1 := v ('APP_USER');
END;
/
I'm trying to create a trigger for automatic generation of primary key values using sequences on Oracle SQL Developer.
Since I'm new to this, it sounds kind of vague to me so I tried various things I found online but failed to create what I am supposed to do. I tried this piece of code but I am completely sure that it is wrong.
CREATE OR REPLACE TRIGGER TRIGGER1
BEFORE INSERT ON Orders
FOR EACH ROW
WHEN (new.ID IS NULL)
BEGIN
:new.ID := Orders_SEQ.NEXTVAL;
END;
Can someone guide me to what am I supposed to do for this question?
I tried this piece of code but I am completely sure that it is wrong.
Why? It is completely correct.
SQL> create table orders (id number);
Table created.
SQL> create sequence orders_seq;
Sequence created.
SQL> CREATE OR REPLACE TRIGGER TRIGGER1
2 BEFORE INSERT ON Orders
3 FOR EACH ROW
4 WHEN (new.ID IS NULL)
5 BEGIN
6 :new.ID := Orders_SEQ.NEXTVAL;
7 END;
8 /
Trigger created.
SQL> insert into orders (id) values (null);
1 row created.
SQL> select * from orders;
ID
----------
1
SQL>
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.
This is what I currently have:
CREATE OR REPLACE TRIGGER MYTRIGGER
AFTER INSERT ON SOMETABLE
FOR EACH ROW
DECLARE
v_emplid varchar2(10);
BEGIN
SELECT
personnum into v_emplid
FROM PERSON
WHERE PERSONID = :new.EMPLOYEEID;
dbms_output.put(v_emplid);
/* INSERT INTO SOMEOTHERTABLE USING v_emplid and some of the other values from the trigger table*/
END MYTRIGGER;
DBA_ERRORS has this error:
PL/SQL: ORA-00923: FROM keyword not found where expected
1) There must be something else to your example because that sure seems to work for me
SQL> create table someTable( employeeid number );
Table created.
SQL> create table person( personid number, personnum varchar2(10) );
Table created.
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE TRIGGER MYTRIGGER
2 AFTER INSERT ON SOMETABLE
3 FOR EACH ROW
4 DECLARE
5 v_emplid varchar2(10);
6 BEGIN
7 SELECT personnum
8 into v_emplid
9 FROM PERSON
10 WHERE PERSONID = :new.EMPLOYEEID;
11 dbms_output.put(v_emplid);
12 /* INSERT INTO SOMEOTHERTABLE USING v_emplid and some of the other values
from the trigger table*/
13* END MYTRIGGER;
14 /
Trigger created.
SQL> insert into person values( 1, '123' );
1 row created.
SQL> insert into sometable values( 1 );
1 row created.
2) You probably want to declare V_EMPLID as being of type Person.PersonNum%TYPE so that you can be certain that the data type is correct and so that if the data type of the table changes you won't need to change your code.
3) I assume that you know that your trigger cannot query or update the table on which the trigger is defined (so no queries or inserts into someTable).
You are playing with Lava (not just fire) in your trigger. DBMS_OUTPUT in a trigger is really, really bad. You can blow-out on a buffer overflow in your trigger and the whole transaction is shot. Good luck tracking that down. If you must do output-to-console like behavior, invoke an AUTONOMOUS TRANSACTION procedure that writes to a table.
Triggers are pretty evil. I used to like them, but they are too hard to remember about. They affect data often times leading to MUTATING data (scary and not just because Halloween is close).
We use triggers to change the value of columns like .new:LAST_MODIFIED := sysdate and .new:LAST_MODIFIED_BY := user. That's it.
Don't ever allow a TRIGGER to prevent a transaction from completing. Find another option.
I would not use a select statment in a trigger ever. Insert into the table rather than a select into. Once the table already exists select into does not work in most databases.