Get AI summaries of any video or article — Sign up free
Python SQLite Tutorial: Complete Overview - Creating a Database, Table, and Running Queries thumbnail

Python SQLite Tutorial: Complete Overview - Creating a Database, Table, and Running Queries

Corey Schafer·
5 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

SQLite is available via Python’s standard library (`sqlite3`), enabling database work without installing servers or extra packages.

Briefing

SQLite in Python can be set up with almost no infrastructure: import the built-in `sqlite3` module, connect to a disk-backed database file (or an in-memory database), create tables, and run CRUD operations through SQL executed by a cursor. The practical payoff is fast prototyping—especially for small-to-medium apps, testing, and later migration to heavier databases like MySQL or Postgres.

The tutorial starts by creating a connection using `sqlite3.connect()`. Passing a filename such as `employee.db` creates the database file automatically if it doesn’t exist, and reconnecting later reuses it. For ephemeral testing, it also demonstrates `:memory:` to create a fresh database in RAM on every run. After connecting, a cursor is created (`con.cursor()`), and SQL statements are executed with `cursor.execute()`.

A full example builds an `employees` table with three columns: `first` (TEXT), `last` (TEXT), and `pay` (INTEGER). The workflow emphasizes transaction handling: after DDL or data changes, `con.commit()` is required, and forgetting it is a common reason changes appear missing. Closing the connection (`con.close()`) is treated as good hygiene. When the table-creation code runs a second time, SQLite correctly errors because the table already exists—useful feedback that the schema persists in the file-backed database.

With the schema in place, the tutorial inserts employee rows using `INSERT INTO employees VALUES (...)`, then verifies them using `SELECT` queries with a `WHERE` clause. It demonstrates how to retrieve results using `fetchone()` (single row or `None`), `fetchall()` (a list of rows), and briefly contrasts `fetchmany(n)` for partial batching. Hard-coded values are used first for clarity, then the example shifts to parameterized queries so Python variables can safely feed into SQL.

A key security lesson follows: string formatting inside SQL is discouraged because it can enable SQL injection when user-controlled input is involved. Instead, the tutorial shows two DB-API-safe placeholder patterns. One uses `?` placeholders with a tuple of values passed as the second argument to `execute()`. The other uses named placeholders like `:first`, `:last`, and `:pay` with a dictionary mapping placeholder names to values. Both approaches keep quoting and escaping handled by SQLite’s parameter system.

Finally, the tutorial turns the scattered SQL snippets into a small, reusable CRUD layer. It defines functions to insert employees, fetch employees by last name, update pay, and delete employees. For inserts/updates/deletes, it uses the connection as a context manager (`with con:`), which automatically commits on success and rolls back on exceptions—removing the need to manually call `commit()` after every write. The end-to-end demo inserts two employees, retrieves them, updates one employee’s pay, deletes another, and re-queries to confirm the changes.

The closing takeaway is that SQLite supports lightweight prototyping now and smoother upgrades later. It also notes compatibility with SQLAlchemy, an ORM that can abstract database differences, making it easier to swap SQLite for MySQL or Postgres when the application grows.

Cornell Notes

SQLite can be used from Python with the standard library (`sqlite3`) to create a database file or an in-memory database, define tables, and run CRUD operations. A typical flow is: connect → cursor → `execute()` SQL → `commit()` for schema/data changes → close the connection. The tutorial stresses safe parameterized queries using `?` placeholders (tuple values) or named placeholders like `:last` (dictionary values), avoiding string formatting that can lead to SQL injection. For cleaner code, inserts/updates/deletes can be wrapped in `with con:` so transactions commit automatically and roll back on errors. This structure scales from quick demos to a small reusable CRUD API for employees.

How does SQLite connection setup differ between a persistent database file and an in-memory database?

A persistent database uses a filename in `sqlite3.connect()`, such as `employee.db`. SQLite creates the file if it doesn’t exist and reuses it on later runs, so the schema (like the `employees` table) persists. An in-memory database uses `sqlite3.connect(':memory:')`, which creates a fresh database in RAM each run—so tables and inserted rows start from scratch every time.

Why is `commit()` necessary, and what happens if it’s omitted?

After executing statements that change the database—like `CREATE TABLE`, `INSERT`, `UPDATE`, or `DELETE`—the changes must be committed with `con.commit()`. Without committing, later queries may not reflect the intended changes, leading to confusion. The tutorial also warns that it’s easy to accidentally commit the wrong object or forget the commit step entirely.

What’s the difference between `fetchone()`, `fetchall()`, and `fetchmany(n)` in practice?

`fetchone()` returns the next matching row or `None` if no rows remain—useful when the query should return a single result. `fetchall()` returns all remaining rows as a list, which is convenient for “get all employees matching this last name.” `fetchmany(n)` returns up to `n` rows as a list, useful when processing large result sets in chunks.

What two parameterization styles are shown to prevent SQL injection, and how do they work?

One style uses `?` placeholders in SQL and passes a tuple of values as the second argument to `execute()`. Even a single value must be wrapped in a one-element tuple (e.g., `('Schaeffer',)`). The second style uses named placeholders like `:first`, `:last`, and `:pay`, and passes a dictionary mapping placeholder names to values (e.g., `{'last': 'Doe', 'pay': 90000, 'first': 'Jane'}`). Both let SQLite handle quoting/escaping safely.

How does using `with con:` improve transaction handling for write operations?

Wrapping write operations in `with con:` turns the connection into a context manager that automatically commits on success. If an exception occurs inside the block, SQLite rolls back the transaction. This removes repetitive manual `con.commit()` calls after every `INSERT`, `UPDATE`, or `DELETE`.

Review Questions

  1. When would you choose `:memory:` over a filename like `employee.db`, and what effect does that choice have on table creation errors (e.g., “table already exists”)?
  2. Why does the tutorial discourage SQL string formatting for query parameters, and what placeholder mechanisms replace it?
  3. In the CRUD function approach, which operations should be wrapped in `with con:` and why?

Key Points

  1. 1

    SQLite is available via Python’s standard library (`sqlite3`), enabling database work without installing servers or extra packages.

  2. 2

    Use `sqlite3.connect('file.db')` for persistent storage and `sqlite3.connect(':memory:')` for a clean database on every run.

  3. 3

    Create tables with `cursor.execute()` and remember to commit schema/data changes using `con.commit()` (or `with con:` for writes).

  4. 4

    Retrieve query results with `fetchone()` for a single row and `fetchall()` for multiple rows; `fetchmany(n)` supports chunked reads.

  5. 5

    Avoid building SQL with string formatting when parameters come from outside sources; use parameterized queries with `?` or named placeholders like `:last`.

  6. 6

    Parameter binding requires correct argument types: `?` placeholders use tuples (including one-element tuples), while named placeholders use dictionaries keyed by placeholder names.

  7. 7

    Wrap `INSERT`, `UPDATE`, and `DELETE` in `with con:` to get automatic commit/rollback and cleaner CRUD function code.

Highlights

SQLite creates the database file automatically when connecting to a new filename like `employee.db`, and the schema persists across runs.
`fetchone()` returns `None` when no rows match, while `fetchall()` returns an empty list—both behaviors make control flow straightforward.
Using `?` placeholders with a tuple (and a one-element tuple for single values) prevents SQL injection compared with string formatting.
Named placeholders like `:first` and `:pay` make multi-parameter queries more readable by tying values to explicit keys.
`with con:` turns transaction management into a safety net: successful writes commit automatically; exceptions trigger rollback.

Topics

  • SQLite Basics
  • Database Connections
  • SQL Table Creation
  • Parameterized Queries
  • CRUD Functions

Mentioned