Trigger in oracle - triggers

account
----------
accno varchar
acctotal number
student
----------
id varchar
fees number
accno varchar
I want to create a trigger which updates 'account' table anytime a record is deleted from 'student'.
It subtracts fees of student from acctotal in account.
thnx in advance.

Try this
CREATE OR REPLACE TRIGGER account_after_delete
AFTER DELETE
ON student
FOR EACH ROW
BEGIN
UPDATE account SET acctotal=acctotal -:old.fees
WHERE accno=:old.accno;
END;

Related

Trigger and function to insert user id into another table

I am using Prisma as my schema and migrating it to supabase with prisma migrate dev
One of my tables Profiles, should reference the auth.users table in supabase, in sql something like this id uuid references auth.users not null,
Now since that table is automatically created in supabase do I still add it to my prisma schema? It's not in public either it is in auth.
model Profiles {
id String #id #db.Uuid
role String
subId String
stripeCustomerId String
refundId String[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
The reason I want the relation is because I want a trigger to automatically run a function that inserts an id and role into the profiles table when a new users is invited.
This is that trigger and function
-- inserts a row into public.profiles
create function public.handle_new_user()
returns trigger
language plpgsql
security definer
as $$
begin
insert into public.Profiles (id, role)
values (new.id, 'BASE_USER');
return new;
end;
$$;
-- trigger the function every time a user is created
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
I had this working when I created the profiles table manually in supabase I included the reference to the auth.users, that's the only reason I can think of why the user Id and role won't insert into the profiles db when I invite a user, the trigger and function are failing
create table public.Profiles (
id uuid references auth.users not null,
role text,
primary key (id)
);
Update from comment:
One error I found is
relation "public.profiles" does not exist
I change it to "public.Profiles" with a capital in supabase, but the function seem to still be looking for lowercase.
What you show should just work:
db<>fiddle here
Looks like you messed up capitalization with Postgres identifiers.
If you (or your ORM) created the table as "Profiles" (with double-quotes), non-standard capitalization is preserved and you need to double-quote the name for the rest of its life.
So the trigger function body must read:
...
insert into public."Profiles" (id, role) -- with double-quotes
...
Note that schema and table (and column) have to be quoted separately.
See:
Are PostgreSQL column names case-sensitive?

Postgres: Ignore DELETE triggers for cascade deletes

I am trying to implement a relation of persons to email addresses where a person must have at least one email address at all times. The tables look like this:
CREATE TABLE persons (
id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE email_addresses (
id serial PRIMARY KEY,
person_id integer REFERENCES persons (id) ON DELETE CASCADE ON UPDATE CASCADE,
email_address text NOT NULL
);
In order to implement the constraint that a person must have at least one email address, I thought I'd use triggers. One of the triggers necessary to satisfy the constraint is a BEFORE DELETE trigger on the email_addresses table that raises an error if the DELETE would remove the last email address for a person:
CREATE FUNCTION email_addresses_delete_trigger() RETURNS trigger AS $$
DECLARE
num_email_addresses integer;
BEGIN
num_email_addresses := (SELECT count(*) FROM email_addresses WHERE person_id = OLD.person_id);
IF num_email_addresses < 2 THEN
RAISE EXCEPTION 'A person must have at least one email address';
END IF;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER email_addresses_delete_trigger BEFORE DELETE ON email_addresses
FOR EACH ROW EXECUTE FUNCTION email_addresses_delete_trigger();
This trigger does what it is meant to do, however it prevents deletes of a person from the persons table. For example:
mydb=# DELETE FROM persons WHERE id = 1;
ERROR: A person must have at least one email address
CONTEXT: PL/pgSQL function email_addresses_delete_trigger() line 7 at RAISE
SQL statement "DELETE FROM ONLY "public"."email_addresses" WHERE $1 OPERATOR(pg_catalog.=) "person_id""
If I am deleting a person, then I want all their email addresses deleted too, and so I don't care if the constraint represented by the trigger is maintained during the process of the cascade delete. Is there a way to "ignore" this trigger when a person is deleted? Or is there some other way that I would need to delete a person?
My recommendation is to change the data model so that you have a not nullable foreign key constraint from persons to email_addresses that links to one of the addresses of the person. Then your requirement is automatically fulfilled, and you don't need a trigger.
That will make some things like deleting an e-mail address more complicated, but you don't have to rely on a trigger for integrity, which is always subject to race conditions.

Immutability in Postgres

I want to create an immutable Postgres database, where the user can insert & select (write & read) data, but cannot update or delete the data.
I am aware of the FOR UPDATE lock, but I don't understand how to use it.
Let's say for example I have the table below, how can I make it immutable (or, if I understood correctly, how can I use the FOR UPDATE lock permanently)
CREATE TABLE account(
user_id serial PRIMARY KEY,
username VARCHAR (50) UNIQUE NOT NULL,
password VARCHAR (50) NOT NULL,
email VARCHAR (355) UNIQUE NOT NULL,
created_on TIMESTAMP NOT NULL,
last_login TIMESTAMP
);
The solution is to give the user that accesses the database only the INSERT and SELECT privilege on the tables involved.
A lock is not a tool to deny somebody access, but a short-time barrier to prevent conflicting data modifications to happen at the same time.
Here is an example:
CREATE TABLE sensitive (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
available text,
restricted text
);
Now I want to allow someuser to insert data and read and update all columns except restricted, and I want to keep myself from deleting data in that table:
/* the CREATE TABLE above was run by user "laurenz" */
REVOKE DELETE ON sensitive FROM laurenz;
GRANT INSERT ON sensitive TO someuser;
GRANT SELECT (id, available), UPDATE (id, available) ON sensitive TO someuser;
Nope, that 👆🏼 solution doesn't work. I found this one. I make a before trigger on the table on update for each row:
create or replace function table_update_guard() returns trigger
language plpgsql immutable parallel safe cost 1 as $body$
begin
raise exception
'trigger %: updating is prohibited for %.%',
tg_name, tg_table_schema, tg_table_name
using errcode = 'restrict_violation';
return null;
end;
$body$;
create or replace trigger account_update_guard
before update on account for each row
execute function table_update_guard();
See my original research.

Is it possible to pass data to postgreSQL trigger?

I need to log any changes made in some table by trigger which will insert older version of modified row to another table with some additional data like:
-which action was performed
-when this action was performed
-by who.
I have problem with last requirement. While performing SQL somewhere in java by JDBC. I need to somehow pass logged user id stored in variable to postgres table where all older versions of modified row will be stored.
Is it even possible?
It may be stupid question but I desperately try to avoid inserting data like that manually in java. Triggers done some work for me but not all I need.
Demonstrative code below (I've cut out some code for security reasons):
"notes" table:
CREATE TABLE my_database.notes
(
pk serial NOT NULL,
client_pk integer,
description text,
CONSTRAINT notes_pkey PRIMARY KEY (pk)
)
Table storing older versions of every row changed in "notes" table:
CREATE TABLE my_database_log.notes_log
(
pk serial NOT NULL,
note_pk integer,
client_pk integer,
description text,
who_changed integer DEFAULT 0, -- how to fill in this field?
action_date timestamp without time zone DEFAULT now(), --when action was performed
action character varying, --which action was performed
CONSTRAINT notes_log_pkey PRIMARY KEY (pk)
)
Trigger for "notes" table:
CREATE TRIGGER after_insert_or_update_note_trigger
AFTER INSERT OR UPDATE
ON database.notes
FOR EACH ROW
EXECUTE PROCEDURE my_database.notes_new_row_log();
Procedure executed by trigger:
CREATE OR REPLACE FUNCTION my_database.notes_new_row_log()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO my_database_log.notes_log(
note_pk, client_pk, description, action)
VALUES (
NEW.pk, NEW.client_pk, NEW.description, TG_OP);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION my_database.notes_new_row_log()
OWNER TO database_owner;
According to #Nick Barnes hint in comments, there is a need to declare a variable in postgresql.conf file:
...
#----------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#----------------------------------------------------------------------------
custom_variable_classes = 'myapp' # list of custom variable class names
myapp.user_id = 0
and call:
SET LOCAL customvar.user_id=<set_user_id_value_here>
before query that should be triggered.
To handle variable in trigger use:
current_setting('myapp.userid')

Postgresql 9.2 trigger to log changes to data in another table

I have several tables and want to log when changes are made to them, what the change was and who made the change. Postgresql 9.2
CREATE TABLE unitsref (
unitsrefid serial primary key,
units varchar,
unitname varchar,
inuse boolean,
systemuse varchar,
keynotes integer,
linkid integer
);
Is the best practise to use OLD.* IS DISTINCT FROM NEW.* ?
CREATE TRIGGER log_unitsref
AFTER UPDATE ON unitsref
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE log_unitsref();
I am only really interested in the three fields:
units varchar,
unitname varchar,
inuse boolean,
I want to record these changes in a table eventlog with the fields:
recordtype varchar,
recordkey varchar,
changetype varchar,
personid integer,
changedate date,
changetime time,
changefrom varchar,
changeto varchar,
What is the best syntax to write a function to do this?
In Progress Openedge I would write
create EventLog.
assign EventLog.PersonId = glb-Personid
EventLog.RecordType = "UnitsRef"
EventLog.RecordKey = UnitsRef.Units
EventLog.ChangeType = "Create"
EventLog.changeFrom = ""
EventLog.changeTo = ""
EventLog.changeDate = today
EventLog.changeTime = time
but I don`t know the best method in Postgresql
I am only really interested in the three fields
Then it should be more efficient to only call the trigger after changes to these fields:
CREATE TRIGGER log_unitsref
AFTER UPDATE OF units, unitname, inuse
ON unitsref
FOR EACH ROW
WHEN (OLD.units, OLD.unitname, OLD.inuse) IS DISTINCT FROM
(NEW.units, NEW.unitname, NEW.inuse)
EXECUTE PROCEDURE log_unitsref();
I quote the manual on CREATE TRIGGER:
UPDATE OF ...
The trigger will only fire if at least one of the listed columns is
mentioned as a target of the UPDATE command.
WHEN ...
A Boolean expression that determines whether the trigger function will
actually be executed.
Note that these two elements are closely related but neither mutually exclusive nor redundant.
It is much cheaper not to fire the trigger at all, if no column of interest is involved.
It is much cheaper not to execute the trigger function if no column of interest was actually altered.
Related answers here or here ...