Skip to main content

Module 24 - Database Access

Python's sqlite3 module provides a lightweight disk-based database that doesn't require a separate server. It's perfect for learning SQL and building small to medium applications.


1. Introduction to SQLite

SQLite is a self-contained, serverless SQL database engine embedded in Python's standard library.

Advantages

✅ No separate server required
✅ Zero configuration
✅ Cross-platform
✅ Lightweight and fast
✅ ACID compliant

When to Use SQLite
  • Desktop applications
  • Mobile apps
  • Small to medium websites
  • Embedded systems
  • Development and testing

2. Connecting to a Database

import sqlite3

# Connect to database (creates if doesn't exist)
conn = sqlite3.connect('mydata base.db')

# Create cursor object
cursor = conn.cursor()

# Execute SQL commands...

# Close connection
conn.close()
import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()
# Execute SQL commands
# Automatically commits and closes

In-Memory Database

# Useful for testing
conn = sqlite3.connect(':memory:')

3. Creating Tables

Basic Table Creation

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Create table
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL,
age INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')

conn.commit()
print("Table created successfully")

Data Types in SQLite

SQLite TypePython TypeDescription
NULLNoneNull value
INTEGERintSigned integer
REALfloatFloating point
TEXTstrText string
BLOBbytesBinary data

4. CRUD Operations

4.1 Create (INSERT)

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Insert single record
cursor.execute('''
INSERT INTO users (username, email, age)
VALUES (?, ?, ?)
''', ('alice', 'alice@example.com', 25))

# Insert multiple records
users = [
('bob', 'bob@example.com', 30),
('charlie', 'charlie@example.com', 35),
('diana', 'diana@example.com', 28)
]
cursor.executemany('''
INSERT INTO users (username, email, age)
VALUES (?, ?, ?)
''', users)

conn.commit()
print(f"Inserted {cursor.rowcount} rows")
SQL Injection

Always use parameterized queries with ? placeholders. Never use string formatting!

# ❌ NEVER DO THIS (SQL injection risk)
username = "alice'; DROP TABLE users; --"
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")

# ✅ ALWAYS DO THIS
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))

4.2 Read (SELECT)

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Fetch all records
cursor.execute('SELECT * FROM users')
rows = cursor.fetchall()
for row in rows:
print(row)

# Fetch one record
cursor.execute('SELECT * FROM users WHERE username = ?', ('alice',))
user = cursor.fetchone()
print(user)

# Fetch multiple records
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))
users = cursor.fetchmany(2) # Fetch 2 records
print(users)

# Iterate over results
cursor.execute('SELECT username, email FROM users')
for username, email in cursor:
print(f"{username}: {email}")

4.3 Update (UPDATE)

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Update single record
cursor.execute('''
UPDATE users
SET email = ?
WHERE username = ?
''', ('newemail@example.com', 'alice'))

# Update multiple fields
cursor.execute('''
UPDATE users
SET age = ?, email = ?
WHERE username = ?
''', (26, 'alice_new@example.com', 'alice'))

conn.commit()
print(f"Updated {cursor.rowcount} rows")

4.4 Delete (DELETE)

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Delete specific record
cursor.execute('DELETE FROM users WHERE username = ?', ('bob',))

# Delete with condition
cursor.execute('DELETE FROM users WHERE age < ?', (25,))

conn.commit()
print(f"Deleted {cursor.rowcount} rows")

5. Advanced Queries

5.1 WHERE Clauses

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Single condition
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))

# Multiple conditions (AND)
cursor.execute('''
SELECT * FROM users
WHERE age > ? AND username LIKE ?
''', (25, '%a%'))

# OR condition
cursor.execute('''
SELECT * FROM users
WHERE age < ? OR username = ?
''', (25, 'alice'))

# IN clause
cursor.execute('''
SELECT * FROM users
WHERE username IN (?, ?, ?)
''', ('alice', 'bob', 'charlie'))

results = cursor.fetchall()

5.2 Sorting and Limiting

# ORDER BY
cursor.execute('SELECT * FROM users ORDER BY age DESC')

# LIMIT
cursor.execute('SELECT * FROM users LIMIT 5')

# OFFSET (for pagination)
cursor.execute('SELECT * FROM users LIMIT 10 OFFSET 20')

# Combine
cursor.execute('''
SELECT username, age FROM users
WHERE age > ?
ORDER BY age DESC
LIMIT 5
''', (25,))

5.3 Aggregate Functions

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# COUNT
cursor.execute('SELECT COUNT(*) FROM users')
count = cursor.fetchone()[0]
print(f"Total users: {count}")

# AVG
cursor.execute('SELECT AVG(age) FROM users')
avg_age = cursor.fetchone()[0]
print(f"Average age: {avg_age:.1f}")

# MIN and MAX
cursor.execute('SELECT MIN(age), MAX(age) FROM users')
min_age, max_age = cursor.fetchone()
print(f"Age range: {min_age}-{max_age}")

# GROUP BY
cursor.execute('''
SELECT age, COUNT(*) as count
FROM users
GROUP BY age
ORDER BY count DESC
''')
for age, count in cursor:
print(f"Age {age}: {count} users")

6. Working with Multiple Tables

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

