SQLite
About SQLite
- SQLite is the most popular embedded relational database and probably the widely deployed software module.
Some features:
- The database is just a single file.
- It has an in-memory version for fast access to temporary data.
- No need for external server process.
- The client directly accesses the file containing the data.
- There is a command-line tool called
sqlite3to access the data. - There are several GUI tools to view and manipulate data.
- Most programming languages have a library to access an SQLite database.
- ACID - Atomic, Consistent, Isolated, and Durable
Documentation
Using SQLite in programming languages
Main features - where to use it
It is very fast for reading, but not so good for concurrent writing.
- Embedded in every web browser
- Embedded in many other applications.
- Cars etc.
Install SQLite command-line tool
Debian/Ubuntu Linux:
$ sudo apt install sqlite3
Windows
- Download
sqlite-tools-win-x64-3530000.zip(or a later version) that includes the “Command-line tools for Windows x64,”
macOS
- Use Homebrew
Version of SQLite
On Ubuntu 25.10:
$ sqlite3 --version
3.46.1 2024-08-13 09:16:08 c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69aalt1 (64-bit)
Downloaded from web site of SQLite
$ ~/bin/sqlite3 --version
3.53.0 2026-04-09 11:41:38 4525003a53a7fc63ca75c59b22c79608659ca12f0131f52c18637f829977f20b (64-bit)
Some of the examples only work on the most recent version.
GUI for SQLite
TODO
There are number of desktop and web-based GUI tools to be used with an SQLite database.
sudo apt install sqlitebrowser
SQLite dialect
- SQLite works according to the SQL92 standard.
- There are some SQLite specific extensions.
Data types
- Data types
- Flexible typing - SQLite does not really care about the types you defined for the columns. You can even create columns without types.
- STRICT tables - In special cases you can ask SQLite to enforce strict type-checking by setting the table to be
STRICT.
Basic types
- NULL
- INTEGER
- REAL
- TEXT
- BLOB
Conventions
SQL is case insensitive, but in order to make it easier to read some people follow the rule to always write every SQL command in capital letters and every user-defined values (e.g. names of the tables, columns etc.) in lower-case or maybe mixed case.
We’ll follow this convention in the examples.
SQLite interactive shell - .help and .quit
The command line tool has a number of internal commands starting with a dot. .help will show the list of the commands.
.quit or .exit will close the shell.
The commands can be given
- interactively
- sent through a pipe
- redirected from a file
$ sqlite3
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .help
...
sqlite> .quit
$
Help with SQLite CLI using a pipe
Pipe the command to the STDIN of sqlite3. Available on Linux and macOS and in the git-bash on Windows.
echo .help | sqlite3
Help with SQLite CLI using redirect
Redirect from an external file to the Standard Input (STDIN) of the sqlite3 command.
.help
$ sqlite3 < examples/help.sql
SQLite version
This is using the version I installed from SQLite on Ubuntu Linux.
Interactive
$ sqlite3
sqlite> .version
SQLite 3.53.0 2026-04-09 11:41:38 4525003a53a7fc63ca75c59b22c79608659ca12f0131f52c18637f829977f20b
zlib version 1.3.1
gcc-13.3.0 (64-bit)
sqlite> .quit
Piped to STDIN
$ echo .version | sqlite3
SQLite 3.53.0 2026-04-09 11:41:38 4525003a53a7fc63ca75c59b22c79608659ca12f0131f52c18637f829977f20b
zlib version 1.3.1
gcc-13.3.0 (64-bit)
Redirect from file
.version
$ sqlite3 < examples/version.sql
SQLite 3.53.0 2026-04-09 11:41:38 4525003a53a7fc63ca75c59b22c79608659ca12f0131f52c18637f829977f20b
zlib version 1.3.1
gcc-13.3.0 (64-bit)
Command line flag
$ sqlite3 --version
3.53.0 2026-04-09 11:41:38 4525003a53a7fc63ca75c59b22c79608659ca12f0131f52c18637f829977f20b (64-bit)
SQLite CLI - external commands .shell
To run any external (shell) command use .shell.
On Linux / macOS you have ls:
sqlite> .shell ls
On Windows you have dir:
sqlite> .shell dir
SQLite CLI - .schema and .tables
Using the .tables command we can see the list of tables and using the .schema command we can see the whole definition.
.schema
.tables
SQLite CLI - .mode
The .mode controls how results of SELECT statements are displayed.
Show the current mode
sqlite> .mode
.mode qbox --limits on --quote relaxed --sw auto --textjsonb on
List available modes
sqlite> .mode --list
available modes: ascii box c column count csv html insert jatom jobject json
line list markdown off psql qbox quote split table tabs tcl batch tty
Set a mode:
sqlite> .mode column
SQLite in-memory or file-based
- Using in-memory database
$ sqlite3
- Using a persistent database file. If the file does not exist, this command will create it.
$ sqlite3 my.db
DDL - Data Definition Language
- CREATE TABLE
- ALTER TABLE
- DROP TABLE
CREATE TABLE
CREATE TABLE person (
id INTEGER,
name TEXT
);
.schema
$ sqlite < examples/create-table.sql
CREATE TABLE person (
id INTEGER,
name TEXT
);
ALTER TABLE
Using ALTER TABLE we can modify the schema.
CREATE TABLE person (
id INTEGER,
name TEXT
);
.schema
ALTER TABLE person ADD COLUMN phone TEXT;
.schema
$ sqlite3 < examples/alter-table.sql
CREATE TABLE person (
id INTEGER,
name TEXT
);
CREATE TABLE person (
id INTEGER,
name TEXT
, phone TEXT);
DROP TABLE
DROP TABLE will remove the table and all its content. In this example we also used the .schema command to show what is the current schema
and the .mode batch to make the SELECT statement show a simple line.
.mode batch
CREATE TABLE person (
id INTEGER,
name TEXT
);
SELECT '-------';
.schema
DROP TABLE person;
SELECT '-------';
.schema
SELECT '-------';
$ sqlite < examples/drop-table.sql
-------
CREATE TABLE person (
id INTEGER,
name TEXT
);
-------
-------
DML - Data Manipulation Language - Basic Operations
- CREATE
- INSERT
- SELECT
- UPDATE
CREATE - INSERT - SELECT
With CREATE we define the schema.
With INSERT we add a row.
With SELECT we can fetch data from the database.
CREATE TABLE person (
id INTEGER,
name TEXT,
email TEXT
);
INSERT INTO person (id, name, email) VALUES (1, 'Joe', 'joe@example.com');
INSERT INTO person (id, name, email) VALUES (2, 'Jane', 'jane@example.com');
SELECT * FROM person;
In memory:
$ sqlite3 < create-insert-select.sql
1|Joe|joe@example.com
2|Jane|jane@example.com
In file:
$ sqlite3 demo.db < create-insert-select.sql
1|Joe|joe@example.com
2|Jane|jane@example.com
Then, if we don’t need it any more, we should remove the database file:
$ rm -f demo.db
SELECT only some of the columns
Instead of using a * to fetch all the columns, we can define the specific columns we would like to fetch.
CREATE TABLE person (
id INTEGER,
name TEXT,
email TEXT
);
INSERT INTO person (id, name, email) VALUES (1, 'Joe', 'joe@example.com');
INSERT INTO person (id, name, email) VALUES (2, 'Jane', 'jane@example.com');
INSERT INTO person (id, name, email) VALUES (3, 'Mary', 'mary@example.com');
INSERT INTO person (id, name, email) VALUES (4, 'Peter', 'peter@example.com');
SELECT id, name FROM person;
$ sqlite3 < examples/select-some-columns.sql
1|Joe
2|Jane
3|Mary
4|Peter
SELECT only some of the rows - WHERE clause
We can also use the WHERE close to select which rows we would like to see.
CREATE TABLE person (
id INTEGER,
name TEXT,
email TEXT
);
INSERT INTO person (id, name, email) VALUES (1, 'Joe', 'joe@example.com');
INSERT INTO person (id, name, email) VALUES (2, 'Jane', 'jane@example.com');
INSERT INTO person (id, name, email) VALUES (3, 'Mary', 'mary@example.com');
INSERT INTO person (id, name, email) VALUES (4, 'Peter', 'peter@example.com');
SELECT * FROM person WHERE id > 2;
$ sqlite < examples/select-some-rows.sql
3|Mary|mary@example.com
4|Peter|peter@example.com
AUTOINCREMENT
Keeping in mind to increase the id number can be annoying. SQL provides the AUTOINCREMENT option
that can help us maintain that value. However in SQLite it is not necessary to use and it isn’t even recommended.
In any case let’s see an example:
CREATE TABLE person (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT
);
INSERT INTO person (name, email) VALUES ('Joe', 'joe@example.com');
INSERT INTO person (name, email) VALUES ('Jane', 'jane@example.com');
INSERT INTO person (name, email) VALUES ('Mary', 'mary@example.com');
INSERT INTO person (name, email) VALUES ('Peter', 'peter@example.com');
SELECT * from person;
.schema
$ sqlite3 < examples/autoincrement.sql
1|Joe|joe@example.com
2|Jane|jane@example.com
3|Mary|mary@example.com
4|Peter|peter@example.com
CREATE TABLE person (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT
);
CREATE TABLE sqlite_sequence(name,seq);
Invisible AUTOINCREMENT (ROWID)
If we have a column that is marked as INTEGER PRIMARY KEY, it will get incremented automatically. See the AUTOINCREMENT description for details.
CREATE TABLE person (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
);
INSERT INTO person (name, email) VALUES ('Joe', 'joe@example.com');
INSERT INTO person (name, email) VALUES ('Jane', 'jane@example.com');
INSERT INTO person (name, email) VALUES ('Mary', 'mary@example.com');
INSERT INTO person (name, email) VALUES ('Peter', 'peter@example.com');
SELECT * from person;
.schema
$ sqlite3 < examples/invisible-autoincrement.sql
1|Joe|joe@example.com
2|Jane|jane@example.com
3|Mary|mary@example.com
4|Peter|peter@example.com
CREATE TABLE person (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
);
Missing value
When we insert data, some columns (besides the id) might be missing. SQLite will insert a NULL value.
CREATE TABLE qa (
question TEXT,
answer TEXT
);
INSERT INTO qa (question, answer) VALUES ('Language?', 'SQL');
INSERT INTO qa (question, answer) VALUES ('Database?', 'SQLite');
INSERT INTO qa (question) VALUES ('Meaning of life?');
SELECT * from qa;
$ sqlite3 < examples/missing-text-value.sql
Language?|SQL
Database?|SQLite
Meaning of life?|
This is hard to see in the above display, but in the interactive shell or if we use a real terminal it will look much better:
╭──────────────────┬────────╮
│ question │ answer │
╞══════════════════╪════════╡
│ Language? │ SQL │
│ Database? │ SQLite │
│ Meaning of life? │ NULL │
╰──────────────────┴────────╯
Missing numerical value
CREATE TABLE qa (
question TEXT,
answer INTEGER
);
INSERT INTO qa (question, answer) VALUES ('2+2', 4);
INSERT INTO qa (question, answer) VALUES ('2-2', 0);
INSERT INTO qa (question) VALUES ('Meaning of life?');
SELECT * from qa;
$ sqlite3 < examples/missing-integer-value.sql
2+2|4
2-2|0
Meaning of life?|
╭──────────────────┬────────╮
│ question │ answer │
╞══════════════════╪════════╡
│ 2+2 │ 4 │
│ 2-2 │ 0 │
│ Meaning of life? │ NULL │
╰──────────────────┴────────╯
Field with DEFAULT value
We can set up a DEFAULT value to each field and if the INSERT does not set the value then this default value will be set.
However, if the INSERT explicitly sets NULL then that will be the value.
CREATE TABLE qa (
question TEXT,
answer TEXT DEFAULT 42
);
INSERT INTO qa (question, answer) VALUES ('Language?', 'SQL');
INSERT INTO qa (question, answer) VALUES ('Database?', 'SQLite');
INSERT INTO qa (question) VALUES ('Meaning of life?');
INSERT INTO qa (question, answer) VALUES ('What is void?', NULL);
SELECT * from qa;
$ sqlite3 < examples/default-value.sql
Language?|SQL
Database?|SQLite
Meaning of life?|42
What is void?|
╭──────────────────┬────────╮
│ question │ answer │
╞══════════════════╪════════╡
│ Language? │ SQL │
│ Database? │ SQLite │
│ Meaning of life? │ '42' │
│ What is void? │ NULL │
╰──────────────────┴────────╯
Default time
See the SQLite Keywords for DEFAULT_TIMESTAMP and other similar keywords.
CREATE TABLE login (
uid INTEGER NOT NULL,
logged_in DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO login (uid) VALUES (1);
INSERT INTO login (uid, logged_in) VALUES (2, '2026-04-29 08:06:36');
SELECT * from login;
$ sqlite3 < examples/default-time.sql
1|2026-05-05 06:28:37
2|2026-04-29 08:06:36
NOT NULL constraint
Here we have two tables. In the restricted table we set one of the fields to be NOT NULL.
CREATE TABLE plain (
id INTEGER,
name TEXT
);
INSERT INTO plain (id, name) VALUES (1, 'Joe');
INSERT INTO plain (name) VALUES ('Jane');
SELECT * from plain;
CREATE TABLE restricted (
id INTEGER NOT NULL,
name TEXT
);
INSERT INTO restricted (id, name) VALUES (1, 'Joe');
INSERT INTO restricted (name) VALUES ('Jane');
INSERT INTO restricted (id, name) VALUES (NULL, 'Mary');
SELECT * from restricted;
$ sqlite3 < examples/not-null.sql
1|Joe
|Jane
Error near line 18: NOT NULL constraint failed: restricted.id
Error near line 19: NOT NULL constraint failed: restricted.id
1|Joe
UPDATE
With UPDATE we can change one or more fields in one or more rows.
.mode batch
CREATE TABLE people (
name TEXT,
grade INTEGER
);
INSERT INTO people (name, grade) VALUES ('Joe', 40);
INSERT INTO people (name, grade) VALUES ('Jane', 60);
SELECT * from people;
SELECT '-------';
-- SELECT 'Setting the grade of Joe to be 44';
UPDATE people SET grade = 44;
SELECT * from people;
SELECT '-------';
UPDATE people SET grade = 100 WHERE name = 'Joe';
SELECT * from people;
SELECT '-------';
$ sqlite < examples/update.sql
Joe|40
Jane|60
-------
Joe|44
Jane|44
-------
Joe|100
Jane|44
-------
SELECT without table
We can use SELECT event without tables to use SQLite as bloated calculator or to demonstrate some of the functions available.
SELECT 19 + 23
$ sqlite3 < examples/add.sql
42
SELECT 'Hello World'
$ sqlite3 < examples/text.sql
Hello World
SELECT date and time
SELECT 'date', date();
SELECT 'time', time();
SELECT 'datetime', datetime();
SELECT 'julianday',julianday();
SELECT 'unixepoch', unixepoch();
SELECT 'strftime', strftime();
SELECT 'strftime with param', strftime('%Y-%m-%d %H:%M:%S');
--SELECT timediff('%Y');
$ sqlite3 < examples/date-and-time.sql
date|2026-05-04
time|13:59:26
datetime|2026-05-04 13:59:26
julianday|2461165.0829416784
unixepoch|1777903166
strftime|
strftime with param|2026-05-04 13:59:26
SELECT functions
SELECT abs(-42);
SELECT random();
SELECT upper('aBcdEf');
42
9185603501169008873
ABCDEF
SELECT mathematical functions
Built-In Mathematical SQL Functions
SELECT ceil(3.1);
SELECT ceil(-3.1);
SELECT floor(3.8);
SELECT floor(-3.8);
SELECT trunc(3.8);
SELECT trunc(-3.8);
$ sqlite3 < examples/math.sql
SELECT ceil(3.1);
SELECT ceil(-3.1);
SELECT floor(3.8);
SELECT floor(-3.8);
SELECT trunc(3.8);
SELECT trunc(-3.8);
Data type enforcement
SQLite employes flexible data types, but allows use to opt-in the STRICT handling of data types.
Flexible typing
The Advantages Of Flexible Typing
Here, although we declared the types of the fields, we can insert any type of value.
CREATE TABLE flexible (
id INTEGER,
name TEXT,
height REAL
);
INSERT INTO flexible (id, name, height) VALUES (1, 'foo', 1.8);
INSERT INTO flexible (id, name, height) VALUES ('id', 23, 'tall');
SELECT * from flexible;
$ sqlite3 < examples/flexible-data.sql
1|foo|1.8
id|23|tall
STRICT Tables
If you really want, you can defined a table to be STRICT, but you need to do that on a per-table basis and the data types you can use are limited.
- INT
- INTEGER
- REAL
- TEXT (UTF-8)
- BLOB (for binary)
- ANY
CREATE TABLE strict_data (
id INTEGER,
name TEXT,
height REAL
) STRICT;
INSERT INTO strict_data (id, name, height) VALUES (1, 'foo', 1.8);
INSERT INTO strict_data (id, name, height) VALUES ('2', 'bar', '2.1');
INSERT INTO strict_data (id, name, height) VALUES ('three', 'Jane', 1.3);
-- Runtime error near line 10: cannot store TEXT value in INTEGER column strict_data.id (19)
SELECT * from strict_data;
$ sqlite < examples/strict-table.out
Error near line 10: cannot store TEXT value in INTEGER column strict_data.id
1|foo|1.8
2|bar|2.1
VARCHAR
You can define a column to be VARCHAR(10), but SQLite will totally disregard it.
It can be still useful if your application code can rely on it or if at one point you
might want to migrate to another database respects such definitions.
You can’t use this in a STRICT table.
CREATE TABLE people (
name TEXT,
phone VARCHAR(10)
);
INSERT INTO people VALUES ('John', '1234567890-123456789');
SELECT * FROM people;
$ sqlite3 < examples/varchar.sql
John|1234567890-123456789
Import data from CSV
We are going to see two ways to import data from CSV.
First we let SQLite create the table based on the first row in the CSV file.
Then we’ll create the table ourselves setting the column types as we prefer and then import the CSV file skipping the first row.
Import planets.csv
Planet name,Distance (AU),Mass
Mercury,0.4,0.055
Venus,0.7,0.815
Earth,1,1
Mars,1.5,0.107
Ceres,2.77,0.00015
Jupiter,5.2,318
Saturn,9.5,95
Uranus,19.6,14
Neptune,30,17
Pluto,39,0.00218
Charon,39,0.000254
.mode batch
.import --csv -v examples/planets.csv planets
SELECT '----------';
.schema
SELECT '----------';
SELECT * FROM planets;
SELECT "Distance (AU)" FROM planets;
$ sqlite3 < examples/import-planets.sql
Column separator ",", row separator "\n"
CREATE TABLE "main"."planets"(
"Planet name" ANY, "Distance (AU)" ANY, "Mass" ANY)
Added 11 rows with 0 errors using 12 lines of input
----------
CREATE TABLE "planets"(
"Planet name" ANY, "Distance (AU)" ANY, "Mass" ANY);
----------
Mercury|0.4|0.055
Venus|0.7|0.815
Earth|1|1
Mars|1.5|0.107
Ceres|2.77|0.00015
Jupiter|5.2|318
Saturn|9.5|95
Uranus|19.6|14
Neptune|30|17
Pluto|39|0.00218
Charon|39|0.000254
0.4
0.7
1
1.5
2.77
5.2
9.5
19.6
30
39
39
Load schema and import planets.csv
.mode batch
.read examples/planets.sql
.import --csv --skip 1 -v examples/planets.csv planets
SELECT '----------';
.schema
SELECT '----------';
SELECT * FROM planets;
SELECT "Distance (AU)" FROM planets;
$ sqlite3 < exampes/create-and-import-planets.sql
Column separator ",", row separator "\n"
Added 11 rows with 0 errors using 12 lines of input
----------
CREATE TABLE planets (
"Planet name" TEXT,
"Distance (AU)" REAL,
Mass REAL
);
----------
Mercury|0.4|0.055
Venus|0.7|0.815
Earth|1.0|1.0
Mars|1.5|0.107
Ceres|2.77|0.00015
Jupiter|5.2|318.0
Saturn|9.5|95.0
Uranus|19.6|14.0
Neptune|30.0|17.0
Pluto|39.0|0.00218
Charon|39.0|0.000254
0.4
0.7
1.0
1.5
2.77
5.2
9.5
19.6
30.0
39.0
39.0
Import iris.csv
.mode batch
.import --csv -v examples/iris.csv iris
SELECT '------';
.schema
SELECT '------';
$ sqlite3 < examples/import-iris.sql
Column separator ",", row separator "\n"
CREATE TABLE "main"."iris"(
"Id" ANY, "SepalLengthCm" ANY, "SepalWidthCm" ANY, "PetalLengthCm" ANY,
"PetalWidthCm" ANY, "Species" ANY)
Added 150 rows with 0 errors using 151 lines of input
------
CREATE TABLE "iris"(
"Id" ANY, "SepalLengthCm" ANY, "SepalWidthCm" ANY, "PetalLengthCm" ANY,
"PetalWidthCm" ANY, "Species" ANY);
------
╭──────────╮
│ COUNT(*) │
╞══════════╡
│ 150 │
╰──────────╯
╭────────────────────╮
│ AVG(SepalLengthCm) │
╞════════════════════╡
│ 5.8433333333333337 │
╰────────────────────╯
╭────────────────────╮
│ AVG(PetalLengthCm) │
╞════════════════════╡
│ 3.7586666666666662 │
╰────────────────────╯
╭────────────────────┬─────────────────╮
│ AVG(PetalLengthCm) │ Species │
╞════════════════════╪═════════════════╡
│ 1.464 │ Iris-setosa │
│ 4.26 │ Iris-versicolor │
│ 5.5520000000000005 │ Iris-virginica │
╰────────────────────┴─────────────────╯
Load schema and Import iris.csv
CREATE TABLE iris (
Id INTEGER,
SepalLengthCm REAL,
SepalWidthCm REAL,
PetalLengthCm REAL,
PetalWidthCm REAL,
Species TEXT
);
sqlite> .schema
sqlite> .read iris.sql
sqlite> .import --csv --skip 1 -v iris.csv iris
Added 150 rows with 0 errors using 151 lines of input
sqlite> .schema
CREATE TABLE iris (
Id INTEGER,
SepalLengthCm REAL,
SepalWidthCm REAL,
PetalLengthCm REAL,
PetalWidthCm REAL,
Species TEXT
);
LIMIT the number of rows
.import --csv examples/iris.csv iris
SELECT * FROM iris LIMIT 3;
sqlite3 < examples/limit-iris.sql
1|5.1|3.5|1.4|0.2|Iris-setosa
2|4.9|3|1.4|0.2|Iris-setosa
3|4.7|3.2|1.3|0.2|Iris-setosa
Aggregate: COUNT
.mode batch
.import --csv examples/iris.csv iris
SELECT COUNT(*) FROM iris;
SELECT COUNT(*) FROM iris WHERE Species == 'Iris-virginica';
$ sqlite3 < examples/count-iris.sql
150
50
Aggregate: AVG
.mode batch
.import --csv examples/iris.csv iris
SELECT AVG(SepalLengthCm) FROM iris;
SELECT AVG(PetalLengthCm) FROM iris;
SELECT AVG(PetalLengthCm), Species FROM iris GROUP BY Species;
$ sqlite3 < examples/averages-iris.sql
5.8433333333333337
3.7586666666666662
1.464|Iris-setosa
4.26|Iris-versicolor
5.5520000000000005|Iris-virginica
DISTINCT
.import --csv examples/iris.csv iris
SELECT COUNT(species) FROM iris;
SELECT COUNT(DISTINCT species) FROM iris;
$ sqlite3 < examples/distinct-iris.sql
150
3
Aggregate: COUNT, AVG, MAX, MIN
CREATE TABLE person (
name TEXT,
subject TEXT,
grade INTEGER
);
INSERT INTO person (name, subject, grade) VALUES
('Jane', 'Chemistry', 62),
('Jane', 'Physics', 70),
('Jane', 'Math', 23),
('Joe', 'Chemistry', 45),
('Joe', 'Physics', 85),
('Joe', 'Math', 77),
('Peter', 'Chemistry', 35),
('Peter', 'Physics', 63),
('Mary', 'Chemistry', 95),
('Dean', 'Physics', 100),
('Dana', 'Math', 97),
('George', 'Math', 97),
('George', 'Chemistry', 45);
SELECT * FROM person;
SELECT COUNT(*) FROM person;
SELECT 'Total', COUNT(*) FROM person;
SELECT 'Physycs', COUNT(*) FROM person WHERE subject == 'Physics';
SELECT 'Failed', COUNT(*) FROM person WHERE grade < 60;
SELECT 'Maximum all', MAX(grade) FROM person;
SELECT 'Maximum Chemistry', MAX(grade) FROM person WHERE subject == 'Chemistry';
SELECT 'Average all', AVG(grade) FROM person;
SELECT 'Average Chemistry', AVG(grade) FROM person WHERE subject == 'Chemistry';
SELECT 'Average Physics', AVG(grade) FROM person WHERE subject == 'Physics';
$ cat examples/grades.sql examples/aggregate.sql | sqlite3
Jane|Chemistry|62
Jane|Physics|70
Jane|Math|23
Joe|Chemistry|45
Joe|Physics|85
Joe|Math|77
Peter|Chemistry|35
Peter|Physics|63
Mary|Chemistry|95
Dean|Physics|100
Dana|Math|97
George|Math|97
George|Chemistry|45
13
Total|13
Physycs|4
Failed|4
Maximum all|100
Maximum Chemistry|95
Average all|68.769230769230774
Average Chemistry|56.4
Average Physics|79.5
Increment value
CREATE TABLE people (
name TEXT,
grade INTEGER
);
INSERT INTO people (name, grade) VALUES ('Joe', 40);
INSERT INTO people (name, grade) VALUES ('Jane', 60);
SELECT * from people;
SELECT '-------';
SELECT 'Incrementing the grade of Joe by 1';
UPDATE people SET grade = (SELECT grade FROM people WHERE name = 'Joe') + 1 WHERE name = 'Joe';
SELECT * from people;
SELECT '-------';
SELECT 'Incrementing the grade of every person by 5';
UPDATE people SET grade = grade + 5;
SELECT * from people;
SELECT '-------';
sqmlite3 < examples/increment.sql
Joe|40
Jane|60
-------
Incrementing the grade of Joe by 1
Joe|41
Jane|60
-------
Incrementing the grade of every person by 5
Joe|46
Jane|65
-------
Constraints
CHECK, UNIQUE, NOT NULL
This example shows all 3 constraint types.
CREATE TABLE grades (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
value INTEGER,
CHECK (value IS NULL OR (value BETWEEN 0 AND 100))
);
INSERT INTO grades (name, value) VALUES ('Joe', 100);
INSERT INTO grades (name, value) VALUES ('Jane', 200);
INSERT INTO grades (name, value) VALUES ('Mary', NULL);
INSERT INTO grades (name) VALUES ('Peter');
INSERT INTO grades (name, value) VALUES ('Zorg', 'hello');
SELECT * FROM grades;
$sqlite3 < examples/range-constraint.sql
Error near line 9: CHECK constraint failed: value IS NULL OR (value BETWEEN 0 AND 100)
Error near line 12: CHECK constraint failed: value IS NULL OR (value BETWEEN 0 AND 100)
1|Joe|100
2|Mary|
3|Peter|
Multi-column UNIQUE-ness constraint
Sometimes the each column can have the same value more than once, but 2 (or more column) put together must be unique. For example in this table we are storing coordinates of points. Both x and y can be any value and the same value can appear multiple times in either x or y.
However the (x, y) pair must be unique.
Here is how we can accomplish that.
CREATE TABLE points (
x INTEGER,
y INTEGER
);
CREATE UNIQUE INDEX xy_index ON points (x, y);
INSERT INTO points (x, y) VALUES (2, 3);
INSERT INTO points (x, y) VALUES (4, 5);
INSERT INTO points (x, y) VALUES (2, 3);
SELECT * FROM points;
$ sqlite3 < examples/points.sql
Error near line 10: UNIQUE constraint failed: points.x, points.y
2|3
4|5
Database normalization
- First Normal Form (1NF) - every column should store only one value.
- Second Normal Form (2NF) - the same value should not be repeated in the column.
- Third Normal Form (3NF)
- BCNF
Multivalue fields
CREATE TABLE grades (
student TEXT,
subjects TEXT,
grades TEXT
);
INSERT INTO grades (student, subjects, grades) VALUES ('Joe', 'Math,Chemistry,Programming', '27,89,32');
INSERT INTO grades (student, subjects, grades) VALUES ('Jane', 'Math,Literature,Physics', '99,100,97');
SELECT * FROM grades;
$ sqlite < examples/multivalue-fields.sql
Joe|Math,Chemistry,Programming|27,89,32
Jane|Math,Literature,Physics|99,100,97
╭─────────┬────────────────────────────┬───────────╮
│ student │ subjects │ grades │
╞═════════╪════════════════════════════╪═══════════╡
│ Joe │ Math,Chemistry,Programming │ 27,89,32 │
│ Jane │ Math,Literature,Physics │ 99,100,97 │
╰─────────┴────────────────────────────┴───────────╯
Flat table
CREATE TABLE person (
name TEXT,
subject TEXT,
grade INTEGER
);
INSERT INTO person (name, subject, grade) VALUES
('Jane', 'Chemistry', 62),
('Jane', 'Physics', 70),
('Jane', 'Math', 23),
('Joe', 'Chemistry', 45),
('Joe', 'Physics', 85),
('Joe', 'Math', 77),
('Peter', 'Chemistry', 35),
('Peter', 'Physics', 63),
('Mary', 'Chemistry', 95),
('Dean', 'Physics', 100),
('Dana', 'Math', 97),
('George', 'Math', 97),
('George', 'Chemistry', 45);
SELECT * FROM person;
Two tables
-- enforce foreignk key integrity
PRAGMA foreign_keys = ON;
CREATE TABLE subject (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE person (
name TEXT,
subject_id INTEGER REFERENCES subject(id),
grade INTEGER
);
INSERT INTO subject (id, name) VALUES
(1, 'Chemistry'),
(2, 'Physics'),
(3, 'Math');
INSERT INTO person (name, subject_id, grade) VALUES
('Jane', 1, 62),
('Jane', 2, 70),
('Jane', 3, 23),
('Joe', 1, 45),
('Joe', 2, 85),
('Joe', 3, 77),
('Peter', 1, 35),
('Peter', 2, 63),
('Mary', 1, 95),
('Dean', 2, 100),
('Dana', 3, 97),
('Gerorge', 3, 97),
('Gerorge', 1, 45);
SELECT * FROM subject;
SELECT * FROM person;
SELECT person.name, subject.name AS subject, person.grade
FROM person, subject
WHERE person.subject_id = subject.id;
SELECT person.name, subject.name AS subject, person.grade
FROM person JOIN subject
WHERE person.subject_id = subject.id;
SELECT person.name, subject.name AS subject, person.grade
FROM person INNER JOIN subject
WHERE person.subject_id = subject.id;
FOREIGN KEY
-- turn on FOREIGN KEY checking
PRAGMA foreign_keys = ON;
CREATE TABLE people (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
CREATE TABLE groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
manager INTEGER,
FOREIGN KEY(manager) REFERENCES people(id)
);
INSERT INTO people (name) VALUES ('Natalie Portman ');
INSERT INTO people (name) VALUES ('Gal Gadot');
INSERT INTO people (name) VALUES ('Cole Lawrence');
INSERT INTO people (name) VALUES ('Lior Raz');
INSERT INTO people (name) VALUES ('Erick Tryzelaar');
INSERT INTO people (name) VALUES ('Ernest Kissiedu');
SELECT * FROM people;
SELECT "-----";
INSERT INTO groups (name, manager) VALUES ('Rust London', (SELECT id FROM people WHERE name = 'Ernest Kissiedu'));
INSERT INTO groups (name, manager) VALUES ('Rust NYC', (SELECT id FROM people WHERE name = 'Cole Lawrence'));
INSERT INTO groups (name, manager) VALUES ('Rust Bay Area', (SELECT id FROM people WHERE name = 'Erick Tryzelaar'));
INSERT INTO groups (name, manager) VALUES ('Other Group', 42);
SELECT * FROM people;
SELECT "";
SELECT * FROM groups;
SELECT "-----";
DELETE FROM people WHERE name = 'Cole Lawrence';
SELECT * FROM people;
SELECT "";
SELECT * FROM groups;
SELECT "-----";
PRAGMA
Family
PRAGMA foreign_keys = ON;
CREATE TABLE people (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
father INTEGER,
mother INTEGER,
FOREIGN KEY (father) REFERENCES people(id),
FOREIGN KEY (mother) REFERENCES people(id)
);
INSERT INTO people (name) VALUES
('Abraham'),
('Hagar'),
('Sarah'),
('Keturah'),
('Ishmael'),
('Isaac'),
('Rebecca'),
('Jacob');
UPDATE people SET
father = (SELECT id FROM people WHERE name = 'Abraham'),
mother =(SELECT id FROM people WHERE name = 'Sarah')
WHERE name = 'Isaac';
UPDATE people SET
father = (SELECT id FROM people WHERE name = 'Abraham'),
mother =(SELECT id FROM people WHERE name = 'Hagar')
WHERE name = 'Ishmael';
UPDATE people SET
father = (SELECT id FROM people WHERE name = 'Isaac'),
mother =(SELECT id FROM people WHERE name = 'Rebecca')
WHERE name = 'Jacob';
UPDATE people SET father = 32;
SELECT child.name AS Name, father.name AS Father, mother.name AS Mother
FROM people child, people father, people mother
WHERE father.id = child.father AND mother.id = child.mother
-- ORDER BY child.id;
ORDER BY child.name;
-- SELECT child.name AS Name,
-- IFNULL(father.name, 'NULL') AS Father,
-- IFNULL(mother.name, 'NULL') AS Mother
-- FROM people AS child
-- LEFT JOIN people AS father ON father.id = child.father
-- LEFT JOIN people AS mother ON mother.id = child.mother
-- ORDER BY child.id;
--
-- CREATE VIEW family AS
-- SELECT child.name AS Name,
-- IFNULL(father.name, 'NULL') AS Father,
-- IFNULL(mother.name, 'NULL') AS Mother
-- FROM people AS child
-- LEFT JOIN people AS father ON father.id = child.father
-- LEFT JOIN people AS mother ON mother.id = child.mother
-- ORDER BY child.id;
--
-- SELECT * FROM family;
--
-- INSERT INTO people (name) VALUES ('Gabor');
-- SELECT * FROM family;
Flat grades
Student,Math,Chemistry,Biology,Physics,Literature,Sport,Drawing
Joe,100,80,,30,,,
Jane,90,92,100,,,,
Mary,,70,,48,51,97,20
CREATE TABLE IF NOT EXISTS grades (
student TEXT NOT NULL UNIQUE,
math INTEGER,
chemistry INTEGER,
biology INTEGER,
physics INTEGER,
literature INTEGER,
sport INTEGER,
drawing INTEGER,
id INTEGER PRIMARY KEY
);
.import --csv --skip 1 examples/flat-grades.csv grades
SELECT * FROM grades;
---------------------------
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
SELECT id, student from grades ORDER BY id;
INSERT INTO students SELECT id, student FROM grades;
SELECT * FROM students ORDER BY id;
CREATE TABLE subjects (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
SELECT name FROM pragma_table_info('grades') WHERE name != 'id' AND name != 'student';
INSERT INTO subjects (name) SELECT name FROM pragma_table_info('grades') WHERE name != 'id' AND name != 'student';
SELECT * FROM subjects;
CREATE TABLE new_grades (
student INTEGER REFERENCES students(id),
subject INTEGER REFERENCES subjects(id),
grade INTEGER
);
CREATE UNIQUE INDEX new_grades_index ON new_grades (student, subject);
INSERT INTO new_grades (student, subject, grade)
SELECT students.id, subjects.id, flat_grades.grade
FROM (
SELECT student, 'math' AS subject, math AS grade FROM grades
UNION ALL
SELECT student, 'chemistry' AS subject, chemistry AS grade FROM grades
UNION ALL
SELECT student, 'biology' AS subject, biology AS grade FROM grades
UNION ALL
SELECT student, 'physics' AS subject, physics AS grade FROM grades
UNION ALL
SELECT student, 'literature' AS subject, literature AS grade FROM grades
UNION ALL
SELECT student, 'sport' AS subject, sport AS grade FROM grades
UNION ALL
SELECT student, 'drawing' AS subject, drawing AS grade FROM grades
) AS flat_grades
JOIN students ON students.name = flat_grades.student
JOIN subjects ON subjects.name = flat_grades.subject
WHERE flat_grades.grade IS NOT NULL AND flat_grades.grade != '';
SELECT students.name AS student, subjects.name AS subject, new_grades.grade
FROM new_grades
JOIN students ON students.id = new_grades.student
JOIN subjects ON subjects.id = new_grades.subject
ORDER BY students.id, subjects.id;
Transactions
SQLite Transaction - in a bank
- Setup Bank accounts with some initial money.
- Move some money from one account to another - two separate steps. Worked.
- Move some money from one account to another - two separate steps - stop in the middle. Failed.
- Move some money from one account to another - Transaction - stop in the middle. Worked. (money stayed where it was)
- Remove bank
CREATE TABLE bank (
name TEXT PRIMARY KEY,
balance INTEGER NOT NULL
);
INSERT INTO bank (name, balance) VALUES ("Jane", 0);
INSERT INTO bank (name, balance) VALUES ("Mary", 1000);
INSERT INTO bank (name, balance) VALUES ("Ann", 1000);
SELECT * FROM bank;
SELECT "Total", SUM(balance) FROM bank;
SELECT "-----";
UPDATE bank SET balance = (SELECT balance FROM bank WHERE name = "Mary") - 100 WHERE name = "Mary";
UPDATE bank SET balance = (SELECT balance FROM bank WHERE name = "Jane") + 100 WHERE name = "Jane";
UPDATE bank SET balance = (SELECT balance FROM bank WHERE name = "Mary") - 100 WHERE name = "Mary";
.exit
UPDATE bank SET balance = (SELECT balance FROM bank WHERE name = "Jane") + 100 WHERE name = "Jane";
BEGIN TRANSACTION;
UPDATE bank SET balance = (SELECT balance FROM bank WHERE name = "Mary") - 100 WHERE name = "Mary";
.exit
UPDATE bank SET balance = (SELECT balance FROM bank WHERE name = "Jane") + 100 WHERE name = "Jane";
COMMIT;
sqlite3 bank.db < setup_bank.sql
sqlite3 bank.db < show.sql
sqlite3 bank.db < transfer.sql
sqlite3 bank.db < show.sql
sqlite3 bank.db < without_transaction.sql
sqlite3 bank.db < show.sql
sqlite3 bank.db < with_transaction.sql
sqlite3 bank.db < show.sql
rm -f bank.db
Jane|0
Mary|1000
Ann|1000
Total|2000
-----
Jane|100
Mary|900
Ann|1000
Total|2000
-----
Jane|100
Mary|800
Ann|1000
Total|1900
-----
Jane|100
Mary|800
Ann|1000
Total|1900
-----
- TODO: loading a large CSV file into the database and running queries.
- TODO: creating a multi-tabe database, dumping it and then loading it and running queries against it.
- TODO: FOREIGN KEY - cascading deletition?
ALTER TABLE
As we make progress with our application we might need to change the schema. For example we might need to add a new field to a table.
ALTER TABLE after adding data
Here we have a table with two fields (id and name) and with some data in it. Then we inserted some data.
Then we execute ALTER TABLE to add another column.
After that we can add new rows, this time already supplying the new field as well.
The old rows will have NULL in the new field.
We can also update the old rows to have some value in the field using the UPDATE command.
CREATE TABLE person (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
INSERT INTO person (name) VALUES ('Joe');
INSERT INTO person (name) VALUES ('Jane');
SELECT * FROM person;
ALTER TABLE person ADD COLUMN phone TEXT;
SELECT * FROM person;
INSERT INTO person (name, phone) VALUES ('Marcus', '1234565');
SELECT * FROM person;
sqlite3 < exmples/alter-table-after-adding-data.sql
1|Joe
2|Jane
1|Joe|
2|Jane|
1|Joe|
2|Jane|
3|Marcus|1234565
╭────┬──────╮
│ id │ name │
╞════╪══════╡
│ 1 │ Joe │
│ 2 │ Jane │
╰────┴──────╯
╭────┬──────┬───────╮
│ id │ name │ phone │
╞════╪══════╪═══════╡
│ 1 │ Joe │ NULL │
│ 2 │ Jane │ NULL │
╰────┴──────┴───────╯
╭────┬────────┬───────────╮
│ id │ name │ phone │
╞════╪════════╪═══════════╡
│ 1 │ Joe │ NULL │
│ 2 │ Jane │ NULL │
│ 3 │ Marcus │ '1234565' │
╰────┴────────┴───────────╯
ALTER TABLE not NULL
What if we would like to add a column that should be NOT NULL?
We cannot add a column that is NOT NULL as it would immediately violate the integriry of the table.
We can first add the column. Then update the existing rows, then change the column definition.
CREATE TABLE person (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
INSERT INTO person (name) VALUES ('Joe');
INSERT INTO person (name) VALUES ('Jane');
SELECT * FROM person;
-- ALTER TABLE person ADD COLUMN phone TEXT NOT NULL;
-- Error near line 11: Cannot add a NOT NULL column with default value NULL
ALTER TABLE person ADD COLUMN phone TEXT;
UPDATE person SET phone = '123' WHERE name = 'Joe';
UPDATE person SET phone = '567' WHERE name = 'Jane';
SELECT * FROM person;
ALTER TABLE person ALTER phone SET NOT NULL;
-- INSERT INTO person (name) VALUES ('Mary');
-- Error near line 21: NOT NULL constraint failed: person.phone
INSERT INTO person (name, phone) VALUES ('Mary', '890');
SELECT * FROM person;
sqlite3 < examples/alter-table-not-null.sql
1|Joe
2|Jane
1|Joe|123
2|Jane|567
1|Joe|123
2|Jane|567
3|Mary|890
ALTER TABLE in transaction
Even better, do all the changes inside a transaction. That way they are either all implemented, or nothing changes.
CREATE TABLE person (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
INSERT INTO person (name) VALUES ('Joe');
INSERT INTO person (name) VALUES ('Jane');
SELECT * FROM person;
-- ALTER TABLE person ADD COLUMN phone TEXT NOT NULL;
-- Error near line 11: Cannot add a NOT NULL column with default value NULL
BEGIN TRANSACTION;
ALTER TABLE person ADD COLUMN phone TEXT;
UPDATE person SET phone = '123' WHERE name = 'Joe';
UPDATE person SET phone = '567' WHERE name = 'Jane';
SELECT * FROM person;
ALTER TABLE person ALTER phone SET NOT NULL;
COMMIT;
-- INSERT INTO person (name) VALUES ('Mary');
-- Error near line 21: NOT NULL constraint failed: person.phone
INSERT INTO person (name, phone) VALUES ('Mary', '890');
SELECT * FROM person;
$ sqlite examples/alter-table-in-transaction.sql
1|Joe
2|Jane
1|Joe|123
2|Jane|567
1|Joe|123
2|Jane|567
3|Mary|890
Optimization
We have a Perl script that will generated a file called examples/big-data.csv with lots of rows.
We’ll use that data next.
perl generate.pl
INDEX
Adding INDEX to a table might speed up certain request.
.timer on
.import --csv -v examples/big-data.csv pairs
SELECT * from pairs WHERE name = 'aaaafryc'; -- 'aaaaaaaz';
CREATE INDEX names ON pairs (name);
SELECT * from pairs WHERE name = 'aaaafryc'; -- 'aaaaaaaz';
$ sqlite3 < examples/big-data-time.sql
Column separator ",", row separator "\n"
CREATE TABLE "main"."pairs"(
"name" ANY, "value" ANY)
Added 100000 rows with 0 errors using 100001 lines of input
aaaafryc|99999
Run Time: real 0.005019 user 0.005032 sys 0.000000
Run Time: real 0.027671 user 0.022654 sys 0.005020
aaaafryc|99999
Run Time: real 0.000018 user 0.000018 sys 0.000002
-
The 1st SELECT takes 0.005019 seconds. That’s going over all the rows.
-
The 3rd SELECT takes 0.000018 seconds. That’s because having and index allows SQLite to go directly to the right row.
-
The
CREATE INDEXstatement takes 0.027671 seconds. So it is very time consuming and also uses extra memory. It starts to be worth doing it if you run the select at least 7 times. (In this specific case.)
EXPLAIN
.import --csv -v examples/big-data.csv pairs
EXPLAIN SELECT * from pairs WHERE name = 'aaaaaaaz';
CREATE INDEX names ON pairs (name);
SELECT '--------------------------------';
EXPLAIN SELECT * from pairs WHERE name = 'aaaaaaaz';
$ sqlite < examples/big-data-explain.sql
Column separator ",", row separator "\n"
CREATE TABLE "main"."pairs"(
"name" ANY, "value" ANY)
Added 100000 rows with 0 errors using 100001 lines of input
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 10 0 0 Start at 10
1 OpenRead 0 2 0 2 0 root=2 iDb=0; pairs
2 Rewind 0 9 0 0
3 Column 0 0 1 0 r[1]= cursor 0 column 0
4 Ne 2 8 1 BINARY-8 83 if r[1]!=r[2] goto 8
5 Column 0 0 3 0 r[3]= cursor 0 column 0
6 Column 0 1 4 0 r[4]= cursor 0 column 1
7 ResultRow 3 2 0 0 output=r[3..4]
8 Next 0 3 0 1
9 Halt 0 0 0 0
10 Transaction 0 0 1 0 1 usesStmtJournal=0
11 String8 0 2 0 aaaaaaaz 0 r[2]='aaaaaaaz'
12 Goto 0 1 0 0
--------------------------------
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 13 0 0 Start at 13
1 OpenRead 0 2 0 2 0 root=2 iDb=0; pairs
2 OpenRead 1 483 0 k(2,,) 2 root=483 iDb=0; names
3 String8 0 1 0 aaaaaaaz 0 r[1]='aaaaaaaz'
4 Affinity 1 1 0 C 0 affinity(r[1])
5 SeekGE 1 12 1 1 0 key=r[1]
6 IdxGT 1 12 1 1 0 key=r[1]
7 DeferredSeek 1 0 0 0 Move 0 to 1.rowid if needed
8 Column 1 0 2 0 r[2]= cursor 1 column 0
9 Column 0 1 3 0 r[3]= cursor 0 column 1
10 ResultRow 2 2 0 0 output=r[2..3]
11 Next 1 6 1 0
12 Halt 0 0 0 0
13 Transaction 0 0 2 0 1 usesStmtJournal=0
14 Goto 0 1 0 0
Increment or Insert (UPSERT)
The ON CONFLICT clause is a non-standard extension specific to SQLite.
This way of using ON CONFLICT is also referred to as UPSERT (which is not a keyword in SQLite).
CREATE TABLE counters (
name TEXT UNIQUE NOT NULL,
number INTEGER NOT NULL
);
INSERT INTO counters (name, number)
VALUES ('apple', 1)
ON CONFLICT(name) DO UPDATE SET number = number + 1;
SELECT * FROM counters;
INSERT INTO counters (name, number)
VALUES ('apple', 1)
ON CONFLICT(name) DO UPDATE SET number = number + 1;
SELECT * FROM counters;
$ sqlite < examples/increment-or-insert.sql
apple|1
apple|2
INSERT or UPDATE (replace)
ON CONFLICT
This statement will try to INSERT a row, but if the name is already in the database
then it will update the recent field. The exclude.recent is the value we tried to insert.
It can be used instead of duplicating the value of the recent field.
The first statement will INSERT a row. (because ‘Joe’ was not in the database yet)
The second statement will UPDATE the recent field of Joe.
CREATE TABLE score (
name TEXT UNIQUE NOT NULL,
recent INTEGER NOT NULL
);
INSERT INTO score (name, recent) VALUES('Joe', 23)
ON CONFLICT(name) DO UPDATE SET recent=excluded.recent;
SELECT * FROM score;
INSERT INTO score (name, recent) VALUES('Joe', 42)
ON CONFLICT(name) DO UPDATE SET recent=excluded.recent;
SELECT * FROM score;
CREATE TABLE score (
name TEXT UNIQUE NOT NULL,
recent INTEGER NOT NULL
);
INSERT INTO score (name, recent) VALUES('Joe', 23)
ON CONFLICT(name) DO UPDATE SET recent=excluded.recent;
SELECT * FROM score;
INSERT INTO score (name, recent) VALUES('Joe', 42)
ON CONFLICT(name) DO UPDATE SET recent=excluded.recent;
SELECT * FROM score;
Joe|23
Joe|42
Conditional CREATE
IF NOT EXISTS can help us especially with migration scripts.
Both when they are external to the application and when they
are part of the startup code.
In this example the first statement succeeds creating the table.
The second statement fails with an error.
The third statement fails but there is no error.
CREATE TABLE IF NOT EXISTS counters (
name TEXT UNIQUE NOT NULL,
number INTEGER NOT NULL
);
CREATE TABLE counters (
name TEXT UNIQUE NOT NULL,
number INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS counters (
name TEXT UNIQUE NOT NULL,
number INTEGER NOT NULL
);
.schema
$ sqlite3 < examples/conditional-create.sql
Parse error near line 6: table counters already exists
CREATE TABLE counters ( name TEXT UNIQUE NOT NULL, number INTEGER NOT
^--- error here
CREATE TABLE counters (
name TEXT UNIQUE NOT NULL,
number INTEGER NOT NULL
);
sqldiff
The sqldiff program comes with the installation of SQLite. It can help creating migration scripts by showing the differences between two databases.
We have a before and after schema:
CREATE TABLE person (
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE person (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
phone TEXT NOT NULL
);
We create two db files and run the sqldiff command on these files.
rm -f /tmp/before.db /tmp/after.db
~/bin/sqlite3 /tmp/before.db < examples/before.sql
~/bin/sqlite3 /tmp/after.db < examples/after.sql
~/bin/sqldiff /tmp/before.db /tmp/after.db
This is the output we get:
ALTER TABLE person ADD COLUMN phone;
SQLite in Python
Python comes with the sqlite3 module that wraps (embeds) the C library implementing SQLite.
SQLite in Rust
SQLite in Perl
- See some examples here
- DBI
- DBD::SQLite
- DBIx::Class