Replace DELETE for UPDATE with a trigger on SQLite - sqlite

Is it possible in SQLite to make an update instead of a delete within a trigger ?
I.e, I got these two tables:
CREATE TABLE author (authorid INTEGER PRIMARY KEY, temporal NUMERIC);
CREATE TABLE comment (id INTEGER PRIMARY KEY, text TEXT, authorid INTEGER, FOREIGN KEY(authorid) REFERENCES author(authorid));
When a deletion of an author is attempted and there's any comment referencing that author i want to update the "temporal" field and abort deletion.
I've tested different approaches with triggers but i have not found a way to do the two things, make the update and abort the delete. I can abort the delete (though in this case it's not necessary as it is enforced by the foreign key constraint) or make the update (though the delete will remove the record, so the update has no effect)

Aborting the deletion is possible only with using RAISE to generate an error, but this would have the consequence that any UPDATE gets rolled back.
You could make author a view and create several INSTEAD OF triggers that pass through most actions to the base table.
However, it would be much easier to handler the temporal logic in your application.

Related

Sqlite: table constraints and triggers

I know the order of triggers in SQLite is undefined (you cannot be sure what trigger will be executed first), but, how about the relationship between table constraints and triggers?
I mean, suppose I have, for example, a UNIQUE (or CHECK) constraint in a column, and a BEFORE and AFTER UPDATE triggers on that table. If the UNIQUE column is modified, when does sqlite check the UNIQUE constraint? before calling BEFORE triggers, after calling AFTER triggers, between them, or with undefined order?
I have found nothing in SQLite docs about it.
SQLite reccommends not to modify data in BEFORE UPDATE/DELETE triggers, since it will lead to undefined behaviour (see: Cautions on the use of before triggers in the documentations).
There is a hint in a SQLite source code comment (src/update.c) that helps to know what happens under the hood:
/* Fire any BEFORE UPDATE triggers. This happens before constraints are
** verified. One could argue that this is wrong.
*/
Looking at the source code, whenever SQLite updates a table it perform this actions:
Loads the table data used by the update.
Runs the UPDATE operation (you need this to populate old.field and new.field)
Then, it Executes the BEFORE UPDATE trigger(s).
If the BEFORE UPDATE trigger(s) didn't delete the row data:
Loads the table data not used by the trigger.
Then Checks constraints (Primary keys, foreign keys, uniqueness, on..cascade, etc)
And then SQLite executes the AFTER UPDATE trigger(s).
If any BEFORE UPDATE trigger deleted the row data:
There is no need to check constraints.
No AFTER UPDATE triggers are run.
When the documentation does not say anything about it, then the order is undefined.
As long as the triggers do not have side effects outside the database, this does not matter, because any changes made by a trigger would be rolled back if a constraint fails.
Please note that SQLite takes backwards compatibility very seriously, so it is unlikely that the actual order will ever change.

Extending delete method of GridView

I have a page with GridView pulling some data from a SQL Server database via Linq-to-SQL.
I made use of the automatically-generated buttons for deleting. However, in order for the delete command to work properly, I need to somehow make sure that one table in relation with those records I want to delete, is also modified (the related record in it is also looked up and deleted).
Whats the easiest way to do this?
Thanks,
Ondrej
Define a foreign-key constraint with cascade delete.
Delete Rule
Specify what happens if a user tries to delete a row with data that is involved in a foreign key relationship:
No Action An error message tells the user that the deletion is not allowed and the DELETE is rolled back.
Cascade Deletes all rows containing data involved in the foreign key relationship.
Set Null Sets the value to null if all foreign key columns for the table can accept null values.

INSERT OR REPLACE + foreign key ON DELETE CASCADE working too good

I am currently trying to create an sqlite database where I can import a table from another sqlite database (can't attach) and add some extra data to each column.
Since there is no INSERT OR UPDATE I came up with this:
I was thinking about splitting the data into two tables and join them afterwards so I can just dump the whole import into one table replacing everything that changed and manage the extra data separately since that does not change on import.
The first table (let's call it base_data) would look like
local_id | remote_id | base_data1 | base_data2 | ...
---------+-----------+------------+------------+----
besides the local_id everything would just be a mirror of the remote database (I'll probably add a sync timestamp but that does not matter now).
The second table would look similar but has remote_id set as foreign key
remote_id | extra_data1 | extra_data2 | ...
----------+-------------+-------------+----
CREATE TABLE extra_data (
remote_id INTEGER
REFERENCES base_data(remote_id)
ON DELETE CASCADE ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
extra_data1 TEXT,
extra_data2 TEXT,
/* etc */
)
Now my idea was to simply INSERT OR REPLACE INTO base_data ... values because the database I import from has no sync timestamp or whatsoever and I would have to compare everything to find out what row I have to UPDATE / what to INSERT.
But here lies the problem: INSERT OR REPLACE is actually a DELETE followed by an INSERT and the delete part triggers the foreign key ON DELETE which I thought I could prevent by making the constraint DEFERRED. It does not work if I wrap INSERT OR REPLACE in a transaction either. It's always deleting my extra data although the same foreign key exists after the statement.
Is it possible to stop ON DELETE to trigger until the INSERT OR REPLACE is finished? Maybe some special transaction mode / pragma ?
It seems work if I replace the ON DELETE CASCADE part by a trigger like:
CREATE TRIGGER on_delete_trigger
AFTER DELETE ON base_data
BEGIN
DELETE FROM extra_data WHERE extra_data.remote_id=OLD.remote_id;
END;
That trigger is only triggered by a DELETE statement and should solve my problem so far.
(Answer provided by the OP in the question)
Additional info by jmathew, citing the documentation:
When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and only if recursive triggers are enabled.
Assuming you only have a single foreign key relationship on a primary key in your referenced table (as you do in your example), this proved to be a fairly painless solution for me.
Simply disable foreign key checks, run the replace query, then enable foreign keys again.
If the replace query is the only query that runs while foreign keys are disabled, you can be assured that no foreign keys will be fouled up. If you are inserting a new row, nothing will have had a chance to be linked to it yet and if you are replacing a row, the existing row will not be removed or have its primary key changed by the query so the constraint will still hold once foreign keys are re-enabled.
SQLlite code looks something like this:
PRAGMA foreign_keys=OFF;
INSERT OR REPLACE ...;
PRAGMA foreign_keys=ON;
What about the reasons of such behavior, there's an explanation from PostgreSQL team:
Yeah, this is per SQL spec as far as we can tell. Constraint checks can
be deferred till end of transaction, but "referential actions" are not
deferrable. They always happen during the triggering statement. For
instance SQL99 describes the result of a cascade deletion as being that
the referencing row is "marked for deletion" immediately, and then
All rows that are marked for deletion are effectively deleted
at the end of the SQL-statement, prior to the checking of any
integrity constraints.

SQL Delete taking too long

We have a table(say T1) that is referenced by about 16 other tables with foreign keys in our SQL Server database. The data is accessed through an ASP.NET application with LINQToSQL. When the user tried to delete a record from T1 the statement would time out. So we decided to first delete the records from the tables that reference T1 and only then delete the record in T1. The problem is that deletion from T1 does not work as fast as expected.
My question is: is it normal that deletion from a table referenced by many other tables to be so time-consuming even if the record itself does not have any 'children' records?
EDIT: Apparently the cause for the timeout was not the delete itself but another query that retrieved data from the same DataContext. Thank you for your suggestions, I have marked as answer the suggestion to add indexes for all foreign keys because it improved our script's execution plan.
I suspect that you may need to look into the indexing on your child tables.
It sounds as if you FKs are set to Cascade Deletes, so I would suspect that some of your tables do not have an index that includes the key to the parent as the first in the index.
In this way your delete will be full scanning the child tables - even if you've already deleted the child records it will still check as you've still got the Cascade set.
When you define a relationship in DB, you can set the Delete rule as Cascade in SQL server. In this way, when you delete the record from the parent table, it will be automatically deleted from the child tables.
Please see the image below:
If it taking long time, you may have set other constraint that will slow
down the process of deletion.
Linq does not do bulk deletes if you're having it operate directly on the record set -- instead, it is probably deleting one record at a time.
To improve performance, use a stored procedure instead for any bulk insert, update or delete operations.

It has a DefiningQuery but no InsertFunction element... err

This thing is driving me crazy, and the error is quite meaningless to me:
Unable to update the EntitySet 'TableB' because it has a DefiningQuery and no element exists in the element to support the current operation.
My tables are put like this:
TableA
int idA (identity, primary key)
...
TableB
int idA (FK for TableA.idA)
int val
TableB has no defined primary key in the SQL server. The Entity Framework has imported the table and the association and set both fields as key. But it will output that error when I try to do an insert into the table!
What's wrong??
Edit:
As suggested by Alex, the solution was this:
Right click on the edmx file, select Open with, XML editor
Locate the entity in the edmx:StorageModels element
Remove the DefiningQuery entirely
Rename the store:Schema="dbo" to Schema="dbo" (otherwise, the code will generate an error saying the name is invalid)
Remove the store:Name property
I left the key as it was, since it was OK to me that both the columns are part of the key.
Well when a table is encountered without a PrimaryKey it is treated as a View.
And views show up in the EDMX file (open in an XML editor to see) in the StorageModel\EntitySet[n]\DefiningQuery element.
When you have a DefiningQuery the Entity becomes readonly unless you add modification functions. You need 3 modifications functions (aka Stored Procedures) one for each of Insert, Update and Delete.
But you have two options:
Change the key definion:
And convince the EF that what it thinks is a view is really a table
Or add the appropriate modification functions
In your case I recommend (1).
Just Add a primary key to the table. That's it. Problem solved.
ALTER TABLE <TABLE_NAME>
ADD CONSTRAINT <CONSTRAINT_NAME> PRIMARY KEY(<COLUMN_NAME>)
I was missing a primary key on my table and got this error message. One thing I noted was after I added the key to the table, I needed to clear the table from the edmx using the designer, save the edmx, then update it again to add the table back in. It wasn't picking up the key since it was already assigned as a view. This didn't require editing the edmx manually.
Add primary key to table, delete the model from the edmx model, then select update from database, build and run...... works
#Palantir. Verify that both of you tables have Primary Keys set, and be careful with multiple primary keys set in a table.
You need to manually open the .EDMX file in notepad or notepad++ or
in any text editor of your choice.
Locate the entry in edmx:StorageModels in file opened in step1.
Find the DefiningQuery element and remove this tag entirely.
Find the store:Schema="dbo" to Schema="dbo" (if you skip this step
it will generate error of the name is invalid).
Save and close the file.
Hope it will solve the problem.

Resources