Using sqlite I want to have a field 'url' generated from the given 'id' using a trigger both on insert and on update. For example id: '1' => url: 'test.com/1'
The table looks like this:
CREATE TABLE t1 (
id TEXT,
url TEXT
);
Since sqlite can't run the same trigger for update and insert, I see two options to accomplish this.
Option A
run a trigger after insert, that updates the id to itself, which in turns triggers the update trigger
CREATE TRIGGER run_updates_on_insert AFTER INSERT ON t1
BEGIN
UPDATE t1 SET id = NEW.id WHERE id = NEW.id;
END;
CREATE TRIGGER set_url_on_update BEFORE UPDATE on t1
BEGIN
UPDATE t1 SET url = 'test.com/' || NEW.id WHERE id = OLD.id;
END;
Option B
replicating the logic in two separate triggers for update and insert
CREATE TRIGGER set_url_on_insert AFTER INSERT on t1
BEGIN
UPDATE t1 SET url = 'test.com/' || NEW.id WHERE id = NEW.id;
END;
CREATE TRIGGER set_url_on_update BEFORE UPDATE on t1
BEGIN
UPDATE t1 SET url = 'test.com/' || NEW.id WHERE id = OLD.id;
END;
Both of these options give me the desired results, I tend to favor Option A, as I only have to write the update logic once, but I was wondering if there are any other advantages/disadvantage to prefer one to the other?
EDIT: For this particular use case it is better to use generated column (see forpas answer below)
Since version 3.31.0 (2020-01-22) of SQLite, you can create generated columns (stored or virtual), so you don't need any triggers:
CREATE TABLE t1 (
id TEXT,
url TEXT GENERATED ALWAYS AS ('test.com/' || id) STORED
);
Related
I've found few questions addressing the same question but without a better solution.
I need to create an Oracle trigger which will prevent new inserts upon a condition, but silently (without raising an error).
Ex : I need to stop inserting rows with bar='FOO' only. (I can't edit the constraints of the table, can't access the procedure which really does the insertion etc so the trigger is the only option)
Solutions so far confirms that it isn't possible. One promising suggestion was to create an intermediate table, insert key values to that when bar='FOO' and then delete those records from original table once insertion is done, which is not correct I guess.
Any answer will be highly appreciated.
Apparently, it is not possible to use a trigger to stop inserts without raising an exception.
However, if you have access to the schema (and asking about a trigger this is probably ok), you could think about replacing the table with a view and an instead of trigger.
As a minimal mock up for your current table. myrole is just a stand in for the privileges granted on the table:
CREATE ROLE myrole;
CREATE TABLE mytable (
bar VARCHAR2(30)
);
GRANT ALL ON mytable TO myrole;
Now you rename the table and make sure nobody can directly access it anymore, and replace it with a view. This view can be protected by a instead of trigger:
REVOKE ALL ON mytable FROM myrole;
RENAME mytable TO myrealtable;
CREATE OR REPLACE VIEW mytable AS SELECT * FROM myrealtable;
GRANT ALL ON mytable TO myrole;
CREATE OR REPLACE TRIGGER myioftrigger
INSTEAD OF INSERT ON mytable
FOR EACH ROW
BEGIN
IF :new.bar = 'FOO' THEN
NULL;
ELSE
INSERT INTO myrealtable(bar) VALUES (:new.bar);
END IF;
END;
/
So, if somebody is inserting a normal row into the fake view, the data gets inserted into your real table:
INSERT INTO mytable(bar) VALUES('OK');
1 row inserted.
SELECT * FROM mytable;
OK
But if somebody is inserting the magic value 'FOO', the trigger silently swallows it and nothing gets changed in the real table:
INSERT INTO mytable(bar) VALUES('FOO');
1 row inserted.
SELECT * FROM mytable;
OK
Caution: If you want to protect your table from UPDATEs as well, you'd have to add a second trigger for the updates.
One way would be to hide the row. From 12c this is reasonably easy:
create table demo
( id integer primary key
, bar varchar2(10) );
-- This adds a hidden column and registers the table for in-database archiving:
alter table demo row archival;
-- Set the hidden column to '1' when BAR='FOO', else '0':
create or replace trigger demo_hide_foo_trg
before insert or update on demo
for each row
begin
if :new.bar = 'FOO' then
:new.ora_archive_state := '1';
else
:new.ora_archive_state := '0';
end if;
end demo_hide_foo_trg;
/
-- Enable in-database archiving for the session
-- (probably you could set this in a log-on trigger):
alter session set row archival visibility = active;
insert into demo (id, bar) values (1, 'ABC');
insert into demo (id, bar) values (2, 'FOO');
insert into demo (id, bar) values (3, 'XYZ');
commit;
select * from demo;
ID BAR
-------- --------
1 ABC
3 XYZ
-- If you want to see all rows (e.g. to delete hidden rows):
alter session set row archival visibility = all;
In earlier versions of Oracle, you could achieve the same thing using a security policy.
Another way might be to add a 'required' flag which defaults to 'Y' and set it to to 'N' in a trigger when bar = 'FOO', and (assuming you can't change the application to use a view etc) have a second trigger delete all such rows (or perhaps better, move them to an archive table).
create table demo
( id integer primary key
, bar varchar2(10) );
alter table demo add required_yn varchar2(1) default on null 'Y';
create or replace trigger demo_set_not_required_trg
before insert or update on demo
for each row
begin
if :new.bar = 'FOO' then
:new.required_yn := 'N';
end if;
end demo_hide_foo_trg;
/
create or replace trigger demo_delete_not_required_trg
after insert or update on demo
begin
delete demo where required_yn = 'N';
end demo_delete_not_required_trg;
/
I want a trigger that updates the value of a column, but I just want to update a small set of rows that depends of the values of the inserted row.
My trigger is:
CREATE OR REPLACE TRIGGER example
AFTER INSERT ON table1
FOR EACH ROW
BEGIN
UPDATE table1 t
SET column2 = 3
WHERE t.column1 = :new.column1;
END;
/
But as I using FOR EACH ROW I have a problem when I try it, I get the mutating table runtime error.
Other option is not to set the FOR EACH ROW, but if I do this, I dont know the inserted "column1" for comparing (or I dont know how to known it).
What can I do for UPDATING a set of rows that depends of the last inserted row?
I am using Oracle 9.
You should avoid the DML statements on the same table as defined in a trigger. Use before DML to change values of the current table.
create or replace trigger example
before insert on table1
for each row
begin
:new.column2 := 3;
end;
/
You can modify the same table with pragma autonomous_transaction:
create or replace trigger example
after insert on table1 for each row
declare
procedure setValues(key number) is
pragma autonomous_transaction;
begin
update table1 t
set column2 = 3
where t.column1 = key
;
end setValues;
begin
setValues(:new.column1);
end;
/
But I suggest you follow #GordonLinoff answere to your question - it's a bad idea to modify the same table in the trigger body.
See also here
If you need to update multiple rows in table1 when you are updating one row, then you would seem to have a problem with the data model.
This need suggests that you need a separate table with one row per column1. You can then fetch the value in that table using join. The trigger will then be updating another table, so there will be no mutation problem.
`create table A
(
a INTEGER,
b CHAR(10)
);
create table B
(
b CHAR (10),
d INTEGER
);
create trigger trig1
AFTER INSERT ON A
REFERENCING NEW AS newROW
FOR EACH ROW
when(newROW.a<=10)
BEGIN
INSERT into B values(:newROW.b,:newROW.a);
END trig1;
insert into A values(11,'Gananjay');
insert into A values(5,'Hritik');
select * from A;
select * from B;`
I am trying to create a simple table inheritance hierarchy from my entity relationship model in PostgreSQL.
For this, I have created the following tables:
CREATE TABLE base (
id integer PRIMARY KEY,
title varchar(40),
test integer
);
CREATE TABLE sub (
id integer REFERENCES base(id) PRIMARY KEY,
count integer
);
Now, since how the inheritance is solved on the DB does not need to concern the application, I also created a view that the application will access. All operations from the application should be performed on the view, and as such, I also need an instead of trigger to perform updates, inserts and deletes. The view definition and the trigger looks like this:
CREATE VIEW sub_view AS
SELECT s.count, b.title, b.test, b.id FROM sub s
JOIN base b ON b.id = s.id;
--TRIGGER
CREATE OR REPLACE FUNCTION instead_of_f()
RETURNS trigger AS
$$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO base(id, title, test) VALUES (NEW.id, NEW.title, NEW.test);
INSERT INTO sub(id, test) VALUES (NEW.id, NEW.test);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE base SET title = NEW.title, test = NEW.test WHERE id = OLD.id;
UPDATE sub SET count = NEW.count WHERE id = OLD.id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
DELETE FROM sub WHERE id = OLD.id;
DELETE FROM base WHERE id = OLD.id;
RETURN NULL;
END IF;
END;
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER instead_of_dml_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON
sub_view FOR EACH ROW
EXECUTE PROCEDURE instead_of_f();
This basically works fine, but it is tedious and not very maintainable to have to repeat all the column names over and over again. Ideally, I would like to write something like this for the view instead:
CREATE VIEW sub_view AS
SELECT * FROM sub s JOIN base b ON b.id = s.id;
And instead of the insert statements in the trigger:
INSERT INTO base VALUES NEW.*;
INSERT INTO sub VALUES NEW.*;
Is this somehow possible? I couldn't find anything similar, except for audit triggers, and those just saved the NEW and OLD records as a string. In this contrived example it would be easy enough to add new columns or delete them as the base/sub tables change, but as soon as there are a few more sub tables and more columns this becomes practically unmaintainable.
Like Craig suggested, I ended up solving it in my application directly, not on the database. My application knows all the relevant columns from the views, as well as the base types, so it ended up being easier to just create the trigger and the view in the migrations there.
Yii::app()->db->createCommand("DROP TRIGGER IF EXISTS update_todo;")->execute();
Yii::app()->db->createCommand("CREATE TRIGGER update_todo AFTER DELETE ON user_todo_send FOR EACH ROW BEGIN "
. " UPDATE todo SET status = 1 WHERE id = OLD.id_todo; END;")->execute();
In response, I receive an error :
Can't update table 'todo' in stored function/trigger because it is
already used by statement which invoked this stored function/trigger..
I am going to guess that you are really using MySQL. Use an after delete trigger. Using your syntax, that would look like this (and I assume you have the right delimiter statements:
DROP TRIGGER IF EXISTS `update_todo`;
CREATE TRIGGER `update_todo` AFTER DELETE ON `user_todo_send` FOR EACH ROW
BEGIN
UPDATE `todo`
SET status = 1
WHERE id IN (SELECT OLD.id_todo
FROM user_todo_send
WHERE OLD.id_todo = todo.id
);
END;
This is really simpler to write as:
DROP TRIGGER IF EXISTS `update_todo`;
CREATE TRIGGER `update_todo` AFTER DELETE ON `user_todo_send` FOR EACH ROW
BEGIN
UPDATE `todo`
SET status = 1
WHERE id = OLD.id_todo
END;
Or, forget triggers altogether and add a cascading delete foreign key reference.
attempting to maintain an edit log using rules.
create table t1(
id serial primary key,
c1 text,
... );
create table edit_log(
id int references t1,
editor_id int references users,
edit_ts timestamp default current_timestamp );
with an update, wish to update t1 and insert into edit_lot
update t1 set c1='abc', ... where id=456;
insert into edit_log( id, editor_id, current_timestamp );
this would be a pretty straightforward except for the arbitrary number of columns, eg,
update t1 set c1='abc', c2='def', editor_id=123 where id=456;
update t1 set c3='xyz', editor_id=123 where id=456;
how to write a rule for that?
I think a trigger will serve you better than a rule. Consider this demo.
Test setup
CREATE TEMP TABLE t1(id int, editor_id int, c1 text);
INSERT INTO t1(id, editor_id) VALUES (1,1),(2,2);
CREATE TEMP TABLE edit_log(id int, editor_id int, edit_ts timestamp);
Create trigger function
CREATE OR REPLACE FUNCTION trg_t1_upaft_log()
RETURNS trigger AS
$BODY$
BEGIN
IF OLD IS DISTINCT FROM NEW THEN -- to avoid empty updates
INSERT INTO edit_log(id, editor_id, edit_ts)
VALUES(NEW.id, NEW.editor_id, now()::timestamp);
END IF;
RETURN NULL; -- trigger will be fired AFTER updates, return value is irrelevant.
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Create trigger
CREATE TRIGGER upaft_log
AFTER UPDATE ON t1
FOR EACH ROW
EXECUTE PROCEDURE trg_t1_upaft_log();
Test
UPDATE t1 SET c1 = 'baz' WHERE id = 1;
SELECT * FROM edit_log; -- 1 new entry
UPDATE t1 SET c1 = 'baz' WHERE id = 1;
SELECT * FROM edit_log; -- no new entry, update changed nothing!
UPDATE t1 SET c1 = 'blarg';
SELECT * FROM edit_log; -- 2 new entries, update changed two rows.
Cleanup
DROP TRIGGER upaft_log ON t1;
DROP FUNCTION trg_t1_upaft_log()
-- Temp. tables will be dropped automatically at end of session.
Comment
It is very hard or plain impossible (depending on the details of your setup) for a rule to figure out which rows are updated.
A trigger AFTER UPDATE can decide after the fact and is the better choice. Also easy to integrate with (most) additional triggers and / or rules in this scenario.