# Create posts table
cursor.execute('''
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')

conn.commit()

6.2 INNER JOIN

# Get posts with user information
cursor.execute('''
SELECT users.username, posts.title, posts.content
FROM posts
INNER JOIN users ON posts.user_id = users.id
''')

for username, title, content in cursor:
print(f"{username}: {title}")

6.3 LEFT JOIN

# Get all users and their posts (including users with no posts)
cursor.execute('''
SELECT users.username, posts.title
FROM users
LEFT JOIN posts ON users.id = posts.user_id
''')

7. Practical Example: User Management System

import sqlite3
from typing import List, Dict, Optional
from datetime import datetime

class UserDatabase:
def __init__(self, db_name: str = 'users.db'):
self.db_name = db_name
self._create_tables()

def _create_tables(self):
"""Create database tables"""
with sqlite3.connect(self.db_name) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL,
age INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()

def add_user(self, username: str, email: str, age: int) -> int:
"""Add new user"""
with sqlite3.connect(self.db_name) as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO users (username, email, age)
VALUES (?, ?, ?)
''', (username, email, age))
conn.commit()
return cursor.lastrowid

def get_user(self, user_id: int) -> Optional[Dict]:
"""Get user by ID"""
with sqlite3.connect(self.db_name) as conn:
conn.row_factory = sqlite3.Row # Return rows as dictionaries
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
row = cursor.fetchone()
return dict(row) if row else None

def get_all_users(self) -> List[Dict]:
"""Get all users"""
with sqlite3.connect(self.db_name) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
return [dict(row) for row in cursor.fetchall()]

def update_user(self, user_id: int, **kwargs) -> bool:
"""Update user fields"""
fields = ', '.join(f"{k} = ?" for k in kwargs.keys())
values = list(kwargs.values()) + [user_id]

with sqlite3.connect(self.db_name) as conn:
cursor = conn.cursor()
cursor.execute(f'''
UPDATE users
SET {fields}
WHERE id = ?
''', values)
conn.commit()
return cursor.rowcount > 0

def delete_user(self, user_id: int) -> bool:
"""Delete user"""
with sqlite3.connect(self.db_name) as conn:
cursor = conn.cursor()
cursor.execute('DELETE FROM users WHERE id = ?', (user_id,))
conn.commit()
return cursor.rowcount > 0

def search_users(self, keyword: str) -> List[Dict]:
"""Search users by username or email"""
with sqlite3.connect(self.db_name) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM users
WHERE username LIKE ? OR email LIKE ?
''', (f'%{keyword}%', f'%{keyword}%'))
return [dict(row) for row in cursor.fetchall()]

# Usage
db = UserDatabase()

# Add users
user_id = db.add_user('alice', 'alice@example.com', 25)
print(f"Added user with ID: {user_id}")

# Get user
user = db.get_user(user_id)
print(f"User: {user}")

# Update user
db.update_user(user_id, email='alice_new@example.com', age=26)

# Search users
results = db.search_users('alice')
print(f"Search results: {results}")

# Delete user
db.delete_user(user_id)

8. Best Practices

8.1 Connection Pooling

import sqlite3
from contextlib import contextmanager

class DatabasePool:
def __init__(self, db_name: str):
self.db_name = db_name

@contextmanager
def get_connection(self):
"""Context manager for database connections"""
conn = sqlite3.connect(self.db_name)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()

# Usage
pool = DatabasePool('mydatabase.db')

with pool.get_connection() as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
users = cursor.fetchall()

8.2 Transactions

import sqlite3

with sqlite3.connect('mydatabase.db') as conn:
cursor = conn.cursor()

try:
# Start transaction
cursor.execute('BEGIN')

# Multiple operations
cursor.execute('UPDATE users SET age = age + 1 WHERE id = ?', (1,))
cursor.execute('INSERT INTO logs (message) VALUES (?)', ('Age updated',))

# Commit transaction
conn.commit()
print("Transaction committed")

except Exception as e:
# Rollback on error
conn.rollback()
print(f"Transaction rolled back: {e}")

9. SQLite Limitations

FeatureSQLiteMySQL/PostgreSQL
Concurrent writes❌ Limited✅ Full support
User permissions❌ None✅ Granular
Size limit~140 TBMuch larger
Network access❌ File-based✅ Client-server
Use caseSmall appsLarge apps

10. Migrating to Other Databases

Using SQLAlchemy (ORM)

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
__tablename__ = 'users'

id = Column(Integer, primary_key=True)
username = Column(String, unique=True, nullable=False)
email = Column(String, nullable=False)
age = Column(Integer)

# SQLite
engine = create_engine('sqlite:///mydatabase.db')

# Easily switch to PostgreSQL or MySQL
# engine = create_engine('postgresql://user:pass@localhost/dbname')
# engine = create_engine('mysql://user:pass@localhost/dbname')

Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)

Summary

✅ SQLite is embedded in Python's standard library
✅ Use parameterized queries to prevent SQL injection
✅ CRUD operations: INSERT, SELECT, UPDATE, DELETE
✅ Support for joins, aggregates, and complex queries
✅ Perfect for small to medium applications
✅ Use context managers for automatic cleanup


Next Steps

In Module 25, you'll learn:

  • NumPy basics
  • Array operations
  • Mathematical computations
  • Scientific computing with Python

Practice Exercises

  1. Create a todo list application with SQLite backend
  2. Build a contact management system with search functionality
  3. Implement a simple blog with posts and comments tables
  4. Create a library management system (books, authors, borrowers)
  5. Build a database migration script to upgrade schema versions
Challenge

Create a complete inventory management system that:

  • Tracks products, categories, and suppliers
  • Supports transactions (sales, purchases, returns)
  • Maintains stock levels with alerts
  • Generates reports (sales by period, low stock)
  • Includes full CRUD operations with proper error handling
  • Uses transactions for data integrity