SQLite-Ivo

From Cheatsheet
Jump to navigationJump to search


This page was written and contributed by Ivo Palli.

General

An SQL solution that puts data into local files, without a server/client setup.

The source files are merged into a single C file called the amalgamation to easy embedding SQLite into your own program.

There are a number of optional components you can enable via defines:

  • FTS3 and FTS5 - Full Text Searching module
  • RTREE - An R-Tree is a special index that is designed for doing range queries
  • DBSTAT - A read-only table with info about the amount of disk space used to store the content of an SQLite database
  • JSON1 - Functions that are useful for managing JSON content stored in an SQLite database
  • RBU - Resumable Bulk Update. Efficient functions useful on low-power devices

Datatypes

  • NULL - The value is a NULL value.
  • INTEGER - The value is a signed integer, stored in 0, 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
  • REAL - The value is a floating point value, stored as an 8-byte IEEE floating point number.
  • TEXT - The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
  • BLOB - The value is a blob of data, stored exactly as it was input.

More here: https://www.sqlite.org/datatype3.html

Timestamps

There is no specialized data type for timestamp. You can store stuff as an integer with epoch.

A good default value for a timestamp field is:

ts integer(4) default (cast(strftime('%s','now') as int))

Installation

RHEL

yum install sqlite
sqlite3 --version

From source

wget https://www.sqlite.org/2022/sqlite-amalgamation-3390400.zip
unzip sqlite-amalgamation-*.zip
cd sqlite-amalgamation-*/

musl-gcc -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_DBSTAT_VTAB -Wall -g -O2 -c sqlite3.c
ar rcs libsqlite3.a sqlite3.o

musl-gcc -static -Wall -O2 -o shell shell.c -L. -lsqlite3
rlwrap ./shell
.help
.quit

Usage

Create a table

# https://www.sqlite.org/lang_createtable.html
cat << 'EOF' | sqlite3 lookup.db
CREATE TABLE IF NOT EXISTS projects(id TEXT PRIMARY KEY NOT NULL, name TEXT NOT NULL, added TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL);
.quit
EOF

Insert a row

# https://www.sqlite.org/lang_insert.html
cat << 'EOF' | sqlite3 lookup.db
INSERT INTO projects(id, name) VALUES('hello!', 'world');
.quit
EOF

Show a database table

# List tables within a database
sqlite3 scripting3.db ".header off" ".mode tabs" ".tables"

# Describe schema
sqlite3 lookup.db ".header off" ".mode tabs" ".schema usage"

Select

# Note that tables automatically have a 'rowid' field that auto-increments
# https://www.sqlite.org/lang_select.html
sqlite3 lookup.db ".mode tabs" "SELECT rowid,* FROM projects"

Join

# JOINing
sqlite3 lookup.db ".mode tabs" ".headers on" "SELECT * FROM usage u JOIN servers s ON u.server_id = s.id ORDER BY ts DESC LIMIT 1;"

Time

# Converting timestamp to epoch
SELECT strftime('%s', usage.ts) FROM usage;

# https://www.sqlite.org/lang_datefunc.html
select DATETIME('now');                                  -- UTC time
select DATETIME('now', 'localtime');                     -- Local time
select DATETIME('now', 'localtime', '-5 hours');         -- Local time, but 5 hours ago
select DATETIME(0, 'unixepoch');                         -- 1970-01-01 00:00:00

select strftime('%s');                                   -- Current time in epoch UTC
select strftime('%s', '2022-07-01 00:00');               -- Converts to epoch UTC
select strftime('%s', '2022-07-01 00:00', 'localtime');  -- Converts to epoch local time
select strftime('%s', '2022-07-01 00:00', 'utc');        -- Date is local time and you want the epoch in UTC
  1. When your timestamp field is already in seconds since epoch:

db 'select *,strftime("%Y-%m-%d %H:%M:%S", ts, "unixepoch") from usage where ts > strftime("%s", "now", "-7 minutes");' | csv </syntaxhighlight>

Timestamps

-- Set up a table
CREATE TABLE IF NOT EXISTS `config`(`key` TEXT NOT NULL UNIQUE, `value` TEXT, `ts` integer(4) default (cast(strftime('%s','now') as int)));

-- Insert a value, this sets the timestamp. This will also re-set the timestamp if the key/value already exists
INSERT OR REPLACE INTO `config`(`key`,`value`) VALUES('naam','ivo');

-- Query the value. Repeat this after a while to see no value returning
SELECT `value` FROM `config` WHERE `key` = 'naam' AND `ts` > strftime('%s', datetime('now', '-10 seconds'));

-- Update the timestamp
UPDATE `config` SET `ts` = strftime('%s', 'now') WHERE `key` = 'naam';

-- Repeat the query again after updating the timestamp

Select with multiple clauses

# Multiple clauses and last X hours
# https://stackoverflow.com/q/34283690/1007204
SELECT * from hypervisors WHERE hypervisor_hostname = 'node001' AND state = 'up' AND ts >= DATETIME('now','-3 hours')

