Create a trigger after insert that update another table - sql

I'm trying to create a trigger that update another table after an insert, when the state of the swab test changes from positive to negative.
I have created this trigger, but the problem is that every time there is a user with a negative swab, the user id is copied to the table, even if this user has never been positive. Maybe, have I to compare date?
Create or replace trigger trigger_healed
After insert on swab_test
For each row
Begin
if :new.result = 'Negative' then
UPDATE illness_update
SET illness_update.state = 'healed'
WHERE illness_update.id_user = :new.id_user;
end if;
end;
This is the result that I'm trying to get.
SWAB_TEST
id_user id_swab swab_result date
1 test1 'positive' May-01-2020
1 test1 'negative' May-08-2020
2 test2 'negative' May-02-2020
ILLNESS_UPDATE
id_user state date
1 'healed' May-08-2020

What you ask for would require the trigger to look at the existing rows in the table that is being inserted on - which by default cannot be done, since a trigger cannot action the table it fires upon.
Instead of trying to work around that, I would suggest simply creating a view to generate the result that you want. This gives you an always up-to-date perspective at your data without any maintenance cost:
create view illness_update_view(id_user, state, date) as
select id_user, 'healed', date
from (
select
s.*,
lag(swab_result) over(partition by id_user order by date) lag_swab_result
from swab_test s
) s
where lag_swab_result = 'positive' and swab_result = 'negative'
The view uses window function lag() to recover the "previous" result of each row (per user). Rows that represents transitions from a positive to a negative result are retained.

As #GMB indicates you cannot do what you are asking with a standard before/after row trigger as it that cannot reference swab_test as that is the table causing the trigger to fire (that would result in an ORA-04091 mutating table error). But you can do this in a Compound Trigger (or an After statement). But before getting to that I think your data model has a fatal flaw.
You have established the capability for multiple swab tests. A logical extension for this being that each id_swab tests for a different condition, or a different test for the same condition. However, the test (id_swab) is not in your illness update table. This means if any test goes to negative result after having a prior positive result the user is healed from ALL tests. To correct this you need to a include id_swab id making the healed determination. Since GMB offers the best solution I'll expand upon that. First drop the table Illness_update. Then create Illness_update as a view. (NOTE: in answer to your question you DO NOT need a trigger for the view, everything necessary is in the swab_test; see lag windowed function.
create view illness_update(id_user, state, swab_date) as
select id_user, id_swab, 'healed' state,swab_date
from (
select
s.*
, lag(swab_result) over(partition by id_user, id_swab
order by id_user, id_swab, swab_date) as lag_swab_result
from swab_test s
) s
where lag_swab_result = 'positive'
and swab_result = 'negative';
Now, as mentioned above, if your assignment requires the use of a trigger then see fiddle. Note: I do not use date (or any data type) as a column name. Here I use swab_date in all instances.

Related

Trigger keeps updating value in every row instead of particular one

I am trying to calculate a sum for each particular order. I am using this trigger but it doesn't work properly, it updates every row with the same value instead of the only one with proper id.
done_services table
id
service_id
price
service table
id
name
payment table
id
sum
service_id
CREATE FUNCTION make_sum() RETURNS TRIGGER
AS $$
BEGIN
UPDATE payment
SET sum = (select sum(price) from done_services where service_id = new.service_id);
RETURN NULL;
END;$$ LANGUAGE plpgsql;
CREATE TRIGGER make_sum
AFTER INSERT ON basket FOR EACH ROW EXECUTE FUNCTION make_sum();
I used this command to enter an item
insert into done_services(id, service_id, price) values(uuid_generate_v4(), '76594d2f-7153-495f-9671-0ddaa331568c', 100);
But the sum changed for both rows instead of the only one with service id
Image
The immediate cause for the error message is the missing WHERE clause as instructed by Edouard. Plus, prevent expensive empty updates like:
UPDATE payment p
SET sum = ds.sum_price
FROM (
SELECT sum(d.price) AS sum_price
FROM done_services d
WHERE d.service_id = NEW.service_id
) ds
WHERE p.service_id = sum_price
AND p.sum IS DISTINCT FROM ds.sum_price;
In addition to fixing the prime error, this prevents empty updates that would not change the sum, but still write a new row version at full cost.
But the whole idea is questionable.
Keeping a sum from many rows up to date via trigger is expensive and error prone. Did you cover DELETE and INSERT accordingly? What about TRUNCATE? What about concurrent write access? Race conditions, deadlocks?
To get get the current sum for a set that can change dynamically, the superior solution is typically not to save that sum in the table at all. Use a VIEW or MATERIALIZED VIEW instead.
Or, to get the sum for a single or few payments, use a function:
CREATE OR REPLACE FUNCTION f_payment_sum(_service_id int)
RETURNS numeric
LANGUAGE sql STABLE PARALLEL SAFE AS
$func$
SELECT sum(d.price)
FROM done_services d
WHERE d.service_id = _service_id;
$func$
Related:
Updating a row based on a value from another table?
just missing something in your UPDATE statement :
UPDATE payment
SET sum = (select sum(price) from done_services where service_id = new.service_id)
WHERE service_id = new.service_id ;
Next time please create a dbfiddle with your data model, sample of data and queries.

