Oracle Apex - REST data source - nested JSON array - trigger two tables - update function - plsql

This question is a follow up to another SO question.
I've followed Carsten's instructions on the previous question. The trigger works as expected. I now just need to figure out how to handle the updating function of the trigger.
create or replace trigger "TR_MAINTAIN_LINES"
AFTER
insert or update or delete on "ORDERS_LOCAL"
for each row
begin
if inserting then
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) j );
elsif deleting then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
elsif updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
--
-- handle the update case here.
-- I would simply delete and re-insert ORDER_ITEMS rows.
end if;
end;
Carsten did mention in his answer to a previous question:
"In the UPDATING case, :old.{column-name} references the value of a table column column before the update, :new.{column-name} references the value after the update."
Would I leave the delete line? :
elsif updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
--
-- handle the update case here.
-- I would simply delete and re-insert ORDER_ITEMS rows.
Is the pseudo-code below the correct way to handle this? :
elsif updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
:new.line_number,
:new.product_id,
:new.quantity,
:new.price )

I'm assuming that the update could potentially mean a new json value in ORDERS_LOCAL.ORDER_ITEMS. In that case the json needs to be parsed just like for the insert. Combine both inserting and deleting in a single if statement to avoid duplicate code.
create or replace trigger "TR_MAINTAIN_LINES"
AFTER
insert or update or delete on "ORDERS_LOCAL"
for each row
begin
if inserting or updating then
if updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
end if;
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) j );
elsif deleting then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
end if;
end;

Related

Oracle Apex - REST data source - nested JSON array - trigger two tables - delete function error ORA-04091

This question is a follow up to another SO question.
I have an app with a REST Data Source taking in JSON responses from an API. There are two tables ORDERS and ORDER_ITEMS. The ORDERS table contains a column ORDER_ITEMS which is a JSON Document type.
I created a trigger on my ORDERS table which runs AFTER INSERT, UPDATE or DELETE on my ORDERS table, and which maintains the ORDER_ITEMS table (Extracts the JSON array from the ORDERS table ORDER_ITEMS column and inserts into ORDER_ITEMS table and each column).
create or replace trigger "TR_MAINTAIN_LINES"
AFTER
insert or update or delete on "ORDERS_LOCAL"
for each row
begin
if inserting or updating then
if updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
end if;
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) j );
elsif deleting then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
end if;
end;
The trigger works for AFTER INSERT and UPDATE. However, when I try to DELETE a row from the ORDERS table, I receive an error. error ORA-04091: table WKSP_DEMO.ORDER_ITEMS_LOCAL is mutating, trigger/function may not see it
But deleting a row from the ORDER_ITEMS table works as expected.
ORDERS table
enter image description here
ORDERS table data
enter image description here
ORDER_ITEMS table
enter image description here
ORDER_ITEMS table data
enter image description here
Triggers enter image description here
BI_ORDER_ITEMS_LOCAL trigger
create or replace trigger "BI_ORDER_ITEMS_LOCAL"
before insert on "ORDER_ITEMS_LOCAL"
for each row
begin
if :NEW."LINE_ID" is null then
select "ORDER_ITEMS_LOCAL_SEQ".nextval into :NEW."LINE_ID" from sys.dual;
end if;
end;
Error Message enter image description here
---- UPDATE ------
So I took out the delete portion of the trigger and both ORDER and ORDER_LINES table rows get deleted as expected.
create or replace trigger "TR_MAINTAIN_LINES"
AFTER
insert or update on "ORDERS_LOCAL"
for each row
begin
if inserting or updating then
if updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
end if;
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) j );
end if;
end;
you have a conflict between the ON DELETE CASCADE clause and your trigger logic. In the DELETE case, both attempt to delete the child rows. You can ...
remove the DELETE section from your trigger and let the ON DELETE CASCADE clause do the job
remove the ON DELETE CASCADE clause from your foreign key.
I think, I'd recommend the second option, so that all that logic is in one place, i.e. your trigger.

Oracle Apex - REST data source - extract a nested JSON array - trigger two tables - PLSQL error question

This question is a follow up to another SO question.
I've followed Carsten's instructions on the previous question. I am now receiving a new error.
Compilation failed, line 9 (08:51:36) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PL/SQL: ORA-00904: "J"."PRICE": invalid identifierCompilation failed, line 3 (08:51:36) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PL/SQL: SQL Statement ignored
Trigger
create or replace trigger "TR_MAINTAIN_LINES"
AFTER
insert or update or delete on "ORDERS_LOCAL"
for each row
begin
if inserting then
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) );
elsif deleting then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
elsif updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
--
-- handle the update case here.
-- I would simply delete and re-insert LINES rows.
end if;
end;
What I need help figuring out
The order_id column in ORDER_ITEMS table should be a foreign key to the ORDERS table referring to the order_id. That way each 'order line item' can be traced back to the order_id. Is :new.order_id correct in this case?
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
The line_id column in ORDER_ITEMS table should be automatically assigned based on the next value (ex: line_id:98, line_id:99, line_id:100) line_id is not in the JSON response. Is seq_line_id.nextval, correct in this case?
I am not sure what the 'j' is referring to. (j.quantity, j.price)
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) );
Does :new.order_items grab the order_items from the JSON array?
'$[*]' columns = $– Start with the current object. [] – Look inside an array
Would I include order_id in here as well?
JSON RESPONSE
{
"order_id": "HO9b6-ahMY-B2i9",
"order_number": 34795,
"order_date": "2022-11-02",
"store_id": 2,
"full_name": "Ronda Perfitt",
"email": "rperfitt1#microsoft.com",
"city": "Fresno",
"state": "California",
"zip_code": "93762",
"credit_card": "5108758574719798",
"order_items": [
{
"line_number": 1,
"product_id": 2,
"quantity": 1,
"price": 3418.85
},
{
"line_number": 2,
"product_id": 7,
"quantity": 1,
"price": 4070.12
}
]
},
I've found a few resources for the json_table function online but I'm having difficulty finding one that's within a trigger function similar to Carsten's code. Your help explaining this would be much appreciated.
-----------UPDATE---------
ORDERS_LOCAL Table
ORDERS_LOCAL Table data
ORDER_ITEMS_LOCAL Table
LINE_ID column to be automatically created
ORDER_ID column foreign key to ORDERS_LOCAL table
PRODUCT_ID column foreign key to PRODUCTS table
The JSON_TABLE expression in the FROM clause is missing the alias. The select list uses the "j" prefix for the columns from the JSON_TABLE expression, but the JSON_TABLE is not aliased ...
You might change as follows (note the additional "j" in the last line)
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) j );

