This is Part 3 in a series on SQLite:
Open SQLite in the terminal:
$ sqlite3
Exit out of SQLite:
.quit
# or
.exit
You can also use Ctrl+D
Remember, you can press the up arrow in the terminal to recall a previous command
Open a database, creating it if it doesn’t
exist:
- If a different database is already open, this command closes it
and opens the new one
- Use .open :memory:
to create an in-memory
database
.open database.db
Attach another database:
Allows access using a prefix,
e.g. SELECT * FROM other.table_name;
ATTACH DATABASE 'database2.db' AS other;
Detach an attached database:
DETACH DATABASE other;
List open/attached databases:
.databases
Export current database to a file:
Also used to save an in-memory database to disk
.save database.db
Check schema of all tables:
.schema
…or for a specific attached database:
.schema other.table_name
Create a new table (with no column constraints):
With one column:
CREATE TABLE table_name (column_1 data_type);
With multiple columns:
CREATE TABLE table_name (
column_1 data_type,
column_2 data_type,
column_3 data_type
);
Common SQLite data types:
INTEGER
: whole number
REAL
: decimal number
TEXT
: string (use single or double quotes)
DATE
: date in 'YYYY-MM-DD'
format
BLOB
: binary large object (e.g. images, files)
List all tables in all open databases:
.tables
Show the CREATE TABLE
statement that was
used:
.schema table_name
Show table structure (column names and types):
PRAGMA table_info(table_name);
Rename a table:
ALTER TABLE old_name RENAME TO new_name;
Delete (drop) a table:
DROP TABLE table_name;
Columns:
Add a column:
ALTER TABLE table_name ADD COLUMN new_column data_type;
Rename a column:
ALTER TABLE table_name RENAME COLUMN old_name TO new_name;
Delete (drop) a column:
You cannot delete the last column in a table
ALTER TABLE table_name DROP COLUMN column_name;
Create a new table (with column constraints):
Prevent NULL
values from being added in a
particular column:
CREATE TABLE table_name (
column_1 data_type NOT NULL
);
Define a default value to be added if no explicit value is given:
CREATE TABLE table_name (
column_1 data_type DEFAULT 'default_value'
);
Prevent duplicate values from being added in a particular column:
CREATE TABLE table_name (
column_1 data_type UNIQUE
);
Have the column’s values be able to uniquely identify the rows:
CREATE TABLE table_name (
column_1 data_type PRIMARY KEY
);
- There can be multiple UNIQUE
columns in a table
but no more than one PRIMARY KEY
column
- More info on the PRIMARY KEY
constraint
below:
NULL
values
Create a table with a primary key:
CREATE TABLE table_name (
id INTEGER PRIMARY KEY
);
Create a table with a composite primary key:
CREATE TABLE table_name (
col1 INTEGER,
col2 TEXT,
PRIMARY KEY (col1, col2)
);
Create a table with a foreign key:
CREATE TABLE table_name (
othertable_id INTEGER,
FOREIGN KEY (othertable_id) REFERENCES othertable(id)
);
Enforce a one-to-one relationship (foreign key must be unique):
CREATE TABLE table_name (
othertable_id INTEGER UNIQUE,
FOREIGN KEY (othertable_id) REFERENCES othertable(id)
);
Notes:
Foreign keys must be explicitly enabled in SQLite:
PRAGMA foreign_keys = ON;
To check if foreign keys are enabled:
PRAGMA foreign_keys;
To view foreign key constraints on a table:
PRAGMA foreign_key_list(table_name);
SQLite doesn’t enforce strict data types for keys, but it is best practice to use matching types between referenced keys.
Insert a new row:
Insert a value into one column (other columns will be set to
NULL
):
INSERT INTO table_name (column_name)
VALUES (value);
Change the value used for nulls instead of NULL
with:
.nullvalue value
Insert values into multiple columns:
INSERT INTO table_name (column_1, column_2, column_3)
VALUES (value_1, value_2, value_3);
Insert multiple rows in a single statement (SQLite 3.7.11+):
INSERT INTO table_name (column_1, column_2)
VALUES (value_1a, value_2a),
(value_1b, value_2b),
(value_1c, value_2c);
Insert all values in order (omit column names):
Only safe if you know the column order — not recommended for
production code
INSERT INTO table_name
VALUES (value_1, value_2, value_3);
Replace a row (insert or update):
Inserts a row or overwrites the existing one if the primary key
already exists:
Note: This deletes the existing row first, which may trigger
ON DELETE
constraints.
REPLACE INTO table_name (id, column_1, column_2)
VALUES (1, value_1, value_2);
Insert a row only if it doesn’t conflict with existing data (ignore conflict):
INSERT OR IGNORE INTO table_name (column_1, column_2)
VALUES (value_1, value_2);
Insert a row and update if a conflict occurs
(UPSERT):
Requires SQLite 3.24.0+
INSERT INTO table_name (id, column_1)
VALUES (1, 'value')
ON CONFLICT(id) DO UPDATE SET column_1 = 'new_value';
Update existing data:
Change the value of a column for rows matching a condition:
UPDATE table_name
SET column_1 = new_value
WHERE column_2 = value;
Update multiple columns at once:
UPDATE table_name
SET column_1 = value_1,
column_2 = value_2
WHERE id = some_id;
Cases:
Use CASE
to return different values based on conditions
in a column.
SELECT column_1,
CASE
WHEN column_2 = 5 THEN 'Great'
WHEN column_2 >= 4 THEN 'Good'
WHEN column_2 >= 3 THEN 'Average'
WHEN column_2 >= 2 THEN 'Ok'
ELSE 'Poor'
END AS 'new_column_name'
FROM table_name;
Delete rows:
Delete rows matching a condition:
DELETE FROM table_name
WHERE column_name = value;
Delete all rows from a table (the table remains):
DELETE FROM table_name;
Use the SELECT
query to select everything
from a table as your output:
SELECT * FROM table_name;
By default, output (in this case, everything from a table) is
printed to the terminal using the current display mode.
You can change the display format using the .mode
command which sets the format for query results:
Column (default) – neat columns for terminal output:
.mode column
Adjust column width (only affects
.mode column
):
Specify the width for each column (in order).
.width 20 30 15
List – each row printed vertically (one column per line):
.mode list
Line – similar to list, but formatted for readability:
.mode line
CSV – comma-separated values:
.mode csv
Tabs – tab-separated values (TSV):
.mode tabs
HTML – results wrapped in
<table>
tags:
.mode html
JSON – each row as a JSON object (SQLite 3.38.0+):
.mode json
Insert – generates INSERT
statements:
.mode insert table_name
Quote – quoted CSV (useful for importing safely):
.mode quote
Show column headers in output (on by default):
.headers on
Turn off column headers:
.headers off
Send output to terminal (default):
With this setting on, query results get displayed in the
terminal
.output stdout
Send output to a file:
Don’t forget to reset .output
back to
stdout
to see results in the terminal again.
.output filename.csv
SELECT * FROM table_name;
.output stdout
Export as CSV:
-- Include the column headers
.headers on
-- Change the display mode
.mode csv
-- Redirect the output
.output table.csv
-- Select all rows and columns
SELECT * FROM table_name;
-- Redirect the output back to what it was
.output stdout
-- Change the display mode back to what it was
.mode column
Export as HTML:
.mode html
.output table.html
SELECT * FROM table_name;
.output stdout
Export as SQL insert statements:
.mode insert table_name
.output table.sql
SELECT * FROM table_name;
.output stdout
Alternatively, using .dump
:
.mode insert table_name
.output table.sql
.dump
.output stdout
Notes:
.mode
and .output
commands must be
run before the SELECT
statement to take
effect..show
From an SQL file:
Runs SQL commands from a .sql
file, typically
containing CREATE TABLE
and INSERT
statements:
.read path_to_file.sql
From a CSV file:
Creates and populates a table from a CSV file:
- The file must be plain CSV (no Excel formatting)
- The first line is assumed to be column headers unless you specify
otherwise
- If the table doesn’t exist yet, it will be created automatically
using the CSV header row
.mode csv
.import path_to_file.csv table_name
Display one column:
SELECT column_name FROM table_name;
Display multiple columns:
SELECT column_1, column_2, column_3 FROM table_name;
Display all columns, but only some rows:
SELECT * FROM table_name
LIMIT 10;
Select a column and assign it an alias:
SELECT column_name AS 'alias' FROM table_name;
Remove duplicate values:
SELECT DISTINCT column_name FROM table_name;
Round values in a column (e.g. to 2 decimal places):
SELECT column_1, ROUND(column_2, 2) FROM table_name;
Sort a table:
Sort ascending (default):
SELECT * FROM table_name ORDER BY column_name;
Sort descending:
SELECT * FROM table_name ORDER BY column_name DESC;
Filter out NULL
values:
SELECT * FROM table_name
WHERE column_name IS NOT NULL;
Filter using comparison operators:
SELECT * FROM table_name
WHERE column_name > value;
Comparison operators:
=
: equal to
!=
: not equal to
>
: greater than
<
: less than
>=
: greater than or equal to
<=
: less than or equal to
Filter within a range using
BETWEEN
:
SELECT * FROM table_name
WHERE column_name BETWEEN value_1 AND value_2;
Filter using multiple conditions:
SELECT * FROM table_name
WHERE column_1 > value AND column_2 < value;
SELECT * FROM table_name
WHERE column_1 BETWEEN value_1 AND value_2
AND column_2 > value_3;
SELECT * FROM table_name
WHERE column_1 BETWEEN value_1 AND value_2
OR column_2 > value_3;
Logical operators:
AND
: both conditions must be true
OR
: at least one condition must be true
Filter using pattern matching
(LIKE
):
SELECT * FROM table_name
WHERE column_name LIKE 'te_t'; -- Matches 'test', 'text', etc.
SELECT * FROM table_name
WHERE column_name LIKE '%substring%'; -- Matches any text containing 'substring'
Filter and sort together:
SELECT * FROM table_name
WHERE column_1 > value
ORDER BY column_2 DESC;
Count rows:
SELECT COUNT(*) FROM table_name;
SELECT COUNT(*) FROM table_name
WHERE column_name = value;
Sum values:
SELECT SUM(column_name) FROM table_name;
SELECT SUM(column_1) FROM table_name
WHERE column_2 = value;
Get the maximum value:
SELECT MAX(column_name) FROM table_name;
SELECT MAX(column_1) FROM table_name
WHERE column_2 = value;
Get the minimum value:
SELECT MIN(column_name) FROM table_name;
SELECT MIN(column_1) FROM table_name
WHERE column_2 = value;
Get the average value:
SELECT AVG(column_name) FROM table_name;
SELECT AVG(column_1) FROM table_name
WHERE column_2 = value;
Group and show all columns:
SELECT * FROM table_name
GROUP BY column_name;
Show average per group and sort by group:
SELECT column_1, AVG(column_2) FROM table_name
GROUP BY column_1
ORDER BY column_1;
Use positional references in GROUP BY
or
ORDER BY
:
Here, 1
refers to the first column in the
SELECT
clause, 2
to the second, etc.
SELECT column_1, AVG(column_2) FROM table_name
GROUP BY 1
ORDER BY 1;
Filter before grouping using
WHERE
:
SELECT column_1, AVG(column_2) FROM table_name
WHERE column_3 > value
GROUP BY column_1
ORDER BY column_1;
Filter after grouping using
HAVING
:
SELECT column_1, COUNT(column_2) FROM table_name
GROUP BY column_1
HAVING COUNT(column_2) > value;
Note: HAVING
is used after
aggregation (e.g. to filter groups), while WHERE
is used
before aggregation (to filter rows).
Join via an inner join:
Join two tables and select all columns:
SELECT * FROM table_1
JOIN table_2 ON table_1.column_1 = table_2.column_1;
Join two tables and select one column from each:
SELECT table_1.column_2, table_2.column_3 FROM table_1
JOIN table_2 ON table_1.column_1 = table_2.column_1;
Join two tables and filter results:
SELECT * FROM table_1
JOIN table_2 ON table_1.column_1 = table_2.column_1
WHERE table_1.column_2 = value;
Join via a left join:
Returns all rows from the left table and matching rows from the
right (or NULL
if no match)
SELECT * FROM table_1
LEFT JOIN table_2 ON table_1.column_1 = table_2.column_1;
Join via a cross join (Cartesian product):
Every row in table_1
is paired with every row in
table_2
Select all columns:
SELECT * FROM table_1
CROSS JOIN table_2;
Select specific columns from both tables:
SELECT table_1.column_1, table_2.column_2 FROM table_1
CROSS JOIN table_2;
Append rows from one table to another (UNION):
Combine two tables with the same structure into one:
SELECT * FROM table_1
UNION
SELECT * FROM table_2;
Note: UNION
removes duplicates. Use
UNION ALL
to include all rows, even if
duplicated.
Use WITH
to define a subquery that can be
reused later in the main query: This is also known as a
Common Table Expression (CTE), useful for breaking complex queries into
logical steps.
WITH previous_results AS (
SELECT column_1 AS 'alias' FROM table_1
UNION
SELECT * FROM table_2
)
SELECT * FROM previous_results
JOIN table_3 ON table_3.column_2 = previous_query.alias;
You can also nest subqueries directly inside
SELECT
, FROM
, or WHERE
clauses:
SELECT *
FROM table_1
WHERE column_1 IN (
SELECT column_1 FROM table_2
WHERE column_2 > value
);
Use subqueries for filtering, aggregating, or creating reusable logic within queries.