SQLite locking specific columns - sqlite

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;

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 :-

How to add a date with an INSERT INTO SELECT in PL SQL?

For my homework I need to make a package that contains a procedure that removes all records from the table dwdimartist and then fills the table with all records from the table artist and add a field with the date of today.
This is what I've created, it works but I don't know if it's performant enough. Is it better to use a cursor that loops over each record in the table artist?
CREATE OR REPLACE PACKAGE BODY dwh AS
PROCEDURE filldwh IS
today_date DATE := SYSDATE;
BEGIN
DELETE FROM dwdimartist;
INSERT INTO dwdimartist (artistkey, name) (SELECT artistid, name FROM artist);
UPDATE dwdimartist SET added = today_date;
END filldwh;
END dwh;
Simple SQL query like you did is better choice than a cursor or implicit loop.
possible improvement:
You should do it at once without update: set the date during insert.
INSERT INTO dwdimartist (artistkey, name, added)
(SELECT artistid, name, sysdate FROM artist);
Hope it helps
You don’t need to use cursors. You can hardly beat Insert ... select since it’s SQL itself and in most cases it works better than any programmatic structure due to native dbms optimization. However, you can do better if you decrease number of I/O operations. You don’t need update here. Simply add sysdate to your select and insert everything together.
insert into dwdimartist(artistkey, name, added) select artistid, name, sysdate from artist;
Another thing to improve is your ‘delete’. While it’s small table for learning purposes you won’t feel any difference between delete and truncate but for real ETL tasks and big tables you’ll see it definitely. Yes you cannot use truncate directly in your PL/SQL but there are 3 ways to do it:
execute immediate ‘truncate dwdimartist’;
Dbms_utility.exec_ddl_statement(‘truncate ...;’);
DBMS_SQL package
However, remember that calling truncate will execute commit like any ddl command. So delete may be the only option sometimes. But even if it is, you should avoid it for the whole table.

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 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!

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