So I have two tables:
CREATE TABLE SALE(
OID_PA NUMBER(*,0) PRIMARY KEY,
Amount INTEGER NOT NULL,
Delivery_DATE DATE NOT NULL,
OID_V NUMBER(*,0) NOT NULL,
CONSTRAINT "AMOUNTCHECK" CHECK(Amount >=0),
FOREIGN KEY (OID_V) REFERENCES TICKET(OID_V));
and
CREATE TABLE PRODUCT
( Code NUMBER(*,0) PRIMARY KEY,
Stock INTEGER NOT NULL,
Price NUMBER(4,2) NOT NULL,
Production_Cost NUMBER NOT NULL,
Model VARCHAR2(50) NOT NULL,
TipoMueble VARCHAR2(50) NOT NULL,
TipoMaterial VARCHAR2(50) NOT NULL,
OID_PA NUMBER(*,0) NOT NULL,
CONSTRAINT "TipoMueble" CHECK(TipoMueble IN
('Canteado','Complementos','Glaciar',
'GrupoDiseño','Tiradores','Vitrinas')),
CONSTRAINT "TipoMaterial" CHECK(TipoMaterial IN ('Aluminio','Lacados',
'Polilaminado','Madera','Cristal','Chapa','Granito','Formica',
'Aglomerado','Marmol','Elementos de ferreteria')),
CONSTRAINT "StockNegativo" CHECK(Stock >=0),
CONSTRAINT "Precio" CHECK(Price>=0),
CONSTRAINT "CosteProduccion" CHECK (Production_cost>=0),
FOREIGN KEY (OID_PA) REFERENCES PAQUETE(OID_PA));
And now I want to create a trigger, the moment I modify the attribute "Amount" in the table "SALE" then it should check the table "PRODUCT".
if "stock" (in PRODUCT) is less than Amount (in SALE) then
RAISE_APPLICATION_ERROR(-20003, 'Not enough stock');
But I don't know how to make the trigger check in both tables at the same time.
right now my code looks likethis right now, it also has some errors that I'm not sure how to fix.
CREATE OR REPLACE TRIGGER TR_ENOUGH_STOCK
AFTER INSERT OR UPDATE OF Amount ON SALE
BEGIN
SELECT a.Amount, b.Stock
FROM ( SALE a inner join PRODUCT b on (a.Code = b.Code))
IF b.Stock < a.Amount THEN
RAISE APPLICATION ERROR(-20003, 'Not enough stock');
END IF;
END;
/
And I'm getting the following errors:
Error(209,5): PL/SQL: SQL Statement ignored
Error(212,5): PL/SQL: ORA-00933: SQL command not properly ended
Error(214,9): PLS-00103: Encountered the symbol "IF" when expecting one of
the following: ; <an identifier> <a double-quoted delimited-identifier>
Here's an example of how you might do that.
Create tables & a trigger:
SQL> create table sale (oid_pa number, amount number);
Table created.
SQL> create table product (code number, stock number, oid_pa number);
Table created.
SQL>
SQL> create or replace trigger trg_biu_stock
2 before insert or update
3 on sale
4 for each row
5 declare
6 l_stock product.stock%type;
7 begin
8 select stock into l_stock
9 from product
10 where oid_pa = :new.oid_pa;
11
12 if :new.amount > l_stock then
13 raise_application_error(-20000, 'Not enough stock');
14 end if;
15 end;
16 /
Trigger created.
SQL>
Insert sample records into the PRODUCT table:
SQL> insert into product values (1, 100, 10);
1 row created.
SQL> insert into product values (2, 50, 20);
1 row created.
SQL> select * From product;
CODE STOCK OID_PA
---------- ---------- ----------
1 100 10
2 50 20
SQL>
Let's test it:
SQL> -- 90 is less OID_PA = 10 stock value (which is 100)
SQL> insert into sale values (10, 90);
1 row created.
SQL> -- let's try to update it to a value larger than stock value:
SQL> update sale set amount = 200 where oid_pa = 10;
update sale set amount = 200 where oid_pa = 10
*
ERROR at line 1:
ORA-20000: Not enough stock
ORA-06512: at "HR.TRG_BIU_STOCK", line 9
ORA-04088: error during execution of trigger 'HR.TRG_BIU_STOCK'
SQL> -- how about a value which doesn't exceed the stock value?
SQL> update sale set amount = 5 where oid_pa = 10;
1 row updated.
SQL> select * From sale;
OID_PA AMOUNT
---------- ----------
10 5
SQL>
As of your attempt: apart from trigger code having wrong syntax, it wouldn't work anyway because you can't reference a table trigger is based on because it is right now being changed, i.e. it is mutating. Oracle raises an error in such cases (see an example below). The correct way is to reference that table's columns using :new or :old, as I did (see an example above).
SQL> create or replace trigger trg_biu_stock
2 before insert or update
3 on sale
4 for each row
5 declare
6 l_stock product.stock%type;
7 begin
8 select p.stock into l_stock
9 from product p
10 join sale s
11 on s.oid_pa = p.oid_pa
12 where p.oid_pa = :new.oid_pa;
13
14 if :new.amount > l_stock then
15 raise_application_error(-20000, 'Not enough stock');
16 end if;
17 end;
18 /
Trigger created.
SQL> update sale set amount = 50 where oid_pa = 10;
update sale set amount = 50 where oid_pa = 10
*
ERROR at line 1:
ORA-04091: table HR.SALE is mutating, trigger/function may not see it
ORA-06512: at "HR.TRG_BIU_STOCK", line 4
ORA-04088: error during execution of trigger 'HR.TRG_BIU_STOCK'
SQL>
Related
I am trying to make a Trigger to restrict the user to performing DDL on Saturday and Sunday but if someone tries to insert data it will save that that in the weekend_action table but also raise application error that cannot perform DDL.
SQL QUERY:
create or replace trigger tgr_wkd_action
before insert
on tbl_39_dept_k
for each row
declare
begin
IF trim(TO_CHAR(sysdate,'Day')) IN ('Tuesday', 'Sunday') then
RAISE_APPLICATION_ERROR (-20000,'you cannot perform DDL on Weekend');
end if;
if inserting then
insert into user_admin.weekend_actions values
(:NEW.Dept_no,
'updation',
'user'||user||'trying to insert data on'||'_'||sysdate||'from Table tbl_39_dept_k');
end if;
end tgr_wkd_action;
if someone tries to insert data
Exactly. Insert.
Your trigger fires before update, and that's a different DML.
Apart from that:
inserts and updates are DML (data manipulation). DDL you mentioned is data definition (these are create table, alter table, ...)
weekend action vs. Tuesday? Since when is Tuesday weekend?
Perhaps you meant
SQL> CREATE OR REPLACE TRIGGER tgr_wkd_action
2 BEFORE INSERT OR UPDATE
3 ON tbl_39_dept_k
4 FOR EACH ROW
5 DECLARE
6 BEGIN
7 IF TRIM (TO_CHAR (SYSDATE, 'Day', 'nls_date_language = english')) IN
8 ('Saturday', 'Sunday')
9 THEN
10 RAISE_APPLICATION_ERROR (-20000, 'you cannot perform DML on Weekend');
11 END IF;
12
13 IF INSERTING
14 THEN
15 INSERT INTO weekend_actions
16 VALUES (
17 :NEW.Dept_no,
18 'inserting',
19 'user'
20 || USER
21 || 'trying to insert data on'
22 || '_'
23 || SYSDATE
24 || 'from Table tbl_39_dept_k');
25 ELSIF UPDATING
26 THEN
27 INSERT INTO weekend_actions
28 VALUES (
29 :NEW.Dept_no,
30 'updating',
31 'user'
32 || USER
33 || 'trying to update data on'
34 || '_'
35 || SYSDATE
36 || 'from Table tbl_39_dept_k');
37 END IF;
38 END tgr_wkd_action;
39 /
Trigger created.
SQL>
SQL> INSERT INTO tbl_39_dept_k (dept_no) VALUES (10);
1 row created.
SQL>
Tables' contents:
SQL> select * from tbl_39_dept_k;
DEPT_NO
----------
10
SQL> select * from weekend_actions;
DEPT_NO ACTION MSG
---------- ---------- ----------------------------------------------------------------------
10 inserting userSCOTTtrying to insert data on_23.11.21from Table tbl_39_dept_k
SQL>
Pretending it is weekend today (while it is Tuesday):
<snip>
7 IF TRIM (TO_CHAR (SYSDATE, 'Day', 'nls_date_language = english')) IN
8 ('Tuesday', 'Sunday')
9 THEN
<snip>
39 /
Trigger created.
SQL>
SQL> INSERT INTO tbl_39_dept_k (dept_no) VALUES (20);
INSERT INTO tbl_39_dept_k (dept_no) VALUES (20)
*
ERROR at line 1:
ORA-20000: you cannot perform DML on Weekend
ORA-06512: at "SCOTT.TGR_WKD_ACTION", line 6
ORA-04088: error during execution of trigger 'SCOTT.TGR_WKD_ACTION'
SQL>
I am new to this, so this can be silly, but..
I have 3 created tables:
create table faculty (id_faculty number(2) Primary Key, //id , 2 digits, primary key
faculty_name varchar2(30) constraint f_name Not Null, // name, up to 30 symbols, not null
dean varchar2(20), // dean, up to 20 symbols
telephone varchar2(8)); //number, up to 8 symbols
create table student (id_student number(3) Primary Key, //id, 3 digits, primary key
student_name varchar(20) constraint s_name Not Null, // name, up to 20 symbols, not null
student_surname varchar(20) constraint s_surname Not Null, // name, up to 20 symbols, not null
course_year number(1) constraint s_course_year Check(course_year>0 and course_year<=6), // number, 1 digit, in range 1-6
faculty_id number(2), // id, 2 digits
constraint s_fkey Foreign key (Faculty_ID) References faculty(ID_faculty)); // foreign key to table faculty to id_faculty
create table money (id_payout number(4) Primary Key,
student_id number(3),
constraint m_fkey Foreign key (student_ID) References student(ID_student),
payout_date date constraint p_date Not Null,
stipend number(5,2) Check(stipend<151),
compensation number(5,2));
Then I created view:
create or replace view stud_stip AS
Select f.faculty_name, student_surname, student_name, s.course_year,
m.stipend, m.payout_date
from
faculty f inner join student s on f.id_faculty=s.faculty_id
inner join money m on m.student_id=s.id_student
where
m.payout_date = ( select distinct max(payout_date)
from money
where money.student_id=money.student_id)
with check option;
This is how the view looks
Then I wanted to add information to a view:
insert into stud_stip values ('Partikas tehnologiju', 'Lapa', 'Jana', 2, 120, '03.12.1998');
This is the error I got:
ORA-01779: cannot modify a column which maps to a non key-preserved table
I have googled all over the internet and did not solve my error, please don't send me other topics about this question, because I probably did read it.
I will be very grateful for the answers. Thank you in advance.
The way to do it is to create an instead of trigger on a view which would - in turn - insert rows into appropriate tables.
As all ID columns are primary keys and you didn't explain how you populate them, I'll do it using a sequence, in the same instead of trigger.
SQL> create sequence seqs;
Sequence created.
SQL> create or replace trigger trg_stud_stip
2 instead of insert on stud_stip
3 for each row
4 begin
5 insert into faculty (id_faculty, faculty_name)
6 values (seqs.nextval, :new.faculty_name);
7
8 insert into student (id_student, student_name, student_surname, course_year)
9 values (seqs.nextval, :new.student_name, :new.student_surname, :new.course_year);
10
11 insert into money (id_payout, stipend, payout_date)
12 values (seqs.nextval, :new.stipend, :new.payout_date);
13 end;
14 /
Trigger created.
Testing (don't insert strings into DATE datatype columns!):
SQL> insert into stud_stip
2 (faculty_name, student_surname, student_name, course_year, stipend, payout_date)
3 values
4 ('Partikas tehnologiju', 'Lapa', 'Jana', 2, 120, date '1998-12-03');
1 row created.
SQL> select * from faculty;
ID_FACULTY FACULTY_NAME DEAN TELEPHON
---------- ------------------------------ -------------------- --------
1 Partikas tehnologiju
SQL> select * from student;
ID_STUDENT STUDENT_NAME STUDENT_SURNAME COURSE_YEAR FACULTY_ID
---------- -------------------- -------------------- ----------- ----------
2 Jana Lapa 2
SQL> select * from money;
ID_PAYOUT STUDENT_ID PAYOUT_DAT STIPEND COMPENSATION
---------- ---------- ---------- ---------- ------------
3 03.12.1998 120
SQL>
It works. Now that you know how, improve it if necessary.
I have a table, while inserting new records into that table I want to specify new static value for all those records. That static value should be same for all those records, again while inserting some other records want to give another new static value automatically.
Id name salary staticid
-----------------------
1 xyx 100 sh0001
2 ddd 200 sh0001
3 dfs 250 sh0001
While inserting new records I need output like this
Id name salary staticid
------------------------
1 xyx 100 sh0001
2 ddd 200 sh0001
3 dfs 250 sh0001
4 dsaf 320 sh0002
5 wew 650 sh0002
6 ter 365 sh0002
Thanks,
Simplest thing to do would be to insert your static value as you are filling your data/table.
DECLARE #staticValue varchar(100)
SET #staticValue = 'sh0001'
--Static Value 1 / Inserts
INSERT INTO dbo.tblA
(Id, name, salary, staticid)
SELECT 1, 'xyx', 100, #staticValue
UNION SELECT 2, 'ddd', 200, #staticValue
UNION SELECT 3, 'dfs', 250, #staticValue
--Static Value 2 / Inserts
SET #staticValue = 'sh0002'
INSERT INTO dbo.tblA
(Id, name, salary, staticid)
SELECT 4, 'dsaf', 320, #staticValue
Another option, although it requires additional SQL Server permissions for DDL - Data Definition Language, would be to use a default constraint, as below
--Static Value 1 / Inserts
BEGIN TRANSACTION
GO
ALTER TABLE dbo.tblB ADD CONSTRAINT
DF_tblB_staticid DEFAULT 'sh0001' FOR staticid
GO
ALTER TABLE dbo.tblB SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
INSERT INTO dbo.tblB
(Id, name, salary)
SELECT 1, 'xyx', 100
UNION SELECT 2, 'ddd', 200
UNION SELECT 3, 'dfs', 250
--Static Value 2 / Inserts
BEGIN TRANSACTION
GO
ALTER TABLE dbo.tblB
DROP CONSTRAINT DF_tblB_staticid --Removes 'sh0001' default
GO
ALTER TABLE dbo.tblB ADD CONSTRAINT
DF_tblB_staticid DEFAULT ('sh0002') FOR staticid
GO
ALTER TABLE dbo.tblB SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
INSERT INTO dbo.tblB
(Id, name, salary)
SELECT 4, 'dsaf', 320
I have two columns and I need them both to be unique between each other (like it is 1 column).
My first attempt was to create sequence and set default constraints.
create sequence seq1
as bigint
start with 1
increment by 1
cache;
go
create table product (
pk uniqueidentifier
, id_1 bigint not null default (next value for seq1)
, id_2 bigint not null default (next value for seq1)
);
go
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
go
And it doesn't work. The result of query above will be:
id_1 id_2
1 1
2 2
3 3
4 4
5 5
Currently I stopped using 2 sequences with even and not even numbers.
create sequence seq2
as bigint
start with 1
increment by 2
cache;
go
create sequence seq3
as bigint
start with 2
increment by 2
cache;
go
But if in future I will need to add another column which also must be unique I will have a problem.
I also thinked about stored procedures. Something like this works for me.
create procedure sp_insertProduct
as
begin
declare #id1 as bigint = next value for seq1;
declare #id2 as bigint = next value for seq1;
insert into product (pk, id_1, id_2) values (newid(), #id1, #id2);
end
go
exec sp_insertProduct;
exec sp_insertProduct;
exec sp_insertProduct;
go
But due to my ORM framework restictions I cannot use stored procedures for inserting.
So is there a better solution for that problem?
PS. for some reasons I can not use uniqueidentifiers.
UPDATE
I think I need to explain question a bit clearly. I have a working solution for now (and both current answers will also work), but I wonder if there is a extensible solution to:
provide uniqueness of values in multiple columns (with ability to
add additional columns in future).
avoid using uniqueidentifiers
for better understanding of question, this is how I check uniqueness:
with src as (
select id_1 as id from product
union all
select id_2 as id from product
)
select id, count(*) as equal_values
from src
group by id
having (count(*) > 1)
create sequence seq1
as bigint
start with 1
increment by 2
cache;
go
create table product (
pk uniqueidentifier
, id_1 bigint not null default (next value for seq1)
, id_2 bigint not null default (next value for seq1 + 1)
);
go
doing this..
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
this would generate..
pk id_1 id_2
2A159914-8105-4DC1-9D7E-570CC5444172 1 2
6DAFEF16-2B81-4A10-99EF-B3F1A74389C6 3 4
8C6F6697-D993-4320-92BB-04CD56804C5A 5 6
AC97F37F-CAC3-4E83-BDD4-4B55D009C334 7 8
3DDAADA0-D7DB-4350-8087-ABF02B539552 9 10
SQL Fiddle
May be this seems very naive but it should work. I didn't check but you may need to add some parenthesis:
create table product (
pk uniqueidentifier
, id_1 bigint not null default (next value for seq1)
, id_2 bigint not null default (-next value for seq1)
);
go
How do you do multiple insert with SQL in Oracle 12c when you have an identity column?
INSERT ALL
INTO Table1 (Column2) Values (1)
INTO Table1 (Column2) Values (2)
SELECT * FROM dual;
where Table1 has column1 as an identity, will set the identity column to have the same value which violates the primary key constraint.
CREATE TABLE Table1 (
Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
column2 VARCHAR2(255),
column3 NUMBER,
PRIMARY KEY (Table1Id)
);
INSERT ALL
INTO Table1 (column2, column3) VALUES ('a', '1')
INTO Table1 (column2, column3) VALUES ('b', '2')
SELECT * FROM dual;
--SQL Error: ORA-00001: unique constraint violated
What am I doing wrong with this?
EDIT Added two test cases, and a possible workaround.
Though Insert statement and insert all statement are practically the same conventional insert statement. But when it comes to sequences, they work differently.
Test case 1 : Identity columns
SQL> DROP TABLE table1 PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE Table1 (
2 Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
3 column3 NUMBER,
4 PRIMARY KEY (Table1Id)
5 );
Table created.
SQL>
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES ('1')
3 INTO Table1 (column3) VALUES ('2')
4 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010439) violated
SQL>
Let's see what's actually happening under the hood -
SQL> CREATE TABLE Table1 (
2 Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
3 column3 NUMBER,
4 CONSTRAINT A UNIQUE (Table1Id)
5 );
Table created.
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES (1)
3 INTO Table1 (column3) VALUES (2)
4 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.A) violated
SQL> SELECT * FROM table1;
no rows selected
SQL> ALTER TABLE table1
2 DISABLE CONSTRAINT a;
Table altered.
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES (1)
3 INTO Table1 (column3) VALUES (2)
4 SELECT * FROM dual;
2 rows created.
SQL> SELECT * FROM table1;
TABLE1ID COLUMN3
---------- ----------
2 1
2 2
SQL>
So, the sequence progressed to nextval however there was an unique constraint violation the first time we did an Insert All. Next, we disabled the unique constraint, and the subsequent Insert All reveals that the sequence did not progress to nextval, rather it attempted to insert duplicate keys.
Though the issue doesn't occur with a INSERT-INTO-SELECT statement.
SQL> INSERT INTO table1(column3) SELECT LEVEL FROM dual CONNECT BY LEVEL <=5;
5 rows created.
SQL>
SQL> SELECT * FROM table1;
TABLE1ID COLUMN3
---------- ----------
2 1
3 2
4 3
5 4
6 5
SQL>
Surprisingly, as per the metadata, the sequence is supposed to proceed to nextval automatically, however it doesn't happen with an Insert All statement.
SQL> SELECT COLUMN_NAME,
2 IDENTITY_COLUMN,
3 DATA_DEFAULT
4 FROM user_tab_cols
5 WHERE table_name ='TABLE1'
6 AND IDENTITY_COLUMN='YES';
COLUMN_NAME IDENTITY_COLUMN DATA_DEFAULT
--------------- --------------- ------------------------------
TABLE1ID YES "LALIT"."ISEQ$$_94458".nextval
SQL>
Test Case 2 : Using a sequence explicitly
The INSERT ALL would work the same way whether an identity column is used or an explicit sequence is used.
SQL> DROP SEQUENCE s;
Sequence dropped.
SQL>
SQL> CREATE SEQUENCE s;
Sequence created.
SQL>
SQL> DROP TABLE t PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE t (
2 ID NUMBER,
3 text VARCHAR2(50),
4 CONSTRAINT id_pk PRIMARY KEY (ID)
5 );
Table created.
SQL>
SQL> INSERT ALL
2 INTO t VALUES (s.nextval, 'a')
3 INTO t VALUES (s.nextval, 'b')
4 INTO t VALUES (s.nextval, 'c')
5 INTO t VALUES (s.nextval, 'd')
6 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.ID_PK) violated
SQL>
SQL> SELECT * FROM T;
no rows selected
SQL>
SQL> ALTER TABLE t
2 DISABLE CONSTRAINT id_pk;
Table altered.
SQL> INSERT ALL
2 INTO t VALUES (s.nextval, 'a')
3 INTO t VALUES (s.nextval, 'b')
4 INTO t VALUES (s.nextval, 'c')
5 INTO t VALUES (s.nextval, 'd')
6 SELECT * FROM dual;
4 rows created.
SQL> SELECT * FROM T;
ID TEXT
---------- ----------------------------------------
2 a
2 b
2 c
2 d
SQL>
Possible workaround - Using a ROW LEVEL trigger
SQL> CREATE OR REPLACE TRIGGER t_trg
2 BEFORE INSERT ON t
3 FOR EACH ROW
4 WHEN (new.id IS NULL)
5 BEGIN
6 SELECT s.NEXTVAL
7 INTO :new.id
8 FROM dual;
9 END;
10 /
Trigger created.
SQL> truncate table t;
Table truncated.
SQL> INSERT ALL
2 INTO t (text) VALUES ('a')
3 INTO t (text) VALUES ('b')
4 INTO t (text) VALUES ('c')
5 INTO t (text) VALUES ('d')
6 SELECT * FROM dual;
4 rows created.
SQL> SELECT * FROM t;
ID TEXT
---------- -------------------------
3 a
4 b
5 c
6 d
SQL>
Here's a workaround using the UNION ALL method instead of the INSERT ALL method. For some reason the data must be wrapped in a select * from (...) or it will generate the error ORA-01400: cannot insert NULL into ("JHELLER"."TABLE1"."TABLE1ID").
insert into table1(column2, column3)
select *
from
(
select 'a', '1' from dual union all
select 'b', '2' from dual
);