Truncate a table

# Truncate a table
DELETE FROM users_new;

Select into JSON format

# Getting info from OpenStack
db '.mode json' 'select strftime("%Y-%m-%d %H:%M:%S", u.ts, "unixepoch") as clock, s.name as Server, u.vcpu as CPU, u.memory as Memory, u.disk as Disk, p.name as Project from usage u join servers s on u.server_id = s.id join projects p on s.project_id = p.id where u.ts > strftime("%s", "now", "-7 minutes");' | jq .

Dump a database

# Dumping a database
sqlite3 -batch test.db ".dump"

Wait for lock instead of immediate error

# Wait for database unlock instead of exiting immediately
sqlite3 -batch imdb.db "PRAGMA busy_timeout = 5000; select id from imdb" | wc -l                                       # Note, this will echo '5000' to stdout from the PRAGMA

sqlite3 lookup.db ".output /dev/null" "PRAGMA busy_timeout = 5000" ".output stdout" ".schema"                          # This will redirect the PRAGMA to /dev/null

echo ".schema" | sqlite3 lookup.db -cmd ".output /dev/null" -cmd "PRAGMA busy_timeout = 5000" -cmd ".output stdout"    # Execute the PRAGMA while still reading commands from stdin afterwards

Profiling queries

.eqp on          -- Turns on EXPLAIN for all queries
.timer on        -- Keeps track of real, user and system time taken
SELECT ....      -- Do some complex query

Adding a column to a table

ALTER TABLE servers ADD tags TEXT;
ALTER TABLE servers ADD flex INTEGER;

Remove a column from a table

ALTER TABLE servers DROP flex;

Inserting with values from another select

CREATE TABLE IF NOT EXISTS flex(server_id TEXT PRIMARY KEY NOT NULL, ts integer(4) default (cast(strftime('%s','now') as int)));
INSERT OR IGNORE INTO flex (server_id) SELECT id from servers WHERE tags LIKE '%"flex":"true"%';

Printing true or false depending if a field has a value

SELECT s.name,CASE WHEN f.server_id IS NULL THEN 'false' ELSE 'true' END AS flex
  FROM servers s
  LEFT JOIN flex f ON s.id = f.server_id
  WHERE s.project_id = '795d39ce5a5a42539463ccdcf6648b8c' AND s.recent = 1;

Converting one type to another

Here we are converting regular timestamps to seconds since epoch

-- Get the create statement for the original table
.schema users
-- CREATE TABLE users(id TEXT PRIMARY KEY NOT NULL, recent INTEGER NOT NULL, name TEXT NOT NULL, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL);

-- Start a transaction
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;

-- Alter the column
CREATE TABLE users_new(id TEXT PRIMARY KEY NOT NULL, recent INTEGER NOT NULL, name TEXT NOT NULL, ts integer(4) default (cast(strftime('%s','now') as int)));

-- Copy the data while converting
INSERT INTO users_new SELECT id,recent,name,strftime('%s', ts) FROM users;

-- Move table
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;

-- Finish up
COMMIT;
PRAGMA foreign_keys=on;

You can also copy a table into another database file via the ATTACH command.

REPLACE blanks values that you don't specify

rlwrap sqlite3 test.db
SQLite version 3.39.4 2022-09-29 15:55:41
Enter ".help" for usage hints.
sqlite> create table xx (a1 integer PRIMARY KEY, a2 integer, a3 integer);
sqlite> .schema
CREATE TABLE xx (a1 integer PRIMARY KEY, a2 integer, a3 integer);
sqlite> insert into xx(a1,a2,a3) values(1,2,3),(4,5,6),(7,8,9);
sqlite> select * from xx;
1|2|3
4|5|6
7|8|9
sqlite> replace into xx (a1,a2) values(1, 666);
sqlite> select * from xx;
1|666|      <-- last value is blanked
4|5|6
7|8|9

Code example

/*
musl-gcc -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_DBSTAT_VTAB -Wall -O2 -c sqlite3.c -lpthread -ldl   # Create object file
musl-gcc -Wall -O2 -o example example.c sqlite3.o
*/

#include "sqlite3.h"
#include <stdio.h>

#include "debug.c"


sqlite3 *db = NULL;
void close_db()
{
sqlite3_close(db);
}


int main(void)
{
sqlite3_stmt *res;
char *database_file = "example.db";
int rc;

debug_options.to_stdout = 1;
debug("Using SQLite %s", sqlite3_libversion());

atexit(close_db);

if(sqlite3_open(database_file, &db) != SQLITE_OK)
  die("Cannot open database %s: %s", database_file, sqlite3_errmsg(db));

if(sqlite3_prepare_v2(db, "SELECT SQLITE_VERSION()", -1, &res, 0) != SQLITE_OK)
  die("Failed to fetch data: %s", sqlite3_errmsg(db));

rc = sqlite3_step(res);
if(rc == SQLITE_ROW)
  printf("%s\n", sqlite3_column_text(res, 0));

sqlite3_finalize(res);

return 0;
}

// EOF

Links