SQLite trigger to update row gets stuck - sqlite

I'm trying to set on insert, or update, an attribute of a point layer with an attribute derived from a polygon layer (12-digit watershed identifier) in the same database. So this is using SpatiaLite (a spatial extension of SQLite), but that's a detail and this should simply be a SQL problem.
So, I created two triggers below. The UPDATED_ON portion where it sets the timestamp of the action works great. But the spatial parts always assign the same value regardless of where the point occurs as illustrated in the attached example. It seems to be performing the same test for all the rows, with input from one row, but applying to the wrong row. So, something in the SQL of the trigger must be grabbing the wrong point to perform the operation with. Is it the new.rowid part?
CREATE TRIGGER INSERT_UDT_HUC12 AFTER INSERT ON mypoints
BEGIN
UPDATE mypoints SET UPDATED_ON = DATETIME('NOW') WHERE rowid = new.rowid;
UPDATE mypoints SET WBD_HUC12 =
(SELECT HUC_12 FROM cumberland_huc12, mypoints
WHERE st_intersects(mypoints.Geometry, cumberland_huc12.Geometry))
WHERE mypoints.rowid = new.rowid;
END;
CREATE TRIGGER UPDATE_UDT_HUC12 AFTER UPDATE ON mypoints
BEGIN
UPDATE mypoints SET UPDATED_ON = DATETIME('NOW') WHERE rowid = new.rowid;
UPDATE mypoints SET WBD_HUC12 =
(SELECT HUC_12 FROM cumberland_huc12, mypoints
WHERE st_intersects(mypoints.Geometry, cumberland_huc12.Geometry))
WHERE mypoints.rowid = new.rowid;
END

The subquery that returns the new watershed identifier is as follows:
SELECT HUC_12 FROM cumberland_huc12, mypoints
WHERE st_intersects(mypoints.Geometry, cumberland_huc12.Geometry)
Note that this returns all watersheds that intersect any point in mypoints.
Therefore, your trigger will receive the one that happens to be the first in this query.
You have to use the filter WHERE mypoints.rowid = new.rowid also in the subquery.

Related

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']])

Update row with value from next row sqlite

I have the following columns in a SQLite DB.
id,ts,origin,product,bid,ask,nextts
1,2016-10-18 20:20:54.733,SourceA,Dow,1.09812,1.0982,
2,2016-10-18 20:20:55.093,SourceB,Oil,7010.5,7011.5,
3,2016-10-18 20:20:55.149,SourceA,Dow,18159.0,18161.0,
How can I populate the 'next timestamp' column (nextts) with the next timestamp for the same product (ts), from the same source? I've been trying the following, but I can't seem to put a subquery in an UPDATE statement.
UPDATE TEST a SET nextts = (select ts
from TEST b
where b.id> a.id and a.origin = b.origin and a.product = b.product
order by id asc limit 1);
If I call this, I can display it, but I haven't found a way of updating the value yet.
select a.*,
(select ts
from TEST b
where b.id> a.id and a.origin = b.origin and a.product = b.product
order by id asc limit 1) as nextts
from TEST a
order by origin, a.id;
The problem is that you're using table alias for table in UPDATE statement, which is not allowed. You can skip alias from there and use unaliased (but table-name prefixed) reference to its columns (while keeping aliased references for the SELECT), like this:
UPDATE TEST
SET nextts = (
SELECT b.ts
FROM TEST b
WHERE b.id > TEST.id AND
TEST.origin = b.origin AND
TEST.product = b.product
ORDER BY b.id ASC
LIMIT 1
);
Prefixing unaliased column references with the table name is necessary for SQLite to identify that you're referencing to unaliased table. Otherwise the id column whould be understood as the id from the closest[*] possible data source, in which case it's the aliased table (as b alias), while we're interested in the unaliased table, therefore we need to explicitly tell SQLite that.
[*] Closest data source is the one listed in the same query, or parent query, or parent's parent query, etc. SQLite is looking for the first data source (going from inner part to the outside) in the query hierarchy that defines this column.

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

Possible to have SQLite trigger fire on insert of first record only?

I have a SQLite table with a Balance field that will be used as a running total. I have a trigger defined that calculates the Balance on insert of new records, but it doesn't fire on the first insert, so the Balance in the first record is zero. All following records calculate correctly.
Current trigger definition:
CREATE TRIGGER [UpdateBalance_Insert] AFTER INSERT ON [Transaction] FOR EACH ROW BEGIN
REPLACE INTO [Transaction]
SELECT t1.[ID],
t1.[Date],
t1.[Transaction],
t1.[Debit],
t1.[Credit],
( SELECT SUM( t2.[Credit] ) - SUM( t2.[Debit] ) + new.[Credit] - new.[Debit]
FROM [Transaction] AS t2
WHERE t2.[Date] < t1.[Date] OR (
t2.[Date] = t1.[Date] AND t2.[ID] < t1.[ID] )
) AS [Balance]
FROM [Transaction] AS t1
WHERE [ID] = new.[ID];
UPDATE [Transaction]
SET [Balance] = [Balance] + new.[Credit] - new.[Debit]
WHERE [Date] > new.[Date];
END
Edit: The nature of my question changed between typing the title and typing the description. Above is my original trigger. My original question was whether a separate trigger could be created to handle the first insert only, but now I wonder if the existing trigger could be modified to perform the needed functionality.
I'd try to check if the table is empty first, and if it is, hard-code a balance. If not, i'd compute it using your algorithm.

Update table using cursor but also update records in another table

I'm updating the IDs with new IDs, but I need to retain the same ID for the master record in table A and its dependants in table B.
The chunk bracketed by comments is the part I can't figure out. I need to update all the records in table B that share the same ID with the current record I'm looking at for table A.
DECLARE CURSOR_A CURSOR FOR
SELECT * FROM TABLE_A
FOR UPDATE
OPEN CURSOR_A
FETCH NEXT FROM CURSOR_A
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRANSACTION
UPDATE KEYMASTERTABLE
SET RUNNING_NUMBER=RUNNING_NUMBER+1
WHERE TRANSACTION_TYPE='TABLE_A_NEXT_ID'
-- FOLLOWING CHUNK IS WRONG!!!
UPDATE TABLE_B
SET TABLE_B_ID=(SELECT RUNNING_NUMBER
FROM KEYMASTERTABLE WHERE TRANSACTION_TYPE='TABLE_A_NEXT_ID')
WHERE TABLE_B_ID = (SELECT TABLE_A_ID
FROM CURRENT OF CURSOR A)
-- END OF BAD CHUNK
UPDATE TABLE_A
SET TABLE_A_ID=(SELECT RUNNING_NUMBER
FROM KEYMASTERTABLE WHERE TRANSACTION_TYPE='TABLE_A_NEXT_ID')
WHERE CURRENT OF CURSOR_A
COMMIT
FETCH NEXT FROM CURSOR_A
END
CLOSE CURSOR_A
DEALLOCATE CURSOR_A
GO
Based on the assumption that this process of incrementing current data by +1 doesn't cause issues in the data itself, I would create a translation table.
Column1 would be the old ID, Column2 would be the new ID.
Both tables would be run through the same update then.
This also gives you auditing on the process, in case something goes wrong.
Something like
Update table TargetA a
set a.id =(select t.column2 from tranlation_table t where t.column1 = a.id);
Update table TargetB b
set b.id =(select t.column2 from tranlation_table t where t.column1 = b.id)

Resources