I'm practising triggers on Oracle and I'm having troubles with this trigger:
CREATE OR REPLACE TRIGGER MODIFICACIONES_SALARIOS AFTER
UPDATE OF salario ON empleados_pac
FOR EACH ROW
DECLARE
v_username VARCHAR(10);
v_hora VARCHAR2(10);
BEGIN
SELECT user INTO v_username FROM dual;
SELECT to_char(sysdate, 'HH24:MI:ss') INTO v_hora FROM dual;
INSERT INTO audita_salarios (id_emp, salario_antiguo, salario_nuevo, fecha, hora, username) VALUES (id_empleado, :old.salario, :new.salario, sysdate, v_hora, v_username);
END;
I'm doing an historic table where when the salary of the employeer changes the trigger inserts in the historic his ID, the old salary, the new one, the date and the user who changed the salary.
All works perfectly except for the ID insert. Oracle says that the column is not allowed.
I think the problem is because I want to insert an unique ID into a new column where the ID is going to be repeated if the salary changes two or more times. How could I fix this?
I think the problem is because I want to insert an unique ID into a new column where the ID is going to be repeated if the salary changes two or more times. How could I fix this?
Do not have a UNIQUE constraint on audita_salarios.id_emp; instead have a composite UNIQUE constraint on id_emp and fecha (if you need a UNIQUE constraint at all).
Also:
use a virtual column for HORA (or remove that column as it is just duplicating the time component of FECHA);
use :NEW.id_empleado rather than id_empleado in the trigger.
CREATE TABLE empleados_pac (
id_empleado NUMBER(2),
salario NUMBER(10,2)
);
CREATE TABLE audita_salarios (
id_emp NUMBER(2),
salario_antiguo NUMBER(10,2),
salario_nuevo NUMBER(10,2),
fecha DATE,
hora VARCHAR2(8)
GENERATED ALWAYS AS ( CAST( TO_CHAR( fecha, 'HH24:MI:SS' ) AS VARCHAR2(8) ) ),
username VARCHAR2(60),
CONSTRAINT audita_salarios__pk PRIMARY KEY ( id_emp, fecha )
);
CREATE TRIGGER MODIFICACIONES_SALARIOS
AFTER UPDATE OF salario ON empleados_pac
FOR EACH ROW
DECLARE
BEGIN
INSERT INTO audita_salarios (
id_emp, salario_antiguo, salario_nuevo, fecha, username
) VALUES (
:new.id_empleado, :old.salario, :new.salario, sysdate, USER
);
END;
/
INSERT INTO empleados_pac ( id_empleado, salario ) VALUES ( 1, 10 );
UPDATE empleados_pac
SET salario = 20
WHERE id_empleado = 1;
BEGIN
DBMS_SESSION.SLEEP(1);
END;
/
UPDATE empleados_pac
SET salario = 30
WHERE id_empleado = 1;
-- You do not actually need to constraint on the audit table unless you
-- want to enforce that salary updates must be on different times.
ALTER TABLE audita_salarios DROP CONSTRAINT audita_salarios__pk;
UPDATE empleados_pac
SET salario = 40
WHERE id_empleado = 1;
Then:
SELECT * FROM audita_salarios;
Outputs:
ID_EMP | SALARIO_ANTIGUO | SALARIO_NUEVO | FECHA | HORA | USERNAME
-----: | --------------: | ------------: | :------------------ | :------- | :--------------------------
1 | 10 | 20 | 2021-04-01 13:02:42 | 13:02:42 | FIDDLE_XRWUFWRAYDJNIGNCOQFP
1 | 20 | 30 | 2021-04-01 13:02:43 | 13:02:43 | FIDDLE_XRWUFWRAYDJNIGNCOQFP
1 | 30 | 40 | 2021-04-01 13:02:43 | 13:02:43 | FIDDLE_XRWUFWRAYDJNIGNCOQFP
db<>fiddle here
Related
CREATE TABLE source_det (
det_id number(10) by default IDENTITY
e_id NUMBER(10),
sys_name VARCHAR2(20),
ref_id NUMBER(10),
sys_other VARCHAR2(30)
);
INSERT INTO source_det VALUES(1,11,'SOURCE',992,null);
INSERT INTO source_det VALUES(2,11,'SOURCE',637,null);
INSERT INTO source_det VALUES(3,11,'SOURCE',null,'Manual');
INSERT INTO source_det VALUES(4,11,'TARGET',637,null);
INSERT INTO source_det VALUES(5,12,'TARGET',637,null);
Audit table:
CREATE SEQUENCE audit_tab_sq;
CREATE TABLE audit_tab (
a_id NUMBER(10) default audit_tab_sq.nextval,
l_transaction varchar2(20),--INSERT, UPDATE, DELETE
e_id NUMBER(10),
sys_name VARCHAR2(20),
value_old VARCHAR2(20),
value_new VARCHAR2(20)
);
I need to create a trigger that will get fired whenever there is new event occurred on the main table i.e source_det
Example:
Lets say for this record
INSERT INTO source_det VALUES(11,'SOURCE',null,'Manual');
For e_id 11 whose sys_name is SOURCE and ref_id is null and sys_other is Manual.
So, for this record if user has filled in the ref_id to 321 and sys_other to Document.
Then in my audit table, a new entry will get inserted only for this row even though we have multiple e_id.
Expected Output:
+------+---------------+------+----------+-----------+-----------+
| a_id | l_transaction | e_id | sys_name | value_old | value_new |
+------+---------------+------+----------+-----------+-----------+
| 1 | UPDATE | 11 | SOURCE | null | Document |
+------+---------------+------+----------+-----------+-----------+
Likewise, if there are any new records inserted or deleted same should be the case.
I am wondering if this can be handled using trigger because there can be multiple e_id
Tool used: Oracle SQL Developer(18c)
From the information you have given, it seems as though you are missing a unique value (key) for your tables and this may be causing your problems.
Per your given code, you have
CREATE TABLE source_det (
e_id NUMBER(10),
sys_name VARCHAR2(20),
ref_id NUMBER(10),
sys_other VARCHAR2(30)
);
INSERT INTO source_det VALUES(11,'SOURCE',992,null);
INSERT INTO source_det VALUES(11,'SOURCE',637,null);
INSERT INTO source_det VALUES(11,'SOURCE',null,'Manual');
INSERT INTO source_det VALUES(11,'TARGET',637,null);
INSERT INTO source_det VALUES(12,'TARGET',637,null);
The database currently has no way of differentiating between the row inserted with INSERT INTO source_det VALUES(11,'SOURCE',637,null); and the row inserted in the following INSERT INTO source_det VALUES(11,'SOURCE',637,null);.
I would recommend adding a unique value (key) to each of your tables so that there is a unique value to identify each tuple. You can achieve this in multiple ways (id, unique pair, etc.), but your dataset doesn't seem to contain unique pairs, so an id would probably work best:
CREATE TABLE source_det (
id NUMBER GENERATED BY DEFAULT AS IDENTITY
e_id NUMBER(10),
sys_name VARCHAR2(20),
ref_id NUMBER(10),
sys_other VARCHAR2(30),
PRIMARY KEY(id)
);
Then you can create a trigger that fires on update | insert | delete and will have a unique key to reference for each row.
Here's how; trigger:
SQL> CREATE OR REPLACE TRIGGER trg_sdet_audit
2 BEFORE INSERT OR UPDATE OR DELETE
3 ON source_det
4 FOR EACH ROW
5 BEGIN
6 IF INSERTING
7 THEN
8 INSERT INTO audit_tab (a_id,
9 l_transaction,
10 e_id,
11 sys_name,
12 value_old,
13 value_new)
14 VALUES (audit_tab_sq.NEXTVAL,
15 'INSERT',
16 :new.e_id,
17 :new.sys_name,
18 NULL,
19 :new.sys_other);
20 ELSIF UPDATING
21 THEN
22 INSERT INTO audit_tab (a_id,
23 l_transaction,
24 e_id,
25 sys_name,
26 value_old,
27 value_new)
28 VALUES (audit_tab_sq.NEXTVAL,
29 'UPDATE',
30 :new.e_id,
31 :new.sys_name,
32 :old.sys_other,
33 :new.sys_other);
34 ELSIF DELETING
35 THEN
36 INSERT INTO audit_tab (a_id,
37 l_transaction,
38 e_id,
39 sys_name,
40 value_old,
41 value_new)
42 VALUES (audit_tab_sq.NEXTVAL,
43 'DELETE',
44 :new.e_id,
45 :new.sys_name,
46 :old.sys_other,
47 NULL);
48 END IF;
49 END;
50 /
Trigger created.
Testing:
SQL> UPDATE source_det
2 SET sys_other = 'Other'
3 WHERE e_id = 11;
4 rows updated.
SQL> DELETE FROM source_det
2 WHERE det_id = 2;
1 row deleted.
Result:
SQL> SELECT * FROM audit_tab;
A_ID L_TRANSACTION E_ID SYS_NAME VALUE_OLD VALUE_NEW
---------- -------------------- ---------- -------------------- -------------------- --------------------
16 UPDATE 11 SOURCE Other
17 UPDATE 11 SOURCE Other
18 UPDATE 11 SOURCE Manual Other
19 UPDATE 11 TARGET Other
20 DELETE Other
SQL>
Note that it would probably be a good idea to include timestamp into the log table.
I'm learning PL/SQL right now and I have a doubt.
I have created the following table called tbProducts:
CREATE TABLE tbProducts (
nIDProduct NUMBER(2) NOT NULL,
vDescription VARCHAR2(20 CHAR),
nQuantity NUMBER(3),
nPrice NUMBER(6,2),
dLastDate DATE)
And I have inserted some values so the table is like this:
nIDProduct | vDescription | nQuantity | nPrice | dLastDate
1 | 'Hammer' | 50 | 3.25 | 13-MAY-2021
2 | 'Nails' | 100 | 0.75 | 28-AUG-2021
3 | 'Screws' | 250 | 0.16 | 21-JUL-2021
Now what I'm looking for is a boolean variable that can be called bUpdate that returns FALSE if today's date (26-AUG-2021) is greater than dLastDate and returns TRUE if it's less or equal so the table would look like this:
nIDProduct | vDescription | nQuantity | nPrice | dLastDate | bUpdate
1 | 'Hammer' | 50 | 3.25 | 13-MAY-2021 | FALSE
2 | 'Nails' | 100 | 0.75 | 28-AUG-2021 | TRUE
3 | 'Screws' | 250 | 0.16 | 21-JUL-2021 | FALSE
I am trying doing the following:
DECLARE
bUpdate BOOLEAN;
BEGIN
SELECT t.*, bUpdate(
IF SYSDATE > dLastDate THEN
bUpdate := FALSE;
ELSE
bUpdate := TRUE;
END IF
FROM tbProducts t
;
END;
I get an error saying that a FROM was expected after the SELECT statement.
Since I'm still learning I don't know what it's wrong in this statement, could someone help me? Is there a way to do it with a CURSOR too?
Thank you all!
What you try wouldn't work. If you want an additional column, you need to add the column to the table with an ALTER TABLE command - but in this case you're adding a non-deterministic expression and that cannot be added as a virtual column.
The easiest way to achieve what you want is to create a view on top of the table with the case statement as illustrated below:
CREATE TABLE tbproducts (
nidproduct NUMBER(2) NOT NULL,
vdescription VARCHAR2(20 CHAR),
nquantity NUMBER(3),
nprice NUMBER(6,2),
dlastdate DATE);
INSERT INTO tbproducts (nidproduct,vdescription,nquantity,nprice,dlastdate) VALUES (1,'Hammer', 50,3.25,TO_DATE('13-MAY-2021','DD-MON-YYYY'));
INSERT INTO tbproducts (nidproduct,vdescription,nquantity,nprice,dlastdate) VALUES (2, 'Nails',100,0.75,TO_DATE('28-AUG-2021','DD-MON-YYYY'));
INSERT INTO tbproducts (nidproduct,vdescription,nquantity,nprice,dlastdate) VALUES (3,'Screws',250,0.16,TO_DATE('21-JUL-2021','DD-MON-YYYY'));
CREATE VIEW tbproducts_v
AS
SELECT
nidproduct
,vdescription
,nquantity
,nprice
,dlastdate
,CASE WHEN SYSDATE > dlastdate THEN 'TRUE' ELSE 'FALSE' END as status
FROM tbproducts;
select * from tbproducts_v;
NIDPRODUCT VDESCRIPTION NQUANTITY NPRICE DLASTDATE STATU
---------- -------------------- ---------- ---------- ----------- -----
1 Hammer 50 3.25 13-MAY-2021 TRUE
2 Nails 100 .75 28-AUG-2021 FALSE
3 Screws 250 .16 21-JUL-2021 TRUE
If you insist on adding a new column then this is what you'd do.
ALTER TABLE tbproducts ADD status VARCHAR2(100);
UPDATE tbproducts t
SET (t.status) =
(SELECT
CASE WHEN SYSDATE > dlastdate THEN 'TRUE' ELSE 'FALSE' END
FROM tbproducts st
WHERE st.nidproduct = t.nidproduct);
if you just want to display the true/false in the console with a pl/sql block then this is an option (using implicit cursor for loop):
set serveroutput on size 999999
clear screen
DECLARE
BEGIN
FOR r IN (SELECT * FROM tbproducts) LOOP
dbms_output.put_line('Product '||r.vdescription ||', status: '||CASE WHEN SYSDATE > r.dlastdate THEN 'TRUE' ELSE 'FALSE' END);
IF r.dlastdate < SYSDATE THEN
dbms_output.put_line('Product '||r.vdescription ||' is expired !');
END IF;
END LOOP;
END;
/
note 1 : pl/sql has a boolean datatype, but sql does NOT have one. So in your table you'll need to store a string (1/0, T/F, Y/N, TRUE/FALSE).
note 2 : it's not a good practice to use camelcase in table names or column names. If you're creating them without quotes the names are case insensitive anyway.
i have this table salle that has 2 attributes
this is the table that inherits from salle, it's called salleCours and has 3 additional attributes.
when i run the second command in oracle 11g express in sql command line it says under 'under' : missing or invalid option.
i dont know if it's a syntax problem or something else
Create table salle(
Numero varchar(20) primary key,
Videoprojecteur char(1) ) ;
Create table salleCours UNDER salle(
Capacite number(3),
Retroprojecteur char(1),
micro char(1)) ;
You want to define an OBJECT type and then use an object-defined table:
CREATE TYPE salle_type AS OBJECT(
Numero varchar(20),
Videoprojecteur char(1)
) NOT FINAL;
CREATE TYPE salleCours_type UNDER salle_type(
Capacite number(3),
Retroprojecteur char(1),
micro char(1)
);
CREATE TABLE salle OF salle_type (
Numero PRIMARY KEY
);
Then you can insert rows of either type:
INSERT INTO salle VALUES( salle_type( 'abc', 'Y' ) );
INSERT INTO salle VALUES( salleCours_type( 'def', 'Y', 42, 'N', 'X' ) );
And, if you want the values:
SELECT s.*,
TREAT( VALUE(s) AS salleCours_type ).Capacite AS capacite,
TREAT( VALUE(s) AS salleCours_type ).Retroprojecteur AS Retroprojecteur,
TREAT( VALUE(s) AS salleCours_type ).micro AS micro
FROM salle s
Which outputs:
NUMERO | VIDEOPROJECTEUR | CAPACITE | RETROPROJECTEUR | MICRO
:----- | :-------------- | -------: | :-------------- | :----
abc | Y | null | null | null
def | Y | 42 | N | X
db<>fiddle here
there is table called event that act as parent the child inherit tables of event are special event and hotel event i have created the types as bellow but I'm contuse about how to create tables to these tables in oracle.I have referred most of the currently available solutions within Stack overflow, git hub etc. However, none of these solutions have worked out successfully.
Table types :
Event_t (
EventID:char(5),
EventType:varchar(20),
VenueName:varchar(50),
NoOfGuest:number(10)
) NOT FINAL
HotelEvent_t (
Date:date,
Price:numbr(8,2)
) UNDER Event_t
SpecialEvent_t (
BookingDate:date,
EndDate:date,
MenuNumber:number(2),
Reservation ref Reservation_t
) UNDER event_t
Thank you very much and any suggestion will be greatly appreciated.
Create your types using the correct syntax:
CREATE TYPE Event_t AS OBJECT(
EventID char(5),
EventType varchar(20),
VenueName varchar(50),
NoOfGuest number(10)
) NOT FINAL;
CREATE TYPE HotelEvent_t UNDER Event_t (
datetime date, -- Date is a keyword, try to use a different name.
Price number(8,2)
);
CREATE TYPE SpecialEvent_t UNDER event_t (
BookingDate date,
EndDate date,
MenuNumbers NUMBER(2),
Reservation ref Reservation_t
);
Then you can create an object table:
CREATE TABLE Events OF Event_T(
eventid CONSTRAINT Events__EventID__PK PRIMARY KEY
);
Then you can insert the different types into it:
INSERT INTO EVENTS VALUES(
HotelEvent_T(
'H1',
'HOTEL',
'Venue1',
42,
DATE '0001-02-03' + INTERVAL '04:05:06' HOUR TO SECOND,
123456.78
)
);
INSERT INTO EVENTS VALUES(
SpecialEvent_T(
'SE1',
'SPECIAL',
'Time Travel Convention',
-1,
SYSDATE,
TRUNC(SYSDATE),
0,
NULL
)
);
and get the data out of it:
SELECT e.*,
TREAT( VALUE(e) AS HotelEvent_T ).datetime AS datetime,
TREAT( VALUE(e) AS HotelEvent_T ).price AS price,
TREAT( VALUE(e) AS SpecialEvent_T ).bookingdate AS bookingdate,
TREAT( VALUE(e) AS SpecialEvent_T ).enddate AS enddate,
TREAT( VALUE(e) AS SpecialEvent_T ).menunumbers AS menunumbers
FROM Events e;
Which outputs:
EVENTID | EVENTTYPE | VENUENAME | NOOFGUEST | DATETIME | PRICE | BOOKINGDATE | ENDDATE | MENUNUMBERS
:------ | :-------- | :--------------------- | --------: | :------------------ | --------: | :------------------ | :------------------ | ----------:
H1 | HOTEL | Venue1 | 42 | 0001-02-03 04:05:06 | 123456.78 | null | null | null
SE1 | SPECIAL | Time Travel Convention | -1 | null | null | 2020-03-30 21:11:22 | 2020-03-30 00:00:00 | 0
db<>fiddle here
The typical way to create these tables in Oracle would be:
create table event_t (
event_id char(5) primary key not null,
event_type varchar2(20),
venue_mame varchar2(50),
no_of_guest number(10)
);
create table hotel_event_t (
event_date date,
price number(8,2),
event_id char(5),
constraint fk1 foreign key (event_id) references event_t (event_id)
);
create table special_event_t (
booking_date date,
end_date date,
menu_number number(2),
reservation_id char(5),
constraint fk2 foreign key (reservation_id)
references reservation_t (reservation_id),
event_id char(5),
constraint fk3 foreign key (event_id) references event_t (event_id)
);
This question already has answers here:
Multiple insert SQL oracle
(2 answers)
Closed 3 years ago.
I can not use the insert all to insert values into the first_name,last_name and phone columns.
CREATE TABLE accounts (
account_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
first_name VARCHAR2(25) NOT NULL,
last_name VARCHAR2(25) NOT NULL,
email VARCHAR2(100),
phone VARCHAR2(12) ,
full_name VARCHAR2(51) GENERATED ALWAYS AS(
first_name || ' ' || last_name
),
PRIMARY KEY(account_id)
);
INSERT ALL
INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197')
INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198')
INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199')
SELECT* FROM DUAL;
This is the error message I get when I try to Run the code:
ORA-00001: unique constraint (BTMDATABASE.SYS_C0086595925) violated
ORA-06512: at "SYS.DBMS_SQL", line 1721
1. INSERT ALL
2. INTO accounts(first_name,last_name,phone)VALUES('Trinity','Knox','410-555-0197')
3. INTO accounts(first_name,last_name,phone)VALUES('Mellissa','Porter','410-555-0198')
Exactly, you can not. The way you decided to create unique values for the account_id column won't work in insert all as all rows get the same value which violates the primary key constraint.
Two workarounds:
don't use insert all but separate insert statements
switch to a sequence in order to set primary key column's values
And, here's an example (if you need it):
SQL> create table accounts
2 (account_id number primary key,
3 first_name varchar2(20) not null
4 );
Table created.
SQL> create sequence seq_acc;
Sequence created.
SQL> create or replace trigger trg_acc_seq
2 before insert on accounts
3 for each row
4 begin
5 :new.account_id := seq_acc.nextval;
6 end;
7 /
Trigger created.
SQL> insert all
2 into accounts (first_name) values ('John')
3 into accounts (first_name) values ('Ted')
4 into accounts (first_name) values ('Leeanna')
5 select * from dual;
3 rows created.
SQL> select * from accounts;
ACCOUNT_ID FIRST_NAME
---------- --------------------
1 John
2 Ted
3 Leeanna
SQL>
Your statement:
INSERT ALL
INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197')
INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198')
INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199')
SELECT* FROM DUAL;
Will try to give all the rows the same account_id which will violate your primary key.
Instead, you can use separate INSERT statements; or, if you want a single statement/transaction then you can wrap the INSERT statements in an anonymous PL/SQL block:
BEGIN
INSERT INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199');
END;
/
or, you can also use INSERT INTO ... SELECT ... UNION ALL ...:
INSERT INTO accounts(first_name,last_name,phone)
SELECT 'Trinity', 'Knox', '410-555-0197' FROM DUAL UNION ALL
SELECT 'Mellissa','Porter','410-555-0198' FROM DUAL;
Output:
SELECT * FROM accounts;
ACCOUNT_ID | FIRST_NAME | LAST_NAME | EMAIL | PHONE | FULL_NAME
---------: | :--------- | :-------- | :---- | :----------- | :--------------
2 | John | Mobsey | null | 410-555-0197 | John Mobsey
3 | Ted | Scherbats | null | 410-555-0198 | Ted Scherbats
4 | Leeanna | Bowman | null | 410-555-0199 | Leeanna Bowman
5 | Trinity | Knox | null | 410-555-0197 | Trinity Knox
6 | Mellissa | Porter | null | 410-555-0198 | Mellissa Porter
Note: account_id of 1 is the failed INSERT ALL.
db<>fiddle here
INSERT INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199');