Explicitly set ROWNUM in column

I'm trying to split what was a large table update into multiple inserts into working tables. One of the queries needs uses the row number in it. On an INSERT in oracle, can I explicitly add the ROWNUM as an explicit column? This is a working table ultimately used in a reporting operation with a nasty partion over clause and having a true row number is helpful.
create table MY_TABLE(KEY number,SOMEVAL varchar2(30),EXPLICIT_ROW_NUMBER NUMBER);
INSERT /*+PARALLEL(AUTO) */ INTO MY_TABLE(KEY,SOMEVAL,EXPLICIT_ROW_NUMBER) (
SELECT /*+PARALLEL(AUTO) */ KEY,SOMEVAL,ROWNUM
FROM PREVIOUS_VERSION_OF_MY_TABLE
);
where PREVIOUS_VERSION_OF_MY_TABLE has both a KEY and SOMEVAL fields.
I'd like it to number the rows in the order that the inner select statement does it. So, the first row in the select, had it been explicitly run, would have a ROWNUM of 1, etc. I don't want it reversed, etc.
The table above has over 80MM records. Originally I used an UPDATE, and when I ran it, I got some ORA error saying that I ran out of UNDO space. I do not have the exact error message at this point anymore.
I'm trying to accomplish the same thing with multiple working tables that I would have done with one or more updates. Apparently it is either hard, impossible, etc to add UNDO space, for this query (our company DB team says), without making me a DBA, or spending about $100 on a hard drive and attaching it to the instance. So I need to write a harder query to get around this limitation. The goal is to have a session id and timestamps within that session, but for each timestamp within a session (except the last timestamp), show the next session. The original query is included below:
update sc_hub_session_activity schat
set session_time_stamp_rank = (
select /*+parallel(AUTO) */ order_number
from (
select /*+parallel(AUTO) */ schat_all.explicit_row_number as explicit_row_number,row_number() over (partition by schat_all.session_key order by schat_all.session_key,schat_all.time_stamp) as order_number
from sc_hub_session_activity schat_all
where schat_all.session_key=schat.session_key
) schat_all_group
where schat.explicit_row_number = schat_all_group.explicit_row_number
);
commit;
update sc_hub_session_activity schat
set session_next_time_stamp = (
select /*+parallel(AUTO) */ time_stamp
from sc_hub_session_activity schat2
where (schat2.session_time_stamp_rank = schat.session_time_stamp_rank+1) and (schat2.session_key = schat.session_key)
);
commit;

SQL Transaction Set Temporary Value