Oracle 11g Triggers

I have create a table person(id, name ,samenamecount).The samenamecount attribute can be null but for each row can store the row count for same names.I am achieving this by calling a stored procedure inside a after insert trigger.Below is my code.
create or replace procedure automatic(s in person.name%type)
AS
BEGIN
update person set samenamecount=(select count(*) from person where name=s) where name=s;
END;
create or replace trigger inserttrigger
after insert
on person
for each row
declare
begin
automatic(:new.name);
end;
On inserting a row it is giving error like
table ABCD.PERSON is mutating, trigger/function may not see it.
Can somebody help me to figure out this?
If you have the table:
CREATE TABLE person (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT person__id__pk PRIMARY KEY,
name VARCHAR2(20)
NOT NULL
);
Then rather than creating a trigger, instead, you could use a view:
CREATE VIEW person_view (
id,
name,
samenamecount
) AS
SELECT id,
name,
COUNT(*) OVER (PARTITION BY name)
FROM person;
You can use the trigger:
CREATE TRIGGER inserttrigger
AFTER INSERT ON person
BEGIN
MERGE INTO person dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY name) AS cnt
FROM person
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE SET samenamecount = src.cnt;
END;
/
fiddle
If you want to make it more efficient then you could use a compound trigger and collate the names that are being inserted and only update the matching rows.

how to insert row on table B when table A is updated, using a trigger in SQLlite

Table A
userID
Name
Table B
UserID
Timestamp
I need to create a trigger to insert a row in table B, when A.Name changes. so far, i have:
CREATE TRIGGER NameUpdate
AFTER UPDATE OF Name
ON A
FOR EACH ROW
BEGIN
INSERT INTO B(
UserID,
Timestamp
)
VALUES (
xxxxxxx,
DateTime('now')
);
END;
XXXXXXX should be A.UserID that just changed.
You can refer to the before/after values (according to relevance i.e. only new for inserts, old or new for updates and only old for deletes) using a prefix of old. or new. respectively.
Try :-
CREATE TRIGGER NameUpdate AFTER UPDATE OF Name ON A FOR EACH ROW
BEGIN
INSERT INTO B( UserID, Timestamp ) VALUES ( new.UserID, DateTime('now') );
END
;
Or (as the UserID column hasn't changed) :-
CREATE TRIGGER NameUpdate AFTER UPDATE OF Name ON A FOR EACH ROW
BEGIN
INSERT INTO B( UserID, Timestamp ) VALUES ( old.UserID, DateTime('now') );
END
;

trigger to delete a record before insert

i have a table with 4 columns
1.msisdn
2.accountnumber
3.cardnumber
4.subscriptiondate
I want to add a trigger to this table. If the data i am inserting is
1.99999999
2.2
3.3298572857239
4.(this can be blank)
and the data that is currently in the table is
1.99999999
2.1
3.3298572857239
4.(this can be blank)
Trigger should check if there is this msisdn 99999999 is already having a record with this cardnumber 3298572857239. If there is a record already existing in the table, the trigger should delete the existing entry and insert the new one. The final result should look like this
1.99999999
2.1
3.3298572857239
4.(this can be blank)
I want to keep the value of accountnumber same before and after the trigger. This is what i have tried so far but for this trigger, i am not getting any data in accountnumber column. Please someone help
DROP TRIGGER TRIG_TABLEA;
CREATE OR REPLACE TRIGGER TRIG_TABLEA
BEFORE INSERT ON TABLEA
REFERENCING OLD AS Old NEW AS New
FOR EACH ROW
BEGIN
:new.accountnumber := :old.accountnumber;
DELETE FROM TABLEA WHERE MSISDN = :new.MSISDN AND CARDNUMBER = :new.CARDNUMBER;
:new.MSISDN := :new.MSISDN;
:new.CARDNUMBER := :new.CARDNUMBER;
:new.accountnumber := :old.accountnumber;
END;
/
Don't do a delete-and-insert. You want MERGE. The only thing that can change in your statement is accountnumber and subscriptiondate. You don't say where the data is coming from, so I assume this is a PL/SQL procedure with p_* as the parameters. So you want something like this:
MERGE INTO mytable trg
USING ( SELECT p_accountnumber, p_subscriptiondate FROM dual ) src
ON ( trg.msisdn = p_msisdn AND trg.cardnumber )
WHEN NOT MATCHED INSERT ( msisdn, accountnumber, cardnumber, subscriptiondate )
VALUES ( p_msisdn, p_accountnumber, p_cardnumber, p_subscriptiondate )
WHEN MATCHED SET ( cardnumber = p_cardnumber, subscriptiondate = p_subscriptiondate)
This will do an insert if the row doesn't exist or update an existing row if it does.

Resources