update and insert triggers in sqlite - sqlite

I want to have riggers that set the last_modified column automatically each time a row in updated or inserted.
Lets say I have an ID that is unique to each row.
This is my query:
CREATE TRIGGER insert_trigger
AFTER INSERT ON TABLE_NAME
BEGIN
update TABLE_NAME set last_modified =strftime('%Y-%m-%d %H:%M:%S:%s','now', 'localtime') where id = old.id;
END;
After creating this trigger, when I try to insert I get the error:
no such column: old.id
I can understand why I get this error, but how can I create a proper trigger?

When inserting, there is no old row.
To get the ID of the new row, use NEW.id.

Related

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.

sqlite shift rowid in multiple records

Hello i have an sqlite db with many records like 10540 record they are ordered by creation time , i want to shift like a record in the middle and like to do it automatically
for example :
select * from table1 where id >= 8521;
UPDATE Table1 SET id = id +1 ;
does not work i get Error: Result: UNIQUE constraint failed:
so i want to shift up all records from 8521 to the last record and get place in the 8520 place for example so i can insert my record in that place of table .
even the
id = select max(id)+1
does not work how can i increment the id from last record to the needed record so i can put a place in the records db
A simple update statement would fail, as it would try to create duplicate values in the primary key.
What you can do is this:
First update the column to the negatives of the values they should have:
update table1
set id = -(id + 1)
where id > 8520;
Now there are no duplicates and you just need to update again to the positive values:
update table1
set id = -id
where id < 0;
This will do the trick, but any kind of updating the primary key is not a recommended practice

SQLite trigger after update

My table has timestamp column. I want a trigger which sets timestamp to 0 on affected rows when a row is updated and the timestamp is not specified in the update statement.
If I use this trigger:
CREATE TRIGGER AFTER UPDATE ON mytable FOR EACH ROW
WHEN (NEW.timestamp IS NULL)
BEGIN
UPDATE mytable SET timestamp = 0 WHERE id = NEW.id;
END;
then the trigger doesn't fire for this update statement:
UPDATE mytable SET comecolumn='some'
I.e. timestamp of affected rows doesn't change to 0.
Can you please help me define the trigger?
The only way to make additional changes to a row in an UPDATE trigger is to execute another UPDATE on the same table afterwards.
The only way to detect whether a column value is changed is to compare the old and the new row values; the trigger does not know which columns actually were mentioned in the original UPDATE statement.
To prevent the trigger from triggering itself recursively, you should restrict it to be triggered by changes of all columns except the timestamp:
CREATE TRIGGER clear_timestamp
AFTER UPDATE OF all_the, other, columns ON MyTable
FOR EACH ROW
WHEN OLD.timestamp = NEW.timestamp
BEGIN
UPDATE MyTable
SET timestamp = 0
WHERE id = NEW.id;
END;
I think the problem is that in the SET statement is expanded to every column, with every column set to the current value in the database. So the original only trigger works, if the current timestamp column is NULL.
A solution could be to create another trigger that resets the timestamp column to NULL before an UPDATE.
CREATE TRIGGER "set_null"
BEFORE UPDATE ON "mytable" FOR EACH ROW
BEGIN
UPDATE mytable set timestamp = NULL where rowid = NEW.rowid;
END
This way the NEW.timestamp is NULL if it is not specified in the UPDATE SET.
Obviously now a NOT NULL constraint cannot be set on timestamp.
Another problem is that trigger recursion must be off when executing a update query:
PRAGMA recursive_triggers = OFF;
Here is another way:
import sqlite3
conn = sqlite3.connect(':memory:')
c = conn.cursor()
name = {'name':'jack'}
c.execute("""CREATE TABLE Programs (
id INTEGER PRIMARY KEY,
name VARCHAR(64) NOT NULL,
time_added INTEGER
);""")
c.execute("""CREATE TRIGGER program_time_added AFTER INSERT ON Programs
FOR EACH ROW
BEGIN
UPDATE Programs SET time_added =datetime('now', 'localtime') WHERE id = NEW.id;
END;""")
c.execute('INSERT INTO Programs (name) VALUES (?)', [name['name']])

SQLite update trigger changes all rows in the table

