How to delete from multiple unrelated tables in single statement in SQLite? - 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 :-

Related

How do I make a trigger with an increment for the values in a column?

I have two tables TRIP and DRIVER. When a new set of values in inserted into TRIP (to indicate a new trip being made), the values in the column TOTALTRIPMADE (which is currently empty) in the table DRIVER will increase by one. The trigger should recognise which row to update with the select statement I've made.
This is the trigger I've made:
CREATE OR REPLACE TRIGGER updatetotaltripmade
AFTER INSERT ON trip
FOR EACH ROW
ENABLE
BEGIN
UPDATE DRIVER
SET TOTALTRIPMADE := OLD.TOTALTRIPMADE+1
WHERE (SELECT L#
FROM TRIP
INNER JOIN DRIVER
ON TRIP.L# = DRIVER.L#;)
END;
/
However I get this error:
ORA-04098: trigger 'CSCI235.UPDATETOTALTRIPMADE' is invalid and failed re-validation
What should I edit in my code so that my trigger works? Thanks!
One error you made is in trying to reference OLD.TOTALTRIPMADE in your SET clause since no alias OLD exists, and unless the table TRIP contains a TOTALTRIPMADE column then the :OLD record won't contain a TOTALTRIPMADE column either (note that since this is an insert trigger the :OLD record either won't exist or won't contain any meaningful data anyway). Another error is in your WHERE clause where you are selecting L# from TRIP joined to DRIVER, but you aren't linking it back to the DRIVER table that you are attempting to update. Instead just update DRIVER where L# is equal the :NEW value of L# from the trip table. The final error I noticed is your use of , the := assignment operator which is for PLSQL code, however you are using it within SQL so just use = without the colon:
CREATE OR REPLACE TRIGGER updatetotaltripmade
AFTER INSERT ON trip
FOR EACH ROW
ENABLE
BEGIN
UPDATE DRIVER
SET TOTALTRIPMADE = nvl(TOTALTRIPMADE,0)+1
WHERE L# = :NEW.L#;
END;
/
Your code has syntax error due to which the trigger is not compiling,I have modified the trigger and it should get compiled successfully with desired results.Please check and feedback.
Please find below the script to create table and compile the trigger,
drop table trip;
create table trip (trip_id number(10),L# varchar2(10));
drop table driver;
create table driver(driver_id number(10),TOTALTRIPMADE number(10),L# varchar2(10));
drop trigger updatetotaltripmade;
CREATE OR REPLACE TRIGGER updatetotaltripmade
AFTER INSERT ON trip
FOR EACH ROW
ENABLE
DECLARE
BEGIN
UPDATE DRIVER
SET TOTALTRIPMADE = nvl(TOTALTRIPMADE,0) + 1
WHERE DRIVER.L# = :new.L#;
END;
/
select * from ALL_OBJECTS where object_type ='TRIGGER';
Output is below from the tests i did on https://livesql.oracle.com/apex/
There are no issues in the code.The trigger is compiled successfully and is valid.

PLSQL: No output displayed when using dynamic query inside Stored Procedure

I have been asked to create an SP which creates temporary table and insert some records.
I am preparing some sample code for the same as mentioned below but the output is not displayed.
create or replace procedure Test
is
stmt varchar2(1000);
stmt2 varchar2(1000);
begin
stmt := 'create global temporary table temp_1(id number(10))';
execute immediate stmt;
insert into temp_1(id) values (10);
execute immediate 'Select * from temp_1';
execute immediate 'Drop table temp_1';
commit;
end;
When i am executing the SP by (Exec Test) desired O/P is not displayed.
I am expecting O/P of "Select * from temp_1" to be displayed. But it is not happening. Please suggest where i am doing wrong.
But i am interesting in knowing why ( execute immediate 'Select * from temp_1';) do not yield any result
For two reasons. Firstly because as #a_horse_with_no_name said PL/SQL won't display the result of a query. But more importantly here, perhaps, the query is never actually executed. This behaviour is stated in the documentation:
If dynamic_sql_statement is a SELECT statement, and you omit both into_clause and bulk_collect_into_clause, then *execute_immediate_statement( never executes.
You would have to execute immediate into a variable, or more likely a collection if your real scenario has more than one row, and then process that data - iterating over the collection in the bulk case.
There is not really a reliable way to display anything from PL/SQL; you can use dbms_output but that's more suited for debugging than real output, and you usually have no guarantee that the client will be configured to show whatever you put into its buffer.
This is all rather academic since creating and dropping a GTT on the fly is not a good idea and there are better ways to accomplish whatever it is you're trying to do.
The block you showed shouldn't actually run at all; as you're creating temp_1 dynamically, the static SQL insert into temp_1 will error as that table does not yet exist when the block is compiled. The insert would have to be dynamic too. Any dynamic SQL is a bit of a warning sign you're maybe doing something wrong, though it is sometimes necessary; having to do everything dynamically suggests the whole approach needs a rethink, as does creating objects at runtime.

SQLite create table on trigger

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;

Sqlite: Are updates to two tables within an insert trigger atomic?

I refactored a table that stored both metadata and data into two tables, one for metadata and one for data. This allows metadata to be queried efficiently.
I also created an updatable view with the original table's columns, using sqlite's insert, update and delete triggers. This allows calling code that needs both data and metadata to remain unchanged.
The insert and update triggers write each incoming row as two rows - one in the metadata table and one in the data table, like this:
// View
CREATE VIEW IF NOT EXISTS Item as select n.Id, n.Title, n.Author, c.Content
FROM ItemMetadata n, ItemData c where n.id = c.Id
// Trigger
CREATE TRIGGER IF NOT EXISTS item_update
INSTEAD OF UPDATE OF id, Title, Author, Content ON Item
BEGIN
UPDATE ItemMetadata
SET Title=NEW.Title, Author=NEW.Author
WHERE Id=old.Id;
UPDATE ItemData SET Content=NEW.Content
WHERE Id=old.Id;
END;
Questions:
Are the updates to the ItemMetadata and ItemData tables atomic? Is there a chance that a reader can see the result of the first update before the second update has completed?
Originally I had the WHERE clauses be WHERE rowid=old.rowid but that seemed to cause random problems so I changed them to WHERE Id=old.Id. The original version was based on tutorial code I found. But after thinking about it I wonder how sqlite even comes up with an old rowid - after all, this is a view across multiple tables. What rowid does sqlite pass to an update trigger, and is the WHERE clause the way I first coded it problematic?
The documentation says:
No changes can be made to the database except within a transaction. Any command that changes the database (basically, any SQL command other than SELECT) will automatically start a transaction if one is not already in effect.
Commands in a trigger are considered part of the command that triggered the trigger.
So all commands in a trigger are part of a transaction, and atomic.
Views do not have a (usable) rowid.

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