SQLite User Forum

How do I conditionally execute a set of commands?
Login

How do I conditionally execute a set of commands?

(1) By adrian on 2022-05-19 20:03:18 [link] [source]

I'd like to execute several commands based on if a table exists. Is this possible?

Reasoning: I have a db that has a flat table. I have a script that converts it to a relational set of tables to remove redundant strings from the flat table and consolidate them in a separate table. It will then drop the flat table. I have another script that reverses the process. I would like to make the two scripts into one so that it acts like a toggle script based on if the flat table exists.

There are several commands, including a conditional DROP TABLE, a CREATE TABLE and an INSERT INTO done for two columns. This script is to be used by our techs to allow easier transmission of these log dbs as they can be quite large, so I'd like to make this as easy as possible.

Thx,

A

(2.1) By Harald Hanche-Olsen (hanche) on 2022-05-19 21:37:27 edited from 2.0 in reply to 1 [link] [source]

You could query the sqlite_schema table.

But note that the sqlite shell does not support conditionals.

(5) By adrian on 2022-05-20 11:46:15 in reply to 2.1 [link] [source]

Thx hanche. That was what I was asking. I didn't think there was a way. Thx again.

(3.4) By midijohnny on 2022-05-20 08:27:06 edited from 3.3 in reply to 1 [source]

This was fun to write, but its a hack and a half. Not sure it meets your exact requirements , but hopefully give you some ideas. You probably want to write a script in Python or something to do this properly.

Note: I'm using Linux, you will need alter path names ('/tmp/run.sql' for this to work on other platforms).

Create two scripts:

  • script1.sql
  • script2.sql

For instance:

-- script1
-- This will populate the tables based on the flat table.
-- Then drop the flat table
-- Do whatever here
DROP TABLE flat;
-- script2
-- This will create the flat table and populate from relational tables
CREATE TABLE flat(n); -- dummy table
-- Whatever

Open up your DB; and store them in the sqlar table. This is optional - you could just use external files, but this way you get to ship them all wrapped-up nice and cosy in the database itself.

$ sqlite3 hack.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> .archive --create
sqlite> .archive --insert script1.sql
sqlite> .archive --insert script2.sql

To extract a particular file to a new location, we may need to decompress the contents. The function sqlar_uncompress will take care of this for us.

sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as size_written
   ...> from sqlar where name='script1.sql';
size_written
131

To write the contents conditionally; we'll need to check for the existence of the table called 'flat'. Use the sqlite_schema table to look for it. Note I'm using the older synonym for this table as I happen to on an older version of sqlite3 at the moment.

sqlite> create table if not exists flat(n);
sqlite> select * from sqlite_master where (name,type)==('flat','table');
type|name|tbl_name|rootpage|sql
table|flat|flat|4|CREATE TABLE flat(n)
sqlite> drop table if exists flat;
sqlite> select * from sqlite_master where (name,type)==('flat','table');
sqlite> 

Note: the 'if not exists' and 'exists' conditions only apply to the DDL in question, so they can't be used (I don't think?) for other conditional statements. They just make the examples more illustrative.

Put it together: we can now conditionally write out different SQL out to a file based on whether a table exists.

So - to output 'script1.sql' if 'flat' exists:

sqlite> create table if not exists flat(n);
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> join sqlite_master s on (s.name,s.type)==('flat','table')
   ...> where ar.name='script1.sql';
o
131
sqlite> .shell cat /tmp/run.sql
-- This will populate the tables based on the flat table.
-- Then drop the flat table

-- Do whatever here

DROP TABLE flat;
sqlite> 

The same thing, but this time 'flat' doesn't exist:

sqlite> drop table flat;
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> join sqlite_master s on (s.name,s.type)==('flat','table')
   ...> where ar.name='script1.sql';

And to write out 'script2' only if 'flat' does NOT exist:

sqlite> create table if not exists flat(n);
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> where ar.name='script2.sql'
   ...> and not exists (select 1 FROM sqlite_master s where (s.name,s.type)==('flat','table'));
sqlite> 

Same thing, but this time flat does not exist:

sqlite> drop table if exists flat;
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> where ar.name='script2.sql'
   ...> and not exists (select 1 FROM sqlite_master s where (s.name,s.type)==('flat','table'));
o
120

Lastly: use '.read' to run whatever is in the output file 'run.sql'.

sqlite> .shell rm -rf /tmp/run.sql
sqlite> create table if not exists flat(n);
sqlite> .tables
flat   sqlar
sqlite> 
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> join sqlite_master s on (s.name,s.type)==('flat','table')
   ...> where ar.name='script1.sql';
o
131
sqlite> 
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> where ar.name='script2.sql'
   ...> and not exists (select 1 FROM sqlite_master s where (s.name,s.type)==('flat','table'));
sqlite> 
sqlite> .read /tmp/run.sql
sqlite> .tables
sqlar

Then just running the same SQL again:

sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> join sqlite_master s on (s.name,s.type)==('flat','table')
   ...> where ar.name='script1.sql';
sqlite> 
sqlite> select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
   ...> from sqlar ar
   ...> where ar.name='script2.sql'
   ...> and not exists (select 1 FROM sqlite_master s where (s.name,s.type)==('flat','table'));