Problem: a simplest possible update trigger writes a new value to all table rows instead of just the row being updated. Here is the table:
[names]
id INTEGER PRIMARY KEY
name TEXT
len INTEGER
Now I want to create triggers to update 'len' with the length of 'name'. This INSERT trigger seems to be doing the job corectly:
CREATE TRIGGER 'namelen' AFTER INSERT ON 'names'
BEGIN
UPDATE 'names' SET len = length(NEW.name) WHERE (id=NEW.id);
END;
Problems begin when I add a similar UPDATE trigger:
CREATE TRIGGER 'namelenupd' AFTER UPDATE ON 'names'
BEGIN
UPDATE 'names' SET len = length(NEW.name) WHERE (OLD.id=NEW.id);
END;
The update trigger writes the new length to all rows of the table, despite the WHERE clause. For example, if I say
UPDATE 'names' SET name='foo' where id=1;
then the value of 'len' becomes 3 for all rows of the table. I've looked at sqlite trigger examples and I can't see my error. What else must I do to make sure the trigger updates the 'len' column only in the row(s) that are actually updated?
Both OLD.xxx and NEW.xxx refer to the table row that caused the trigger to run.
The UPDATE statement inside the trigger runs independently; if you want to restrict it to one table row, you have to explicitly do this in its WHERE clause by filtering on that statement's table values, i.e., names.id or just id.
When the original UPDATE statement does not change the id column, the old and new id values are the same, and the expression OLD.id=NEW.id is true for all records in the table, as seen by the inner UPDATE statement.
The correct trigger looks like this:
CREATE TRIGGER "namelenupd"
AFTER UPDATE OF name ON "names"
BEGIN
UPDATE "names" SET len = length(NEW.name) WHERE id = NEW.id;
END;
Had the same issue, here's the syntax from my trigger
You would change "ALTER" to "CREATE" depending on what you already have (or not)
You have "id" as your primary key
Your dbo is "names"
Obviously, this will set the name value to "foo" (not really what you wanted). The key seems to be the last line, where you set inner join inserted on names.Id = inserted.Id.
USE [yourDBname]
ALTER TRIGGER [dbo].[yourTrigger]
ON [dbo].[names]
After INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Select id from inserted
begin
update [dbo].names
set [dbo].names.name = 'foo'
from dbo.names
inner join inserted
on names.id = inserted.id
END

SQLITE fill value with unique random table

I want to create a table with a field that is unique and limited to a certain value. Lets say that the limit is 100, the table is full, I remove a random row, and when I create a new row it has the value that was freed before.
It doesn't need to be the fastest thing in the world (the limit is quite small), I just want to implement it in a DB.
Any ideas?
Create one more column in main table, say deleted (integer, 0 or 1). When you need to delete with certain id, do not really delete it, but simply update deleted to 1:
UPDATE mytable SET deleted=1 WHERE id = <id_to_delete>
When you need to insert, find id to be reused:
SELECT id FROM mytable WHERE deleted LIMIT 1
If this query returns empty result, then use INSERT to create new id. Otherwise, simply update your row:
UPDATE mytable SET deleted=0, name='blah', ... WHERE id=<id_to_reuse>
All queries reading from your main table should have WHERE constraint with NOT deleted condition:
SELECT * FROM mytable WHERE NOT deleted
If you add index on deleted, this method should work fast even for large number of rows.
This solution does everything in a trigger, so you can just use a normal INSERT.
For the table itself, we use an autoincrementing ID column:
CREATE TABLE MyTable(ID INTEGER PRIMARY KEY, Name);
We need another table to store an ID temporarily:
CREATE TABLE moriturus(ID INTEGER PRIMARY KEY);
And the trigger:
CREATE TRIGGER MyTable_DeleteAndReorder
AFTER INSERT ON MyTable
FOR EACH ROW
WHEN (SELECT COUNT(*) FROM MyTable) > 100
BEGIN
-- first, select a random record to be deleted, and save its ID
DELETE FROM moriturus;
INSERT INTO moriturus
SELECT ID FROM MyTable
WHERE ID <> NEW.ID
ORDER BY random()
LIMIT 1;
-- then actually delete it
DELETE FROM MyTable
WHERE ID = (SELECT ID
FROM moriturus);
-- then change the just inserted record to have that ID
UPDATE MyTable
SET ID = (SELECT ID
FROM moriturus)
WHERE ID = NEW.ID;
END;

Resources