Condition in SQLite trigger for inheritance - sql

I'm working with SQLite,I am using XOR single table inheritance, I want to create a trigger that enables me to:
Check before insertion if the InstructionRefs.id is already created in the table RideHeightRefs
Ckeck before insertion that the InstructionRefs.id does not exist in the other inherited table StrappingRefs.
I took some oracle PL/SQL code and changed it, I guess I am writing it wrong starting from IF NOT EXISTS (SELECT id...):
CREATE TRIGGER IF NOT EXISTS insert_instructionRefs_trigger BEFORE INSERT ON InstructionRefs
BEGIN
IF NOT EXISTS (SELECT id FROM RideHeightRefs AS RHR INNER JOIN InstructionRefs
IR ON RHR.id = IR.id)
BEGIN
SELECT RAISE(FAIL, '"RideHeightRefs" key is unknown. Insertion in
"instructionRefs" is impossible.')
END'
IF NOT EXISTS (SELECT *
FROM (SELECT RideHeightRefs
FROM StrappingRefs
UNION ALL
SELECT RideHeightRefs
FROM InstructionRefs) T
WHERE RideHeightRefs IN (SELECT RideHeightRefs
FROM NEW))
BEGIN
SELECT RAISE(FAIL, '"RideHeightRefs" key is used in another table. Insertion in "StrappingRefs" is impossible.')
END
END
How can I modify the code to make it compatible with sqlite syntax ?

To check that the corresponding row in the base table exists, just use a foreign key constraint.
SQLite has no IF statement. To check for something, add a WHERE clause to the SELECT FAIL, or use the trigger's WHEN clause:
CREATE TRIGGER IF NOT EXISTS insert_instructionRefs_trigger
BEFORE INSERT ON InstructionRefs
WHEN EXISTS (SELECT *
FROM StrappingRefs
WHERE id = NEW.id)
BEGIN
SELECT RAISE(FAIL, '"RideHeightRefs" key is used in another table. Insertion in "StrappingRefs" is impossible.');
END;

Related

Trigger to automatically add tuples to relation

I'm trying to create a trigger on SQLITE to automatically add tuples to another table when something is added to a certain table, but firstly, I want to check if what I'm adding belongs to another third table.
This is my trigger:
DROP TRIGGER IF EXISTS atualizaBibliotecas;
CREATE TRIGGER atualizaBibliotecas
AFTER INSERT ON FoiComprado
FOR EACH ROW
BEGIN
SELECT CASE
WHEN
(
SELECT count(*)
FROM Album
WHERE idItem = NEW.idItem
) <> 0
THEN INSERT INTO Possui(idItem, email, dataAquisicao) VALUES(11,'aaaa#bbbb.com', '2019-05-14');
END;
This is the error I'm receiving:
Error: near line 8: near "INSERT": syntax error
LINE 8:
CREATE TRIGGER atualizaBibliotecas
I believe that you want :-
CREATE TRIGGER atualizaBibliotecas
AFTER INSERT ON FoiComprado
FOR EACH ROW
WHEN
(
SELECT count(*)
FROM Album
WHERE idItem = NEW.idItem
) <> 0
BEGIN
INSERT INTO Possui(idItem, email, dataAquisicao) VALUES(11,'aaaa#bbbb.com', '2019-05-14');
END;
SQLite doesn't support control-flow logic in its triggers. You can express this as a conditional insert:
INSERT INTO Possui (idItem, email, dataAquisicao)
SELECT 11, 'aaaa#bbbb.com', '2019-05-14'
WHERE NOT EXISTS (SELECT 1 FROM Album a WHERE a.idItem = NEW.idItem);
I find it surprising that you are hardcoding 11 for the insert. I would expect NEW.idITEM.

Comparing :new value inserted with a trigger

I'm trying to build a trigger that checks if the row that is gonna be inserted, exists in another table.
Basically my 2 tables share one column, ID.
I want to prevent the insertion when the new row doesnt exist at least once in the other table.
I have this:
create or replace trigger BIM
before insert on TABLE1
for each row
begin
if not exists (select 1 from TABLE2 where TABLE2.ID = :new.TABLE1.ID)
then
raise_application_error(-20634, 'Error');
end if;
end;
But i'm getting this:
PLS-00049: bad bind variable 'NEW.TABLE1'
Gordon is right, It is preferable to use Foreign Key constraint for this scenario.
The problem with your code ( apart from the error which Gordon pointed out )is that unlike few other DBMS like Postgres, In Oracle you cannot use EXISTS in a PL/SQL expression/statements like IF. It should be a purely SQL statement.
create or replace trigger BIM
before insert on TABLE1
for each row
declare
l_id_exists INT;
begin
select CASE WHEN
exists (select 1 from TABLE2 where TABLE2.ID = :new.ID)
THEN 1
ELSE 0 END INTO l_id_exists from dual;
if l_id_exists = 0
then
raise_application_error(-20634, 'Error');
end if;
end;
/
DEMO
You don't need to repeat the table name:
create or replace trigger BIM
before insert on TABLE1
for each row
begin
if (select 1 from TABLE2 where TABLE2.ID = :new.ID and rownum = 0) is not null
then
raise_application_error(-20634, 'Error');
end if;
end;
That said, this is an odd requirement. I would recommend that you use a foreign key constraint, but you explicitly say "at least once". That leads me to suspect that you have a bad data model -- you are missing some sort of entity where the id would be the primary key of that table.

Table not updated by trigger and procedure

I have a system which people do orders on, each order has actions, and a table exists called cm_ord_order_action. Sometimes actions fail, so I need to make a trigger that gets information for the failed order action and populates a table called cm_ord_failed_order.
the trigger is shown below:
CREATE OR REPLACE TRIGGER CM.TRGID_CM_ORD_FAILED_ORDER
AFTER UPDATE ON CM.CM_ORD_ORDER_ACTION
FOR EACH ROW
BEGIN
IF (:new.STATUS = 'FA') THEN
CM.CM_FAILED_ORDER_MLT(:new.order_unit_id, :new.order_id, :new.action_type);
END IF;
END;
/
This trigger passes parameters to a procedure which updates the table:
CREATE OR REPLACE PROCEDURE CM_FAILED_ORDER_MLT(
v_order_unit_id NUMBER,
v_order_id in NUMBER,
v_action_type in VARCHAR)
AS
v_lob varchar(100);
v_step varchar(100);
v_error varchar(200);
BEGIN
SELECT
ITEM.LOB_NAME, ST.STEP_NAME, ASS.STEP_ERROR
INTO v_lob, v_step, v_error
FROM
CM.CM_ORD_ORDER_ACTION OA
INNER JOIN CM.CM_ORD_ASSIGNMENTS ASS
ON OA.ORDER_UNIT_ID = ASS.ORDER_ACTION_ID
INNER JOIN CM.CM_ORD_PROCESS_STEP ST
ON ST.ORD_PROCESS_STEP_ID = ASS.STEP_ID
INNER JOIN CM.CM_ORD_AP_ITEM ITEM
ON ITEM.AP_SUBSCRIBER_ID = OA.AP_SUBSCRIBER_ID
WHERE ASS.COMPLETION_STATUS = 'FA'
AND OA.ORDER_ID = v_order_id
AND OA.ORDER_UNIT_ID = v_order_unit_id
GROUP BY OA.ORDER_UNIT_ID, ITEM.LOB_NAME, ST.STEP_NAME, ASS.STEP_ERROR;
INSERT INTO CM_ORD_FAILED_ORDER (ORDER_ID, FAILED_DATE, ORDER_ACTION_ID, ACTION_TYPE, LOB, STEP, ERROR)
VALUES (v_order_id, sysdate, v_order_unit_id, v_action_type, v_lob, v_step, v_error);
END CM_FAILED_ORDER_MLT;
/
There is probably something wrong here because:
A - Even though the trigger is for after update on cm_ord_order_action, when the trigger is enabled, the status is not being updated, but when I disable the trigger the status is updated.
B - the table cm_ord_failed_order is not being populated with the information.
Thanks in advance.
You can avoid the mutating table error your script is somehow ignoring or discarding by doing the insert directly in the trigger, where you have the details from the row being updated in the :NEW pseudorecord and don't have to query it again. You can also do an insert...select without needing local variables.
I think this is a rough translation:
CREATE OR REPLACE TRIGGER CM.TRGID_CM_ORD_FAILED_ORDER
AFTER UPDATE ON CM.CM_ORD_ORDER_ACTION
FOR EACH ROW
WHEN (new.STATUS = 'FA')
BEGIN
INSERT INTO CM_ORD_FAILED_ORDER (ORDER_ID, FAILED_DATE, ORDER_ACTION_ID, ACTION_TYPE,
LOB, STEP, ERROR)
SELECT
DISTINCT :new.ORDER_ID, sysdate, :new.Order_Unit_Id, :new.Action_Type,
ITEM.LOB_NAME, ST.STEP_NAME, ASS.STEP_ERROR
FROM
CM.CM_ORD_ASSIGNMENTS ASS
INNER JOIN CM.CM_ORD_PROCESS_STEP ST
ON ST.ORD_PROCESS_STEP_ID = ASS.STEP_ID
CROSS JOIN CM.CM_ORD_AP_ITEM ITEM
WHERE ASS.ORDER_ACTION_ID = :new.ORDER_UNIT_ID
AND ASS.COMPLETION_STATUS = :new.STATUS
AND ITEM.AP_SUBSCRIBER_ID = :new.AP_SUBSCRIBER_ID;
END CM_FAILED_ORDER_MLT;
/
The DISTINCT (instead of grouping) and CROSS JOIN suggest you're missing a join condition in your original query, but without your table structures and data that may not be the case.
Alternatively you could keep the insert in a procedure, but pass :newAP_SUBSCRIBER_ID` as another argument, since that seems to be the only column you need from the mutating table that you aren't already passing in.
Your trigger could also be a BEFORE UPDATE rather than AFTER UPDATE.
An alternative to Alex's solution that avoids the need for a cross join would be to change the procedure to:
create or replace procedure cm_failed_order_mlt (v_order_unit_id number,
v_order_id in number,
v_action_type in varchar,
v_ap_subscriber_id in cm.cm_ord_order_action.ap_subscriber_id%type)
as
v_lob varchar(100);
v_step varchar(100);
v_error varchar(200);
begin
select distinct lob_name
into v_lob
from cm.cm_ord_ap_item
where ap_subscriber_id = v_ap_subscriber_id;
select distinct st.step_name, ass.step_error
into v_step, v_error
from cm.cm_ord_assignments ass
inner join cm.cm_ord_process_step st on st.ord_process_step_id = ass.step_id
where ass.completion_status = 'FA'
and ass.order_action_id = v_order_id
and oa.order_unit_id = v_order_unit_id;
insert into cm_ord_failed_order (order_id, failed_date, order_action_id, action_type, lob, step, error)
values (v_order_id, sysdate, v_order_unit_id, v_action_type, v_lob, v_step, v_error);
end cm_failed_order_mlt;
/
Or, to remove the cross join in Alex's solution, simply replace it with a scalar subquery, e.g.:
select (select distinct lob_name from cm.cm_ord_ap_item where ap_subscriber_id = v_ap_subscriber_id), ...
Like #JustinCave said, it is clear that you have mutating table error:
Mutating table exceptions occur when we try to reference the
triggering table in a query from within row-level trigger code
On a trigger on CM_ORD_ORDER_ACTION you are selecting from that same table. Try to redo the query in the procedure without referencing CM_ORD_ORDER_ACTION.

Trigger that checks if the same tuple exists in two different tables

I wrote the following code:
CREATE OR REPLACE TRIGGER CHECK_tuple
BEFORE INSERT ON tableB
FOR EACH ROW
DECLARE IS_JOIN BOOLEAN:=FALSE
BEGIN
SELECT tableB.column1, tableB.column2,
CASE
WHEN IS_JOIN:= FALSE THEN raise_application_error(-20101, 'ERROR.');
ELSE IS_JOIN:= TRUE
END AS CHCK_JOIN
FROM tableB
JOIN tableA
ON tableB.column1=tableA.column1 AND tableB.column2=tableA.column2;
END;
I have to check if a tuple (t1) exits in table A (with "tuple", i mean the entire row of the table with multiple columns). If exists, it has to match with t2 in table B. Before one inserts tuple t2 in table B, the trigger must activate. If t1 doesn't match with t2, the flag IS_JOIN will remain FALSE and Oracle SQL will give an error. Else, if t1 is equal to t2, IS_JOIN will be TRUE and no action will be take. I want this "check" to take place for each row that one will insert in table B. Is this the proper way to do it? If the task isn't clear, please ask for further info.
The proper way to do something like this:
CREATE OR REPLACE TRIGGER test_test_CHECK_tuple
BEFORE INSERT ON tableB
FOR EACH ROW
declare
v_cnt number(10);
BEGIN
SELECT count(*)
into v_cnt
FROM tableA
where column1=:new.column1
and column2=:new.column2;
if v_cnt= 0 then
raise_application_error(-20101, 'ERROR.');
end if;
END;
:new means, that are the values, you want to insert. There is no other way to use that values.
btw. that is not really how a foreign key works, since a foreign key is assigned to a primary key or unique key

Insert deleted rows to new table in stored procedure

How can I insert the rows deleted in a delete statement into a new table within a stored procedure in DB2 SQL?
DB2 allows the following syntax to return the deleted rows:
select * from old table (
delete from my_table where foo > 1
)
For some reason, you can't just do an insert based on the results returned from that statement. However, you can use common table expressions as a kludgy workaround:
with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted)
This has an unnecessary extra select statement that I don't want, but at least it works. Deleted rows get inserted into another table.
However, how can I do this within a stored procedure?
A stored procedure doesn't allow a bare select statement. I thought of putting it within a set statement:
set my_var = (
with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted)
);
However, this fails because a common table expression is not allowed within such a statement.
Is there any way to do this within a stored procedure?
(The task can be done using some other method as a work-around. But I want to find out if it is possible to do it this way. If this is not possible, it seems like quite a dumb restriction.)
Update: I'm using DB2 9.7 LUW.
If you issue a select, you have to consume the result set somehow, whether it is in a procedure or another application. You can either run a dummy loop within the procedure, like:
for t in (with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted)
) loop
null;
end loop;
or use an explicit cursor:
declare c1 cursor for with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted);
...
open c1;
close c1;
Note that neither of these is tested.
Why dont you flip it around? You can INSERT from a SELECT, and you can SELECT the rows from a DELETE.