Python SQLite & SQL Fundamentals

๐Ÿ Python 3.12+ ๐ŸŸข Chapter 42 of 65 ๐Ÿ“‚ Phase 9: Databases and APIs ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: Relational DB Theory ยท SQLite Embedded Architecture ยท SQLite vs Client-Server ยท sqlite3 Module ยท CREATE TABLE DDL ยท INSERT & SELECT ยท executemany() ยท sqlite3.Row Mapping
Master relational databases and SQL in Python with deep conceptual foundations: understanding RDBMS architecture, tables, primary keys, relational data integrity, SQLite embedded engine internals, the sqlite3 standard library, CRUD operations, batch processing with executemany(), and dictionary row mapping with sqlite3.Row.
1Relational Database Concepts: What is an RDBMS & Why Do We Need It?

Before writing a single line of SQL, it is critical to understand why databases exist and why plain text files (like .txt or .csv) are inadequate for serious applications.

The Critical Flaws of File-Based Storage:

When you store application data in JSON or CSV files, every update requires reading the entire file into RAM, modifying the data structure, and writing the entire file back to disk. This causes four fatal problems:

  1. No Concurrency (Race Conditions): If two users attempt to purchase the last item in a store simultaneously, two Python threads will read the file at the same time, see 1 item in stock, and both write back 0, selling 2 items when only 1 existed!
  2. No Indexing ($O(N)$ Search Penalty): To find a user with ID 98421 in a 1,000,000-line CSV, Python must scan every single line from top to bottom ($O(N)$ time). Databases use B-Trees to find records in $O(log N)$ microsecond time.
  3. No Data Integrity Constraints: Plain files cannot stop someone from writing a string into an "age" column or creating an order for a user ID that doesn't exist.
  4. Lack of Crash Recovery (Atomicity): If power cuts out while rewriting a 50 MB CSV file, the file becomes corrupted and all data is permanently lost.

The Relational Model (Tables, Rows, Columns & Keys):

A Relational Database Management System (RDBMS) organizes information into two-dimensional Tables (also called Relations):

  • Columns (Attributes/Fields): Define the schema and data type of each property (e.g. id: INTEGER, name: TEXT, price: REAL).
  • Rows (Records/Tuples): Individual data entries representing a single real-world entity.
  • Primary Key (PK): A unique column (usually an auto-incrementing integer or UUID) that guarantees every row in the table can be uniquely identified. No two rows can have the same Primary Key.
  • Foreign Key (FK): A column in one table that points directly to the Primary Key of another table, establishing a verifiable relationship (e.g. orders.customer_id references customers.id).
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ RELATIONAL DATABASE MODEL โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ TABLE: customers โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ id (PK) โ”‚ name โ”‚ email โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1 โ”‚ Balaji Dev โ”‚ balaji@example.com โ”‚ โ”‚ โ”‚ โ”‚ 2 โ”‚ Alex Smith โ”‚ alex@example.com โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ 1-to-Many Relationship (One customer has many orders) โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ TABLE: orders โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ order_id โ”‚ customer_id (FK) โ”‚ total_amount โ”‚ order_date โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 101 โ”‚ 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€> โ‚น4,500.00 โ”‚ 2026-08-14 โ”‚ โ”‚ โ”‚ โ”‚ 102 โ”‚ 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€> โ‚น1,200.00 โ”‚ 2026-08-14 โ”‚ โ”‚ โ”‚ โ”‚ 103 โ”‚ 2 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€> โ‚น8,900.00 โ”‚ 2026-08-14 โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
๐Ÿ’ป Concept Blueprint: Relational Database Architecture Overview
# Conceptual demonstration: The building blocks of relational data
schema_explanation = {
    "Table": "A structured spreadsheet-like grid representing an entity (e.g. Products, Users)",
    "Column": "A typed field (e.g. price: REAL, email: TEXT UNIQUE)",
    "Row": "A single concrete instance/record stored in the table",
    "Primary Key": "Uniquely identifies each row (e.g. user_id = 101)",
    "Foreign Key": "Enforces relationship between tables (e.g. order.user_id -> user.id)"
}

for concept, definition in schema_explanation.items():
    print(f"๐Ÿ“Œ {concept:15}: {definition}")
๐Ÿ’ก Real-World Analogy:

Think of a Primary Key like an Aadhaar Number or Social Security Number: even if two people have the exact same name and birthday, their Primary Key uniquely distinguishes them. A Foreign Key is like writing that Aadhaar Number on a passport application to link your passport directly to your identity record.

2The SQLite Architecture: Why is SQLite Embedded & Serverless?

Most relational databases (such as PostgreSQL, MySQL, Oracle, and Microsoft SQL Server) operate as Client-Server systems:

  • They run as continuous background daemon processes on dedicated ports (e.g. port 5432 for Postgres, 3306 for MySQL).
  • Your Python program must establish a network TCP/IP socket connection, transmit credentials, and send queries across the network wire.

SQLite is fundamentally different: It is Serverless and Embedded.

The entire SQLite database engine is written in ANSI C and compiled directly into the Python interpreter itself. When your Python code queries SQLite, there are zero network calls, zero socket overhead, and zero port configurations. The entire database is stored in a single standalone binary file on your hard disk (or in RAM with ":memory:").

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ TRADITIONAL CLIENT-SERVER DATABASE โ”‚ โ”‚ SQLITE EMBEDDED ARCHITECTURE โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Python App โ”€โ”€โ”€(Network TCP Socket)โ”€โ”€โ”€> โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Python Script (sqlite3 standard lib) โ”‚ โ”‚ โ”‚ Remote Database Server (Postgres Daemon) โ”‚ โ”‚ โ”‚ โ”‚ (Direct In-Memory C-Call) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ User Auth & Network Port 5432 โ”‚ โ”‚ โ”‚ SQLite Engine (Compiled into Python) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Dedicated RAM & Multi-Process Storage โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€ Complex Server Management โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ โ”‚ โ”‚ Single Database File on Disk (app.db) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

When should you use SQLite?

  • Desktop and Mobile Applications: SQLite is the internal database engine used inside Android, iOS, Windows 11, macOS, Google Chrome, and Firefox.
  • Local Testing & Prototyping: Develop and run automated unit tests at lightning speed using in-memory databases (":memory:").
  • Low-to-Medium Traffic Web Apps: Websites handling up to ~100,000 requests/day with mostly read operations perform exceptionally well on SQLite (using Write-Ahead Logging / WAL mode).
๐Ÿ’ป Example 1: Establishing SQLite Connection and Creating Relational Tables
import sqlite3

# 1. Establish connection to local SQLite database file:
# If 'company.db' does not exist, SQLite automatically creates it on disk!
conn = sqlite3.connect("company.db")

# 2. Create a Cursor:
# The cursor acts as our execution pointer to send SQL commands to the engine:
cursor = conn.cursor()

# 3. Create 'departments' table using DDL (Data Definition Language):
cursor.execute("""
CREATE TABLE IF NOT EXISTS departments (
    dept_id INTEGER PRIMARY KEY AUTOINCREMENT,
    dept_name TEXT NOT NULL UNIQUE,
    budget REAL NOT NULL
)
""")

