SQLite Guide — Embedded Database for Applications
SQLite is a self-contained, serverless, zero-configuration database engine that stores the entire database as a single file, making it the most widely deployed database in the world.
What You’ll Learn
By the end of this tutorial, you’ll understand SQLite’s zero-config architecture, create and query databases using SQL, use the command-line tool for backups and restores, optimize performance with indexes, and choose between SQLite, MySQL, and PostgreSQL for your projects.
Why SQLite Matters
SQLite is everywhere — every smartphone has thousands of SQLite databases, every browser uses it for bookmarks and history, and countless desktop apps rely on it for local storage. Doda Browser uses SQLite for user bookmarks, browsing history, and cached data. Durga Antivirus Pro uses SQLite for local threat signatures on devices without network access. Learning SQLite is essential for any application developer.
SQLite Learning Path
flowchart LR
A[SQL Basics] --> B[MySQL]
B --> C[SQLite]
C --> D[PostgreSQL]
D --> E[MongoDB]
E --> F[Database Design]
C --> G{You Are Here}
style G fill:#f90,color:#fff
What Is SQLite? (The “Why” First)
Imagine needing a database for your desktop app. With MySQL or PostgreSQL, you’d install a server, configure users, set up networking, and manage connections — overkill for a local application. SQLite flips this: there is no server. Your database is a single file on disk. Your application links against the SQLite library directly and reads/writes that file. No setup, no administration, no network.
SQLite vs MySQL vs PostgreSQL
| Feature | SQLite | MySQL | PostgreSQL |
|---|---|---|---|
| Setup | Zero (file-based) | Server install | Server install |
| Concurrency | Single writer | Many concurrent | Many concurrent |
| Storage | Single file | Multiple files | Multiple files |
| Network | No (embedded) | TCP/IP | TCP/IP |
| User management | File permissions | Built-in users | Built-in roles |
| Best for | Local/mobile apps | Web apps | Enterprise apps |
| Max database size | 281 TB (practical) | Unlimited | Unlimited |
Creating Your First SQLite Database
The sqlite3 command-line tool is all you need:
# Create (or open) a database
sqlite3 inventory.db
# Expected output:
# SQLite version 3.43.2 2026-04-01 12:00:00
# Enter ".help" for usage hints.-- Inside the sqlite3 prompt, create tables
CREATE TABLE products (
product_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL CHECK(price > 0),
quantity INTEGER NOT NULL DEFAULT 0,
category TEXT NOT NULL DEFAULT 'General',
created_at TEXT DEFAULT (datetime('now'))
);
-- Insert sample products
INSERT INTO products (name, description, price, quantity, category) VALUES
('Wireless Mouse', 'Ergonomic wireless mouse with USB receiver', 29.99, 150, 'Electronics'),
('Notebook Set', 'Pack of 5 A5 ruled notebooks', 12.50, 300, 'Stationery'),
('Desk Lamp', 'LED desk lamp with adjustable brightness', 39.99, 75, 'Office'),
('USB-C Cable', '2m braided USB-C charging cable', 9.99, 500, 'Electronics'),
('Whiteboard Markers', 'Set of 8 assorted colors', 7.99, 200, 'Office');
-- Verify data
SELECT * FROM products;Output:
product_id | name | description | price | quantity | category | created_at
1 | Wireless Mouse | Ergonomic wireless mouse with USB receiver | 29.99 | 150 | Electronics| 2026-06-07 10:00:00
2 | Notebook Set | Pack of 5 A5 ruled notebooks | 12.50 | 300 | Stationery | 2026-06-07 10:00:00
3 | Desk Lamp | LED desk lamp with adjustable brightness | 39.99 | 75 | Office | 2026-06-07 10:00:00
4 | USB-C Cable | 2m braided USB-C charging cable | 9.99 | 500 | Electronics| 2026-06-07 10:00:00
5 | Whiteboard Markers| Set of 8 assorted colors | 7.99 | 200 | Office | 2026-06-07 10:00:00Querying Data
-- Products with low stock (less than 100 units)
SELECT name, quantity, price
FROM products
WHERE quantity < 100
ORDER BY quantity;
-- Output:
-- name | quantity | price
-- Desk Lamp | 75 | 39.99
-- Total inventory value per category
SELECT
category,
COUNT(*) AS product_count,
SUM(quantity) AS total_units,
ROUND(SUM(price * quantity), 2) AS total_value
FROM products
GROUP BY category
ORDER BY total_value DESC;
-- Output:
-- category | product_count | total_units | total_value
-- Electronics | 2 | 650 | 9496.50
-- Office | 2 | 275 | 2999.25
-- Stationery | 1 | 300 | 3750.00
-- Search by name (LIKE is case-insensitive by default in SQLite)
SELECT name, price FROM products WHERE name LIKE '%cable%';
-- Output:
-- name | price
-- USB-C Cable | 9.99SQLite-Specific Features
The .dump and .restore Commands
SQLite’s command line has built-in backup tools:
# Dump entire database to SQL script
sqlite3 inventory.db .dump > inventory_backup.sql
# The output file contains all CREATE TABLE + INSERT statements
# You can restore it anytime:
sqlite3 restored_inventory.db < inventory_backup.sql
# Or from within sqlite3:
sqlite3 inventory.db
sqlite> .output backup.sql
sqlite> .dump
sqlite> .exit
# Restore
sqlite3 inventory.db
sqlite> .read backup.sqlATTACH — Working with Multiple Databases
-- Attach another database
ATTACH DATABASE 'users.db' AS users_db;
-- Query across databases
SELECT p.name, u.username
FROM products p
JOIN users_db.users u ON p.product_id = u.favorite_product_id;The json Extension
-- SQLite has built-in JSON functions (since 3.9.0)
SELECT
name,
json_object('price', price, 'in_stock', json(quantity > 0)) AS product_json
FROM products
WHERE category = 'Electronics';Output:
name | product_json
Wireless Mouse | {"price":29.99,"in_stock":true}
USB-C Cable | {"price":9.99,"in_stock":true}Using SQLite with Python
import sqlite3
# Connect to database (creates if not exists)
conn = sqlite3.connect("inventory.db")
cursor = conn.cursor()
# Create a sales table
cursor.execute("""
CREATE TABLE IF NOT EXISTS sales (
sale_id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
quantity_sold INTEGER NOT NULL,
sale_date TEXT DEFAULT (datetime('now')),
FOREIGN KEY (product_id) REFERENCES products(product_id)
)
""")
# Record a sale
cursor.execute(
"INSERT INTO sales (product_id, quantity_sold) VALUES (?, ?)",
(1, 2)
)
# Query with JOIN
cursor.execute("""
SELECT
p.name,
SUM(s.quantity_sold) AS total_sold,
ROUND(SUM(s.quantity_sold * p.price), 2) AS revenue
FROM sales s
JOIN products p ON s.product_id = p.product_id
GROUP BY p.product_id
ORDER BY revenue DESC
""")
for row in cursor.fetchall():
print(f"{row[0]}: sold {row[1]} units, revenue ${row[2]}")
conn.commit()
conn.close()Expected output:
Wireless Mouse: sold 2 units, revenue $59.98SQLite Architecture
flowchart TB
subgraph Application
A[App Code]
P[Python Module]
C[C Library]
end
subgraph SQLite
I[Interface Layer]
T[Tokenizer]
Pa[Parser]
Co[Code Generator]
VM[Virtual Machine]
B[B-Tree Engine]
Pa2[Pager]
OS[OS Interface]
end
subgraph Storage
DB[(database.db)]
end
A --> P
A --> C
P --> I
C --> I
I --> T --> Pa --> Co --> VM --> B --> Pa2 --> OS
OS --> DB
SQLite’s architecture is remarkably simple compared to client-server databases. There’s no network layer, no connection pooling, no authentication system — just your application talking directly to the storage engine.
Common SQLite Errors
1. database is locked
SQLite allows only one writer at a time. If another connection has an open transaction, writes fail. Fix: Use shorter transactions, implement retry logic, or switch to WAL mode.
-- Enable Write-Ahead Logging for better concurrency
PRAGMA journal_mode=WAL;
-- Output: WAL2. attempt to write a readonly database
The database file (or its directory) is not writable by the current user. Fix: Check file permissions with ls -l database.db and the directory with ls -ld .. Ensure the user has write permission.
3. UNIQUE constraint failed
-- If product_id is PRIMARY KEY AUTOINCREMENT, this fails:
INSERT INTO products (product_id, name) VALUES (1, 'Duplicate');
-- UNIQUE constraint failed: products.product_idFix: Don’t specify values for AUTOINCREMENT columns, or use INSERT OR REPLACE for upserts.
4. no such table
SELECT * FROM non_existent_table;
-- Error: no such table: non_existent_tableFix: Check spelling. List tables with .tables command. SQLite table names are case-insensitive but the spelling must be exact.
5. FOREIGN KEY constraint failed
SQLite does NOT enforce foreign keys by default! You must enable it:
PRAGMA foreign_keys = ON;Without this, you can insert orphan records without errors.
6. data type mismatch
SQLite is flexibly typed — you can insert text into an INTEGER column. But with strict tables (SQLite 3.37+):
CREATE TABLE strict_products (
id INTEGER PRIMARY KEY,
price REAL NOT NULL
) STRICT;
INSERT INTO strict_products VALUES (1, 'not_a_number');
-- Error: datatype mismatchFix: Match the declared column types.
7. File Corruption
Power loss during a write can corrupt the database. Fix: Enable WAL mode (PRAGMA journal_mode=WAL), use PRAGMA synchronous=NORMAL, and run periodic integrity checks with PRAGMA integrity_check.
Practice Questions
1. What does “zero-configuration” mean in SQLite?
SQLite requires no server installation, no configuration files, no user management, and no network setup. You just install the library (comes pre-installed on most systems) and create a database file with a single command.
2. How does SQLite handle concurrency compared to MySQL?
SQLite allows unlimited concurrent readers but only one writer at a time. MySQL supports multiple concurrent writers with row-level locking. Use SQLite for single-user apps; use MySQL for multi-user web applications.
3. What is WAL mode in SQLite?
Write-Ahead Logging (WAL) mode improves concurrent read/write performance by allowing readers to continue reading old data while a writer writes to a separate WAL file. Enable it with PRAGMA journal_mode=WAL.
4. Challenge: Write a query that finds products with names matching a search term, using SQLite’s full-text search capabilities.
SQLite has a full-text search extension (FTS5):
-- Create a virtual FTS table
CREATE VIRTUAL TABLE products_fts USING fts5(name, description, content='products');
-- Populate from the source table
INSERT INTO products_fts SELECT name, description FROM products;
-- Search with ranking
SELECT name, rank
FROM products_fts
WHERE products_fts MATCH 'wireless OR mouse'
ORDER BY rank;5. What is the maximum size of a SQLite database?
The default maximum page size is 4096 bytes with a maximum of 2147483646 pages, giving a theoretical maximum of about 281 TB. In practice, performance degrades beyond 100-200 GB.
Real-World Task: Build a Local Note-Taking App Database
Design the database schema for a note-taking app — similar to the local storage used by Doda Browser for bookmarks:
CREATE TABLE notes (
note_id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
category TEXT DEFAULT 'General',
is_pinned INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE tags (
tag_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE note_tags (
note_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (note_id, tag_id),
FOREIGN KEY (note_id) REFERENCES notes(note_id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id) ON DELETE CASCADE
);
-- Enable foreign keys for this session
PRAGMA foreign_keys = ON;
-- Insert sample data
INSERT INTO notes (title, content, category) VALUES
('Meeting Notes', 'Discuss Q3 roadmap and sprint planning', 'Work'),
('Shopping List', 'Milk, eggs, bread, coffee, chicken', 'Personal');
INSERT INTO tags (name) VALUES ('important'), ('urgent'), ('reference');
INSERT INTO note_tags (note_id, tag_id) VALUES (1, 1), (1, 2);
-- Query: Get all notes with their tags
SELECT
n.title,
n.category,
GROUP_CONCAT(t.name, ', ') AS tags
FROM notes n
LEFT JOIN note_tags nt ON n.note_id = nt.note_id
LEFT JOIN tags t ON nt.tag_id = t.tag_id
GROUP BY n.note_id
ORDER BY n.updated_at DESC;FAQ
Try It Yourself
Create the products database above and run these analytical queries:
-- Products with no sales (use LEFT JOIN)
SELECT p.name, COUNT(s.sale_id) AS times_sold
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
GROUP BY p.product_id
HAVING times_sold = 0;
-- Average price per category
SELECT category, ROUND(AVG(price), 2) AS avg_price
FROM products
GROUP BY category;
-- Database file size
PRAGMA page_count;
PRAGMA page_size;
-- Total = page_count * page_size bytesThese patterns power local caching in DodaZIP and offline threat signature lookups in Durga Antivirus Pro — proving that even small, embedded databases can power serious applications.
What’s Next
Congratulations on completing this SQLite tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro