Welcome to the world of databases! SQLite is the perfect place to start learning about databases because it's simple, lightweight, and already included in Python's standard library.
Learn to work with SQLite databases in Python, including creating tables, inserting data, querying records, and understanding when to use databases in your applications.
- Creating databases and tables
- CRUD operations (Create, Read, Update, Delete)
- Parameterized queries (SQL injection prevention)
- JOINs and relationships between tables
- Aggregation (COUNT, SUM, AVG, GROUP BY)
- Context managers for safe database handling
- SQLite data types
- When to use SQLite vs other databases
SQLite is a self-contained, serverless database engine that stores data in a single file (or in memory). Unlike bigger databases like PostgreSQL or MySQL, you don't need to install or configure a separate server—just import sqlite3 and you're ready to go!
- Zero setup: Already in Python's stdlib, no installation needed
- Simple: Just one file (or use
:memory:for temporary databases) - Fast: Perfect for small to medium-sized applications
- Portable: The database file can be copied anywhere
- Full-featured: Supports most SQL features you'll need
import sqlite3
# Create a database file (or use ':memory:' for in-memory)
conn = sqlite3.connect('my_database.db') # Creates file
# or
conn = sqlite3.connect(':memory:') # Temporary, in RAM
# Create a cursor to execute SQL commands
cursor = conn.cursor()
# Create a table
cursor.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER
)
''')
# Always commit your changes!
conn.commit()
conn.close()SQLite uses a simplified type system:
- NULL: Missing value
- INTEGER: Whole numbers (1, 42, -5)
- REAL: Floating-point numbers (3.14, -0.5)
- TEXT: Strings ('hello', 'foo@bar.com')
- BLOB: Binary data (images, files)
Unlike other databases, SQLite is dynamically typed—you can store any type in any column (though you shouldn't!).
CRUD stands for Create, Read, Update, Delete—the four basic operations you'll do constantly.
# Insert a single record
cursor.execute('''
INSERT INTO users (name, email, age)
VALUES ('Alice', 'alice@example.com', 30)
''')
# Insert multiple records
users = [
('Bob', 'bob@example.com', 25),
('Charlie', 'charlie@example.com', 35)
]
cursor.executemany('''
INSERT INTO users (name, email, age) VALUES (?, ?, ?)
''', users)
conn.commit()# Get all records
cursor.execute('SELECT * FROM users')
rows = cursor.fetchall()
for row in rows:
print(row)
# Get one record
cursor.execute('SELECT * FROM users WHERE name = ?', ('Alice',))
user = cursor.fetchone()
# Filter and sort
cursor.execute('''
SELECT name, age FROM users
WHERE age > 25
ORDER BY age DESC
''')cursor.execute('''
UPDATE users
SET age = 31
WHERE name = 'Alice'
''')
conn.commit()cursor.execute('DELETE FROM users WHERE age < 25')
conn.commit()NEVER build SQL queries with string formatting or concatenation—it opens you up to SQL injection attacks!
# BAD - SQL injection vulnerable!
name = "Alice'; DROP TABLE users; --"
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'") # DANGER!
# GOOD - Use parameterized queries
cursor.execute('SELECT * FROM users WHERE name = ?', (name,))The ? placeholder is automatically escaped, keeping your database safe from malicious input.
Real-world data is spread across multiple tables. JOINs let you combine them:
# Create related tables
cursor.execute('''
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
product TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
)
''')
# Join them
cursor.execute('''
SELECT users.name, orders.product
FROM users
JOIN orders ON users.id = orders.user_id
''')- INNER JOIN: Only matching rows from both tables
- LEFT JOIN: All rows from left table, matching from right (or NULL)
- RIGHT JOIN: Not supported in SQLite (use LEFT JOIN instead)
- CROSS JOIN: Cartesian product (every combination)
# Count records
cursor.execute('SELECT COUNT(*) FROM users')
total = cursor.fetchone()[0]
# Group by and aggregate
cursor.execute('''
SELECT age, COUNT(*) as count
FROM users
GROUP BY age
ORDER BY count DESC
''')
# Other aggregate functions: SUM, AVG, MIN, MAXAlways use with statements to ensure connections are properly closed:
with sqlite3.connect(':memory:') as conn:
cursor = conn.cursor()
cursor.execute('CREATE TABLE test (id INTEGER, name TEXT)')
cursor.execute('INSERT INTO test VALUES (1, "Alice")')
conn.commit()
# Connection automatically closed, even if an error occurs!By default, rows come back as tuples. For more readable code, use Row:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
for row in cursor.fetchall():
print(row['name'], row['email']) # Access by column name!- Building small to medium applications
- You need a simple embedded database
- Your app runs on a single machine
- You want zero configuration
- You're prototyping or learning
- You need multiple concurrent writers
- Building large-scale web applications
- You need advanced features (full-text search, geospatial data)
- You have heavy write traffic
- You need strict user permissions
- Forgetting to commit: Changes aren't saved until you call
conn.commit() - Not closing connections: Use
withstatements or always callconn.close() - SQL injection: Always use parameterized queries with
?placeholders - Type confusion: Remember SQLite is dynamically typed—be careful!
- Concurrent writes: SQLite locks the entire database on writes
import sqlite3
# Connect
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# Create
cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
# Insert
cursor.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
# Select
cursor.execute('SELECT * FROM users WHERE name = ?', ('Alice',))
rows = cursor.fetchall()
# Update
cursor.execute('UPDATE users SET name = ? WHERE id = ?', ('Bob', 1))
# Delete
cursor.execute('DELETE FROM users WHERE id = ?', (1,))
# Always commit and close
conn.commit()
conn.close()Now that you know SQLite basics, you can:
- Learn about database normalization (organizing data efficiently)
- Explore ORMs like SQLAlchemy (write Python instead of SQL)
- Try other databases like PostgreSQL
- Build real applications with persistent data storage
Check out example.py for a complete working example.
Try the practice problems in exercises.py to test your understanding.