Python SQLite Database
SQLite is a lightweight, file-based database that comes built-in with Python. The sqlite3 module provides a simple yet powerful interface for database operations without requiring a separate database server.
SQLite Basics
SQLite stores the entire database in a single file.
Connecting to Database
import sqlite3
# Connect to database (creates file if it doesn't exist)
conn = sqlite3.connect('example.db')
# Create cursor object
cursor = conn.cursor()
print("Connected to SQLite database")
print(f"Database file: example.db")
# Close connection
conn.close()sqlite3.connect() creates or opens a database file. Always close connections when done.
Basic Operations
import sqlite3
# Connect and create table
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Create table
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Insert data
cursor.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
("Alice", "[email protected]", 30))
cursor.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
("Bob", "[email protected]", 25))
# Save changes
conn.commit()
print("Table created and data inserted")
conn.close()Use ? placeholders to prevent SQL injection. conn.commit() saves changes.
CRUD Operations
Create, Read, Update, Delete operations.
Creating Records
import sqlite3
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Single insert
cursor.execute("""
INSERT INTO users (name, email, age)
VALUES (?, ?, ?)
""", ("Charlie", "[email protected]", 35))
# Multiple inserts
users_data = [
("David", "[email protected]", 28),
("Eve", "[email protected]", 32),
("Frank", "[email protected]", 45)
]
cursor.executemany("""
INSERT INTO users (name, email, age)
VALUES (?, ?, ?)
""", users_data)
conn.commit()
print(f"Inserted {cursor.rowcount} rows")
conn.close()executemany() efficiently inserts multiple rows.
Reading Records
import sqlite3
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Select all users
cursor.execute("SELECT id, name, email, age FROM users")
rows = cursor.fetchall()
print("All users:")
for row in rows:
user_id, name, email, age = row
print(f"ID: {user_id}, Name: {name}, Email: {email}, Age: {age}")
print(f"\nTotal users: {len(rows)}")
# Select with conditions
cursor.execute("SELECT name, age FROM users WHERE age > ?", (30,))
older_users = cursor.fetchall()
print(f"\nUsers older than 30: {older_users}")
conn.close()fetchall() returns all results as a list of tuples.
Updating Records
import sqlite3
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Update single record
cursor.execute("""
UPDATE users
SET age = ?, email = ?
WHERE name = ?
""", (31, "[email protected]", "Alice"))
print(f"Rows updated: {cursor.rowcount}")
# Update multiple records
cursor.execute("""
UPDATE users
SET age = age + 1
WHERE age < 30
""")
print(f"Young users aged: {cursor.rowcount}")
conn.commit()
conn.close()Use WHERE clauses to target specific records.
Deleting Records
import sqlite3
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Delete specific record
cursor.execute("DELETE FROM users WHERE name = ?", ("Frank",))
print(f"Deleted: {cursor.rowcount} rows")
# Delete with conditions
cursor.execute("DELETE FROM users WHERE age < ?", (25,))
print(f"Deleted young users: {cursor.rowcount} rows")
# Delete all records
cursor.execute("DELETE FROM users")
print(f"Cleared table: {cursor.rowcount} rows deleted")
conn.commit()
conn.close()Be careful with DELETE operations - they permanently remove data.
Advanced Queries
Complex SQL operations with SQLite.
Joins and Relationships
import sqlite3
conn = sqlite3.connect('company.db')
cursor = conn.cursor()
# Create tables
cursor.execute('''
CREATE TABLE IF NOT EXISTS departments (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
location TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
department_id INTEGER,
salary REAL,
FOREIGN KEY (department_id) REFERENCES departments (id)
)
''')
# Insert data
cursor.execute("INSERT INTO departments (name, location) VALUES (?, ?)",
("Engineering", "Building A"))
cursor.execute("INSERT INTO departments (name, location) VALUES (?, ?)",
("Sales", "Building B"))
cursor.execute("INSERT INTO employees (name, department_id, salary) VALUES (?, ?, ?)",
("Alice", 1, 75000))
cursor.execute("INSERT INTO employees (name, department_id, salary) VALUES (?, ?, ?)",
("Bob", 1, 70000))
cursor.execute("INSERT INTO employees (name, department_id, salary) VALUES (?, ?, ?)",
("Charlie", 2, 65000))
conn.commit()
# Join query
cursor.execute('''
SELECT e.name, d.name, e.salary
FROM employees e
JOIN departments d ON e.department_id = d.id
ORDER BY e.salary DESC
''')
print("Employee Department Report:")
for row in cursor.fetchall():
name, dept, salary = row
print(f"{name} - {dept} - ${salary}")
conn.close()SQLite supports JOIN operations for relational queries.
Aggregations and Grouping
import sqlite3
conn = sqlite3.connect('company.db')
cursor = conn.cursor()
# Aggregation queries
cursor.execute('''
SELECT
d.name,
COUNT(e.id) as employee_count,
AVG(e.salary) as avg_salary,
MAX(e.salary) as max_salary
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name
''')
print("Department Statistics:")
for row in cursor.fetchall():
dept_name, count, avg_salary, max_salary = row
print(f"{dept_name}: {count} employees, avg salary ${avg_salary:.0f}, max ${max_salary}")
# Having clause
cursor.execute('''
SELECT department_id, AVG(salary), COUNT(*)
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 1
''')
print("\nDepartments with multiple employees:")
for row in cursor.fetchall():
dept_id, avg_salary, count = row
print(f"Dept {dept_id}: {count} employees, avg salary ${avg_salary:.0f}")
conn.close()Use aggregate functions like COUNT, AVG, MAX with GROUP BY and HAVING.
Data Types and Constraints
SQLite data types and table constraints.
SQLite Data Types
import sqlite3
conn = sqlite3.connect('datatypes.db')
cursor = conn.cursor()
# Create table with various data types
cursor.execute('''
CREATE TABLE IF NOT EXISTS examples (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER,
height REAL,
is_active BOOLEAN,
birth_date DATE,
notes BLOB
)
''')
# Insert sample data
cursor.execute('''
INSERT INTO examples (name, age, height, is_active, birth_date, notes)
VALUES (?, ?, ?, ?, ?, ?)
''', ("Alice", 30, 5.6, True, "1993-05-15", b"Some binary data"))
conn.commit()
# Query with type checking
cursor.execute("SELECT name, age, height, is_active FROM examples")
row = cursor.fetchone()
name, age, height, is_active = row
print(f"Name: {name} (type: {type(name)})")
print(f"Age: {age} (type: {type(age)})")
print(f"Height: {height} (type: {type(height)})")
print(f"Active: {is_active} (type: {type(is_active)})")
conn.close()SQLite uses dynamic typing - data types are per-value, not per-column.
Constraints and Indexes
import sqlite3
conn = sqlite3.connect('constraints.db')
cursor = conn.cursor()
# Create table with constraints
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
price REAL NOT NULL CHECK(price > 0),
category TEXT DEFAULT 'General',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Create index for faster queries
cursor.execute("CREATE INDEX IF NOT EXISTS idx_category ON products(category)")
# Insert valid data
try:
cursor.execute("""
INSERT INTO products (name, price, category)
VALUES (?, ?, ?)
""", ("Laptop", 999.99, "Electronics"))
print("Product inserted successfully")
except sqlite3.IntegrityError as e:
print(f"Constraint violation: {e}")
# Try invalid data
try:
cursor.execute("""
INSERT INTO products (name, price)
VALUES (?, ?)
""", ("Laptop", -100)) # Negative price violates CHECK
except sqlite3.IntegrityError as e:
print(f"Check constraint failed: {e}")
conn.commit()
conn.close()Use constraints to maintain data integrity and indexes for performance.
Context Managers
Use context managers for automatic connection handling.
Custom Context Manager
import sqlite3
from contextlib import contextmanager
@contextmanager
def get_db_connection(db_name):
"""Context manager for database connections."""
conn = sqlite3.connect(db_name)
try:
yield conn
except Exception as e:
conn.rollback()
raise e
else:
conn.commit()
finally:
conn.close()
# Usage
with get_db_connection('test.db') as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS test (id INTEGER, name TEXT)")
cursor.execute("INSERT INTO test VALUES (1, 'Test')")
print("Operation completed successfully")
# Connection automatically closed and committedContext managers ensure proper resource cleanup.
Error Handling
Handle database errors gracefully.
Common Exceptions
import sqlite3
def safe_database_operation():
try:
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
# This might fail
cursor.execute("INSERT INTO nonexistent_table VALUES (?)", (1,))
conn.commit()
except sqlite3.OperationalError as e:
print(f"SQL error: {e}")
if conn:
conn.rollback()
except sqlite3.IntegrityError as e:
print(f"Constraint violation: {e}")
if conn:
conn.rollback()
except Exception as e:
print(f"Unexpected error: {e}")
if conn:
conn.rollback()
finally:
if conn:
conn.close()
safe_database_operation()Handle specific SQLite exceptions appropriately.
Performance Optimization
Techniques for faster database operations.
Prepared Statements
import sqlite3
import time
# Create test data
conn = sqlite3.connect('performance.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
timestamp TEXT,
level TEXT,
message TEXT
)
''')
# Method 1: Multiple executes (slower)
start = time.time()
for i in range(1000):
cursor.execute("""
INSERT INTO logs (timestamp, level, message)
VALUES (?, ?, ?)
""", (f"2023-01-{i%30:02d}", "INFO", f"Log message {i}"))
method1_time = time.time() - start
# Method 2: Execute many (faster)
cursor.execute("DELETE FROM logs") # Clear table
start = time.time()
data = [(f"2023-01-{i%30:02d}", "INFO", f"Log message {i}") for i in range(1000)]
cursor.executemany("""
INSERT INTO logs (timestamp, level, message)
VALUES (?, ?, ?)
""", data)
method2_time = time.time() - start
print(f"Multiple executes: {method1_time:.4f} seconds")
print(f"Execute many: {method2_time:.4f} seconds")
print(f"Speedup: {method1_time/method2_time:.1f}x")
conn.commit()
conn.close()executemany() is much faster for bulk inserts.
Connection Pooling
import sqlite3
import threading
from queue import Queue
class ConnectionPool:
def __init__(self, db_name, pool_size=5):
self.db_name = db_name
self.pool_size = pool_size
self.pool = Queue(maxsize=pool_size)
# Initialize pool
for _ in range(pool_size):
conn = sqlite3.connect(db_name, check_same_thread=False)
self.pool.put(conn)
def get_connection(self):
return self.pool.get()
def return_connection(self, conn):
self.pool.put(conn)
def close_all(self):
while not self.pool.empty():
conn = self.pool.get()
conn.close()
# Usage
pool = ConnectionPool('shared.db')
def worker_thread(thread_id):
conn = pool.get_connection()
cursor = conn.cursor()
cursor.execute("INSERT INTO test (thread_id) VALUES (?)", (thread_id,))
conn.commit()
pool.return_connection(conn)
print(f"Thread {thread_id} completed")
# Create test table
conn = pool.get_connection()
conn.execute("CREATE TABLE IF NOT EXISTS test (thread_id INTEGER)")
conn.commit()
pool.return_connection(conn)
# Run threads
threads = []
for i in range(10):
t = threading.Thread(target=worker_thread, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
pool.close_all()
print("All operations completed")Connection pooling improves performance in multi-threaded applications.
Best Practices
- Use context managers for connections
- Always commit or rollback transactions
- Use parameterized queries to prevent SQL injection
- Create indexes for frequently queried columns
- Use foreign keys for data integrity
- Handle exceptions appropriately
- Close connections when done
- Consider connection pooling for multi-threaded apps
External Resources:
Related Tutorials: