SQLite create table on trigger - sqlite

I want to create a new table for every row that is inserted into an existing table.
As I understand only DML operation is allowed on trigger, is it correct.
If so is there a alternative way of achieving my objective?

SQLite indeed allows only DML in a trigger body.
However, you could do a SELECT with a user-defined function that then executes another SQL command to create the table:
CREATE TRIGGER ...
...
BEGIN
SELECT my_create_table_function(NEW.name);
END;

Related

How to delete from multiple unrelated tables in single statement in SQLite?

I need to implement a function that simply purges the database of all data except for a few tables. Given how the code is written so far, it would be easier to do that than to implement an outside function that actually deletes the entire database file and creates a new one.
I want to do this on one prepared statement, so that I can avoid code bloat by having multiple statements and executing them in sequence. I would also like to do it in a single transaction using begin and commit just in case. What I tried is this:
begin;
DELETE FROM dataTable;
DELETE FROM cacheTable;
DELETE FROM someOtherTable;
commit;
Running this in DBeaver, not only it does not work, it actually somehow leaves the database with the transaction still open, judging from this error that I get on a second try:
SQL Error [1]: [SQLITE_ERROR] SQL error or missing database (cannot start a transaction within a transaction)
I believe what is happening is only the first line is executed and everything after a semicolon is ignored.
Can this be done in one statement?
I want to do this on one prepared statement, so that I can avoid code bloat by having multiple statements and executing them in sequence.
Each has to be a single statement (unless ....).
However if you wanted to do via a single statement then although a bit convoluted you could use a TRIGGER which will can multiple statements (limitations apply) within a single transaction.
I would also like to do it in a single transaction using begin and commit just in case.
If you use a single statement then it is always a single transaction and there is no need for BEGIN .... COMMIT.
If you take the TRIGGER route then here's an example/demo.
The TRIGGER would need to be triggered and thus you could utilise a table specifically for this (utilising other tables could lead to inadvertent triggering).
The example/demo below uses such a table namely triggerTable.
It will typically be empty (cleared along with the other tables by the Trigger).
As the TRIGGER is an AFTER INSERT trigger (as per the demo), inserting a row into the triggerTable initiates the clearing of the other tables and also the triggerTable.
There should be minimal overheads, very little time for the actual insert and subsequent delete of the inserted row and probably just the 1 (4k extra storage) page for the table.
Perhaps consider the following example/demo :-
/* Prepare Testing Environment */
CREATE TABLE IF NOT EXISTS dataTable (x);
CREATE TABLE IF NOT EXISTS cacheTable (y);
CREATE TABLE IF NOT EXISTS someOtherTable(z);
/* triggerTable could be another table */
CREATE TABLE IF NOT EXISTS triggerTable(id INTEGER PRIMARY KEY);
DROP TRIGGER IF EXISTS trigger_deletions;
/* The Trigger that will do the deletions in a single transaction */
CREATE TRIGGER IF NOT EXISTS trigger_deleteions AFTER INSERT ON triggerTable
BEGIN
DELETE FROM dataTable;
DELETE FROM cacheTable;
DELETE FROM someOtherTable;
DELETE FROM triggerTable; /* if triggerTable used */
END
;
/* Load some data */
INSERT INTO dataTable VALUES (1),(2),(3);
INSERT INTO cacheTable VALUES (1),(2),(3);
INSERT INTO someOtherTable VALUES (1),(2),(3);
/* Show the before test data PLUS trailer just in case nothing selected */
SELECT 'DT',* FROM dataTable UNION ALL SELECT 'CT',* FROM cacheTable UNION ALL SELECT 'SOT',* FROM someOtherTable UNION ALL SELECT 'trailer','trailer';
/* Initiate the Deletions */
INSERT INTO triggerTable VALUES(1);
/* Show the after test data PLUS trailer just as no data to select */
SELECT 'DT',* FROM dataTable UNION ALL SELECT 'CT',* FROM cacheTable UNION ALL SELECT 'SOT',* FROM someOtherTable UNION ALL SELECT 'trailer','trailer';
/* show the state of the trigger table */
SELECT * FROM triggerTable;
/* CleanUp Testing Environment */
DROP TABLE IF EXISTS dataTable;
DROP TABLE IF EXISTS cacheTable;
DROP TABLE IF EXISTS someOtherTable;
Running this in DBeaver, not only it does not work, it actually somehow leaves the database with the transaction still open, judging from this error that I get on a second try:
Are you executing the script as opposed to executing a statement ?
Using DBeaver using the above results in :-

SQLite locking specific columns

I am trying to lock specific columns in a table, in order to prevent from a mistakely UPDATE command to modify those fields. On the contrary, some other columns should be allowed to be modified. Is there any specific SQL command for doing so?
You can use a trigger to prevent this:
CREATE TRIGGER PreventUpdateOfThisThat
BEFORE UPDATE OF ThisColumn, ThatColumn OF MyTable
BEGIN
SELECT RAISE(FAIL, "don't do that!");
END;

SQLite Trigger for Inserting values from different tables

I would like to create a trigger on SQLite to update one table with values from more than one table. I have tried the code below but navicat for Sqlite wont save my trigger. Could someone help?
BEGIN
INSERT INTO hdClassSet (setNo, class, type, regdate, acYear)
VALUES (((SELECT max(setNo) FROM hdClassSet)+1),
old.clID,
0,
(date('now','localtime')),
(SELECT acID FROM tblAcad WHERE actv1=1));
END
I managed to save and test the trigger without the last value and it works. How can I reference another table that does not start or affected by the trigger? I will be grateful!

How do I call user-defined function only if it exists?

SQLite allows to define custom functions that can be called from SQL statements. I use this to get notified of trigger activity in my application:
CREATE TRIGGER AfterInsert AFTER INSERT ON T
BEGIN
-- main trigger code involves some extra db modifications
-- ...
-- invoke application callback
SELECT ChangeNotify('T', 'INSERT', NEW.id);
END;
However, user-defined functions are added only to current database connection. There may be other clients who haven't defined ChangeNotify and I don't want them to get a "no such function" error.
Is is possible to call a function only if it's defined? Any alternative solution is also appreciated.
SQLite is designed as an embedded database, so it is assumed that your application controls what is done with the database.
SQLite has no SQL function to check for user-defined functions.
If you want to detect changes made only from your own program, use sqlite3_update_hook.
Prior to calling your user defined function, you can check if the function exists by selecting from pragma_function_list;
select exists(select 1 from pragma_function_list where name='ChangeNotify');
1
It would be possible by combining a query against pragma_function_list and a WHEN statement on the trigger --
CREATE TRIGGER AfterInsert AFTER INSERT ON T
WHEN EXISTS (SELECT 1 FROM pragma_function_list WHERE name = 'ChangeNotify')
BEGIN
SELECT ChangeNotify('T', 'INSERT', NEW.id);
END;
except that query preparation attempts to resolve functions prior to execution. So, afaik, this isn't possible to do in a trigger.
I need to do the same thing and asked here: https://sqlite.org/forum/forumpost/a997b1a01d
Hopefully they come back with a solution.
Update
SQLite forum suggestion is to use create temp trigger when your extension loads -- https://sqlite.org/forum/forumpost/96160a6536e33f71
This is actually a great solution as temp triggers are:
not visible to other connections
are cleaned up when the connection creating them ends

pl sql: trigger for insert data from another table

There is the table OLD and a similar one, NEW. I want to insert in the existing process that fills the table OLD a trigger event that for each new inserted row, this event will insert the newly inserted row to table NEW, as well. Inside the body of trigger, i need to include the query BELOW which aggregates values of OLD before inserted in NEW:
insert into NEW
select (select a.id,a.name,a.address,b.jitter,a.packet,a.compo,b.rtd,a.dur from OLD a,
select address,packet,compo, avg(jitter) as jitter, avg(rtd) as rtd from OLD
group by address,packet,compo ) b
where a.address=b.address and a.packet=b.packet and a.compo=b.compo;
can you correct any possible mistakes or suggest other trigger syntax on the statement below?
CREATE OR REPLACE TRIGGER insertion
after update on OLD
for each row
begin
MY select query above
end;
In a for each row trigger you cannot query the table itself. You will get a mutating table error message if you do.
I recommend only to use triggers for the most basic functionality such as handing out ID numbers and very basic checks.
If you do use triggers for more complex tasks you may very easily end up with a system that's very hard to debug and maintain because of all kinds of actions that appear out of knowhere.
Look at this question for another approach: getting rid of Insert trigger
Oracle Streams might also be a good solution. In the apply handler, you can include your own custom PL/SQL code. This procedure will be called after the COMMIT, so you can avoid mutating table errors.
However, Streams requires a large amount of setup to make it work. It might be overkill for what you are doing.

Resources