# 4. Create 'employees' table with Foreign Key linking to departments:
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees (
    emp_id INTEGER PRIMARY KEY AUTOINCREMENT,
    full_name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    salary REAL NOT NULL,
    dept_id INTEGER,
    joined_date DATE DEFAULT CURRENT_DATE,
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
)
""")

conn.commit() # Flush and save schema changes permanently to disk
print("โœ… Database 'company.db' initialized with relational departments and employees tables!")
conn.close()
๐Ÿ” Step-by-Step Code Walkthrough:
  1. sqlite3.connect("company.db") opens a file handle to company.db in the current folder. If you pass ":memory:", SQLite creates a temporary RAM database that vanishes when Python closes.
  2. cursor = conn.cursor() creates an execution context that tracks query state and fetches result sets.
  3. TEXT NOT NULL UNIQUE enforces data integrity: every employee must have an email, and no two employees can share the same email address.
  4. conn.commit() commits the transaction to non-volatile disk storage.
3Data Manipulation: Inserting, Querying, Updating & Deleting (CRUD)

CRUD stands for the four essential data operations in software engineering: Create (INSERT), Read (SELECT), Update (UPDATE), and Delete (DELETE).

1. Inserting Records (Single vs Batch executemany):

Every time you execute an individual INSERT with a commit, SQLite must sync bytes to the physical storage disk (fsync). If you insert 1,000 records one by one in a loop, it will take several seconds.

By using cursor.executemany(), all 1,000 records are prepared and inserted in a single disk I/O operation in under 5 milliseconds!

2. Querying Data & Row Factories (sqlite3.Row):

By default, SQLite returns query results as plain tuples: (1, 'Balaji', 95000.0). Accessing fields by numerical indices like row[1] or row[2] makes code brittle and unreadable.

By configuring conn.row_factory = sqlite3.Row, SQLite returns rich mapping objects that allow accessing columns both by case-insensitive column name (row['full_name']) and by index!

๐Ÿ’ป Example 2: Complete Relational CRUD & JOIN Queries with sqlite3.Row
import sqlite3

# Connect to database and configure row_factory:
conn = sqlite3.connect("company.db")
conn.row_factory = sqlite3.Row  # Enables column-name dictionary access!
cursor = conn.cursor()

# Enable SQLite foreign key enforcement:
cursor.execute("PRAGMA foreign_keys = ON")

# 1. CREATE: Batch Insert Departments:
departments_data = [
    ("Engineering", 5000000.0),
    ("Product Design", 2000000.0),
    ("Human Resources", 1200000.0)
]
cursor.executemany("INSERT OR IGNORE INTO departments (dept_name, budget) VALUES (?, ?)", departments_data)

# Batch Insert Employees:
employees_data = [
    ("Balaji Dev", "balaji.dev@company.com", 95000.0, 1),
    ("Alex Smith", "alex.smith@company.com", 82000.0, 1),
    ("Chloe Davis", "chloe.d@company.com", 78000.0, 2),
    ("David Miller", "david.m@company.com", 65000.0, 3)
]
cursor.executemany("INSERT OR IGNORE INTO employees (full_name, email, salary, dept_id) VALUES (?, ?, ?, ?)", employees_data)
conn.commit()

# 2. READ: Query Employees joined with their Department names:
print("--- ๐Ÿ“‹ Active Employees Report (SQL JOIN Query) ---")
cursor.execute("""
SELECT e.emp_id, e.full_name, e.salary, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary >= ?
ORDER BY e.salary DESC
""", (70000.0,))

high_earners = cursor.fetchall()
for emp in high_earners:
    # Notice clean dictionary-like column access:
    print(f"โ€ข ID #{emp['emp_id']}: {emp['full_name']:18} | Dept: {emp['dept_name']:15} | Salary: โ‚น{emp['salary']:,.2f}")

# 3. UPDATE: Give 10% raise to all Engineering employees:
cursor.execute("""
UPDATE employees
SET salary = salary * 1.10
WHERE dept_id = (SELECT dept_id FROM departments WHERE dept_name = 'Engineering')
""")
print(f"\n๐Ÿ“ˆ Promoted {cursor.rowcount} engineering employees with a 10% salary raise!")

# 4. DELETE: Remove an employee record safely:
cursor.execute("DELETE FROM employees WHERE email = ?", ("david.m@company.com",))
print(f"๐Ÿ—‘๏ธ Deleted {cursor.rowcount} employee record.")

conn.commit()
conn.close()
๐Ÿ” Deep Dive into CRUD Mechanics:
  • JOIN departments d ON e.dept_id = d.dept_id combines data from two tables in memory using the foreign key relationship.
  • cursor.rowcount returns the exact number of rows modified by the last UPDATE or DELETE statement.
  • PRAGMA foreign_keys = ON is required in SQLite because backward compatibility disables foreign key enforcement by default.
โš ๏ธ Common Developer Pitfall: Assuming SQLite Enforces Foreign Keys by Default Without PRAGMA

In SQLite, foreign key enforcement is DISABLED by default for backward compatibility with SQLite 2.0. You must explicitly execute "PRAGMA foreign_keys = ON" on every newly opened connection, otherwise invalid foreign keys will be silently accepted into your database!

๐Ÿ’ป Hands-on Interactive Practice Challenge

Create an in-memory SQLite table inventory (item_id, name, quantity). Insert 3 items, update the quantity of one item, and select all items with quantity > 10 using sqlite3.Row.

Python 3 Practice Challenge โ–ถ Run in Compiler
import sqlite3

conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
cur = conn.cursor()

cur.execute("CREATE TABLE inventory (item_id INTEGER PRIMARY KEY, name TEXT, quantity INTEGER)")
cur.executemany("INSERT INTO inventory VALUES (?, ?, ?)", [
    (1, "SSD 1TB", 25),
    (2, "RAM 16GB DDR5", 8),
    (3, "Power Supply 750W", 14)
])
cur.execute("UPDATE inventory SET quantity = 30 WHERE item_id = 1")
conn.commit()

cur.execute("SELECT * FROM inventory WHERE quantity > 10")
for row in cur.fetchall():
    print(f"Item #{row['item_id']}: {row['name']} (In Stock: {row['quantity']})")
conn.close()
Run This Challenge in Online Python IDE โ†’
โ“ Frequently Asked Questions (FAQ)

Q Why should I use SQLite instead of JSON or CSV files for desktop apps?

SQLite provides instant O(log N) indexing, zero-risk atomic transactions (preventing file corruption on power cut), concurrency management, and relational foreign keys, all inside a single self-contained file with zero installation overhead.

Q How large can an SQLite database grow on disk?

SQLite supports databases up to 281 Terabytes in size and tables with up to 1 billion rows, making it capable of handling large datasets.

Q What is Write-Ahead Logging (WAL) in SQLite?

WAL mode (PRAGMA journal_mode=WAL;) writes modifications to a separate .wal log file before committing to the main database file. This allows simultaneous readers to read uninterrupted while a writer writes, increasing read/write throughput.

OC
Written by Our Compiler Technical Editorial Team
Reviewed for accuracy & tested on Python 3.12+ runtime ยท Last updated August 2026