o
131
sqlite> 
sqlite> .read /tmp/run.sql
sqlite> .tables
flat   sqlar

Here's the script source without the sqlite prompt etc:

.shell rm -rf /tmp/run.sql
.tables

select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
from sqlar ar
join sqlite_master s on (s.name,s.type)==('flat','table')
where ar.name='script1.sql';

select writefile('/tmp/run.sql',sqlar_uncompress(data,sz)) as o
from sqlar ar
where ar.name='script2.sql'
and not exists (select 1 FROM sqlite_master s where (s.name,s.type)==('flat','table'));

.read /tmp/run.sql
.tables

(4) By adrian on 2022-05-20 11:36:34 in reply to 3.4 [link] [source]

Wow! Thx midijohnny. This is way above my ability level. I'm going to have to chew on this for a while to see exactly what you are doing here. But this is great! Thx again.

(6) By midijohnny on 2022-05-20 13:02:37 in reply to 4 [link] [source]

Thanks - more of a fun-hack that a serious method probably ! :-)

(7) By mgr (mgrmgr) on 2022-05-20 16:07:52 in reply to 6 [link] [source]

Assuming the same setup as in 3.4 - script1.sql & script2.sql in sqlar table - you could define a view that dynamically produces the code to toggle the state:

create view toggle_script(code) as
select sqlar_uncompress(data,sz)
from sqlar
where name=case
   when (select count(*) from sqlite_schema where (name,type)=('flat','table') ) = 0 then 'script2.sql'
   else 'script1.sql'
end;

Then, use it in any one of these ways to toggle:

using a temporary file:

.once /tmp/run.sql
select code from toggle_script;
.read /tmp/run.sql
-- optional clean up
.shell rm /tmp/run.sql

using .read's pipe functionality:

(assuming you know the DB path explicitly)

.read "|sqlite3 hack.db 'select code from toggle_script;'"

from outside:

(assuming unix shell)

sqlite3 hack.db 'select code from toggle_script;' | sqlite3 hack.db 

(8) By Harald Hanche-Olsen (hanche) on 2022-05-20 16:24:13 in reply to 7 [link] [source]

Given the fact that SQL is Turing complete I should perhaps not be too surprised to see these weird and wonderful hacks. But I didn't know about that result before now; the present discussion prompted me to search the net.

(10.1) By midijohnny on 2022-05-21 12:46:57 edited from 10.0 in reply to 7 [link] [source]

I see your view and raise you a 'no-more-uncompress-function' :-)

As before - store the scripts in the sqlar table:

sqlite> .archive --create
sqlite> .archive --insert script1.sql script2.sql
sqlite> .archive --list
script1.sql
script2.sql
sqlite> .quit

Now create a view to conditionally output a new sqlar row - based on the existence (or not) of the 'flat' table:

CREATE VIEW v_toggle AS
WITH
  control(count, name) AS (
    VALUES
      (0,'script1.sql'),
      (1,'script2.sql')
),
  tabcount(count) AS (
    SELECT count(*) FROM sqlite_master
    WHERE (name,type)==('flat','table')
),
  tabmatch(name) AS (
    SELECT name FROM control
    JOIN tabcount USING(count))
SELECT 'run.sql',mode,mtime,sz,data
FROM sqlar
JOIN tabmatch USING(name);

Now we can use the view to replace the sqlar entry dynamically and use '.archive' to extract 'run.sql' and '.read' to run it.

REPLACE INTO sqlar
SELECT * FROM v_toggle;
.archive --extract run.sql
.read run.sql

script1.sql just contains a simple statement for this example.

SELECT 'this is script1';

Here's it running:

sqlite> REPLACE INTO sqlar
   ...> SELECT * FROM v_toggle;
sqlite> .archive --extract run.sql
sqlite> .read run.sql
'this is script1'
this is script1
sqlite> create table flat(n);
sqlite> REPLACE INTO sqlar
   ...> SELECT * FROM v_toggle;
sqlite> .archive --extract run.sql
sqlite> .read run.sql
'this is script2'
this is script2

(9) By ET (EricTsau) on 2022-05-20 18:48:13 in reply to 1 [link] [source]

Heres another hacky way that fulfills "conditionally executes a set of command if table exists in one script". Though it uses a temporary script.

$ cat toggle.sql
.once /tmp/temp_script.sql
with table_exists(flat_table_exists) as (
        select exists(select 1 from sqlite_schema where type='table' and name='flat_table')
),
this_script(script) as (select readfile('toggle.sql'))
select
iif(flat_table_exists,
        substr(script,instr(script,char(0xa)||'.exit 69')+9,
                instr(script,char(0xa)||'.exit 79')-instr(script,char(0xa)||'.exit 69')-9),
        substr(script,instr(script,char(0xa)||'.exit 79')+9)
)'--query'
from table_exists,this_script;
.read /tmp/temp_script.sql
.exit 69
.print flat table to relational table section
.exit 79
.print relational table to flat table section

$ # when flat_table doesn't exist
$ sqlite3 database < toggle.sql
relational table to flat table section
$ # when flat_table exists
$ sqlite3 database < toggle.sql
flat table to relational table section