I am relatively new to databases and SQL, and I am not clear on how or whether transactions may relate to a problem that I am trying to solve. I want to be able to temporarily set a value in a database table, run some query, and then clear out the value that was set, and I don't want any operations outside of the transaction to be able to see or alter the temporary value that was set.
The reason I am doing this is so that I can create predefined views that query certain data depending on variables such as the current user's id. In order for the predefined view to have access to the current user's id, I would save the id into a special table just before querying the view, then delete the id immediately afterward. I don't want to worry about some other user overwriting the current user's id while the transaction is in process. Is this a proper use for a transaction?
I am using H2 if that makes a difference.
SET #tempVar=value;
I don't know if you really need to go through the pain of creating a temp table and setting the value. This seems far simpler.
You can then do - SELECT * FROM TABLE_NAME WHERE COLUMN=#tempVar;
I think you want a Procedure or Function. Both can take a parameter as input.
ex.
CREATE PROCEDURE pr_emp
(
#input INT
)
AS
SELECT *
FROM myTable
WHERE emp_id = #input
ex.
CREATE FUNCTION v_empid (#input INT)
RETURNS TABLE
AS
RETURN
SELECT * FROM myTABLE WHERE emp_id = #input;
These could let you to access information for an empid. For example:
SELECT * FROM v_empid(32)

How can I create constraint using subquery in sql?

Here is my table game :
create table game (
h_team number,
a_team number,
p_date date
);
Condition to be followed: Every team plays a single game on a particular date. Basically normal rules that usually should happen for a tournament.
I have added following constraints:
I want to add another constraints which restricts to add what the following queries performs:
select h_team, p_date
from game
where (h_team,p_date) not in (select a_team,p_date from game);
select a_team, p_date
from game
where (a_team,p_date) not in (select h_team,p_date from game);
For example,Suppose a record in that table is (1,2,23-JAN-2000). So records like (3,1,23-JAN-2000), (2,4,23-JAN-2000) etc. cannot be inserted. Thanks!
I preferred in SQl but it seems it is not possible in SQL. So How will it be using PL-SQL.
SQL Assertions
The feature you're looking for is called SQL assertions, and it's not yet implemented in Oracle 12c. Meanwhile, use a trigger, as you've suggested yourself.
Your trigger
Of course, your trigger doesn't work because its syntax is quite wrong.
CREATE TRIGGER xx_game_trigger
BEFORE INSERT -- This clause
ON xx_game -- before this one
REFERENCING NEW AS new -- You'll probably need this
FOR EACH ROW
BEGIN
-- There's no such thing as IF EXISTS in PL/SQL. Here's a workaround. This loop will run
-- zero or one times.
FOR rec IN (
SELECT 1 FROM dual
WHERE EXISTS (
-- I'm assuming that you're interested in matches between existing records
-- And the record you're about to insert (:new.xxx). Adapt accordingly
SELECT 1 FROM xx_game WHERE (home_team,play_date) IN (:new.away_team,:new.play_date)
)
OR EXISTS (
SELECT 1 FROM xx_game WHERE (away_team,play_date) IN (:new.home_team,:new.play_date)
)
)
LOOP
-- There's no TRANSACTION keyword here. But anyway, I'd rather raise an exception
-- than roll back the transaction. That seems much cleaner to me.
ROLLBACK;
END LOOP;
END xx_game_trigger;
Please consider the Oracle documentation for the complete CREATE TRIGGER syntax

A trigger that inserts several rows instead of one

We have an issue with the following trigger. We would like to insert a row into the UPDATEPROCESSINFO table when there is no row with the new INSTANCEID and update it for the next ones.
But we were surprised to discover that sometimes we have multiple rows with the same INSTANCEID. Is it because it was very fast? How to prevent this from happening? Our aim is to have one row per INSTANCEID.
Thanks for help
create or replace TRIGGER TRIG_UPDATE_PROCESS_INFO
AFTER INSERT ON PROCESSSTEP
FOR EACH ROW
DECLARE
AUDIT_TIME TIMESTAMP(6);
BEGIN
SELECT MAX(LASTUPDATETIME)
INTO AUDIT_TIME
FROM UPDATEPROCESSINFO
WHERE INSTANCEID = :NEW.INSTANCEID;
IF AUDIT_TIME IS NULL THEN
INSERT INTO UPDATEPROCESSINFO
(INSTANCEID, STEPID, STEPSTATUS, STEPITERATION, LASTUPDATETIME)
VALUES
(:NEW.INSTANCEID, :NEW.STEPID, :NEW.STATUS, :NEW.STEPITERATION, :NEW.AUDITTIMESTAMP);
ELSIF :NEW.AUDITTIMESTAMP > AUDIT_TIME THEN
UPDATE UPDATEPROCESSINFO
SET STEPID = :NEW.STEPID,
LASTUPDATETIME = :NEW.AUDITTIMESTAMP,
STEPSTATUS = :NEW.STATUS,
STEPITERATION = :NEW.STEPITERATION
WHERE INSTANCEID = :NEW.INSTANCEID;
END IF;
END;
This may be occurring because you have multiple sessions which are inserting into PROCESSSTEP for the same INSTANCEID. If two of the sessions insert into PROCESSSTEP at nearly the same time, and neither of them has committed their changes, then neither session will "see" the other's changes, and both will think that a row does not exist in UPDATEPROCESSINFO.
In my view this design appears to have a problem. I suggest changing it to have a PROCESS_STEP_HISTORY table, and as each step in the process is completed a row is inserted into PROCESS_STEP_HISTORY to record the information for the process step that was completed. Then, when something needed to find out information about the "last" step which was completed it would just do something like
SELECT a.*
FROM (SELECT *
FROM PROCESS_STEP_HISTORY h
WHERE INSTANCE_ID = whatever
ORDER BY LASTUPDATETIME DESC) a
WHERE ROWNUM = 1
It also has the advantage of preserving information about every step in the process, which may prove useful.
I also don't recommend using a trigger to do this sort of thing. This is business logic, and putting business logic into triggers is never a good idea.
Best of luck.