Python sqlite3 — Database Basics
sqlite3sqlite3 is Python’s built-in interface to SQLite, a tiny, serverless SQL database stored in a single file (or in memory). No setup, no server — perfect for scripts, prototypes, tests, and small apps.
quickstart.py
import sqlite3
conn = sqlite3.connect(":memory:") # database that lives in RAM
cur = conn.cursor()
cur.execute("CREATE TABLE users (id INTEGER, name TEXT)")
cur.execute("INSERT INTO users VALUES (1, 'Ada')")
conn.commit()
cur.execute("SELECT * FROM users")
print(cur.fetchall()) # [(1, 'Ada')]
conn.close()quickstart.py
import sqlite3
conn = sqlite3.connect(":memory:") # database that lives in RAM
cur = conn.cursor()
cur.execute("CREATE TABLE users (id INTEGER, name TEXT)")
cur.execute("INSERT INTO users VALUES (1, 'Ada')")
conn.commit()
cur.execute("SELECT * FROM users")
print(cur.fetchall()) # [(1, 'Ada')]
conn.close()The workflow
Every sqlite3 program follows the same shape:
- Connect to a database file (or
:memory::memory:). - Get a cursor to run SQL.
- execute statements.
- commit changes that modify data.
- fetch results from queries.
- close the connection.
| Object / method | Purpose |
|---|---|
sqlite3.connect(path)sqlite3.connect(path) | Open (or create) a database. |
conn.cursor()conn.cursor() | Create a cursor to run statements. |
cur.execute(sql, params)cur.execute(sql, params) | Run one SQL statement. |
cur.executemany(sql, seq)cur.executemany(sql, seq) | Run a statement for many rows. |
cur.fetchone()cur.fetchone() / fetchall()fetchall() | Get one row / all rows. |
conn.commit()conn.commit() | Save pending changes. |
conn.close()conn.close() | Close the connection. |
Creating tables and inserting
create_insert.py
import sqlite3
conn = sqlite3.connect("app.db") # creates app.db if missing
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
year INTEGER
)
""")
cur.execute("INSERT INTO books (title, year) VALUES (?, ?)", ("Dune", 1965))
conn.commit()
print("Inserted row id:", cur.lastrowid)
conn.close()create_insert.py
import sqlite3
conn = sqlite3.connect("app.db") # creates app.db if missing
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
year INTEGER
)
""")
cur.execute("INSERT INTO books (title, year) VALUES (?, ?)", ("Dune", 1965))
conn.commit()
print("Inserted row id:", cur.lastrowid)
conn.close()Parameterized queries — never use string formatting
Always pass values with ?? placeholders. Building SQL with f-strings or ++ opens the door to SQL injection and quoting bugs.
parameters.py
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE users (name TEXT, age INTEGER)")
# SAFE: values are passed separately
name = "Ada'; DROP TABLE users; --" # malicious input
cur.execute("INSERT INTO users VALUES (?, ?)", (name, 30))
# Insert many rows at once
rows = [("Bob", 25), ("Cara", 41)]
cur.executemany("INSERT INTO users VALUES (?, ?)", rows)
conn.commit()
cur.execute("SELECT * FROM users WHERE age > ?", (28,))
print(cur.fetchall()) # [("Ada'; DROP TABLE users; --", 30), ('Cara', 41)]
conn.close()parameters.py
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE users (name TEXT, age INTEGER)")
# SAFE: values are passed separately
name = "Ada'; DROP TABLE users; --" # malicious input
cur.execute("INSERT INTO users VALUES (?, ?)", (name, 30))
# Insert many rows at once
rows = [("Bob", 25), ("Cara", 41)]
cur.executemany("INSERT INTO users VALUES (?, ?)", rows)
conn.commit()
cur.execute("SELECT * FROM users WHERE age > ?", (28,))
print(cur.fetchall()) # [("Ada'; DROP TABLE users; --", 30), ('Cara', 41)]
conn.close()Rule: the SQL string stays constant; data goes through
??parameters.
Reading results
reading.py
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE nums (n INTEGER)")
cur.executemany("INSERT INTO nums VALUES (?)", [(i,) for i in range(5)])
conn.commit()
cur.execute("SELECT n FROM nums")
print(cur.fetchone()) # (0,) -> first row only
print(cur.fetchall()) # [(1,), (2,), (3,), (4,)] -> remaining rows
# A cursor is iterable — stream rows without loading all at once
cur.execute("SELECT n FROM nums")
for (value,) in cur:
print(value, end=" ") # 0 1 2 3 4
conn.close()reading.py
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE nums (n INTEGER)")
cur.executemany("INSERT INTO nums VALUES (?)", [(i,) for i in range(5)])
conn.commit()
cur.execute("SELECT n FROM nums")
print(cur.fetchone()) # (0,) -> first row only
print(cur.fetchall()) # [(1,), (2,), (3,), (4,)] -> remaining rows
# A cursor is iterable — stream rows without loading all at once
cur.execute("SELECT n FROM nums")
for (value,) in cur:
print(value, end=" ") # 0 1 2 3 4
conn.close()Rows as dictionaries
By default rows are tuples. Set a row_factoryrow_factory to access columns by name.
row_factory.py
import sqlite3
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row # rows behave like dicts
cur = conn.cursor()
cur.execute("CREATE TABLE p (id INTEGER, name TEXT)")
cur.execute("INSERT INTO p VALUES (1, 'Ada')")
row = cur.execute("SELECT * FROM p").fetchone()
print(row["name"]) # Ada
print(row["id"]) # 1
conn.close()row_factory.py
import sqlite3
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row # rows behave like dicts
cur = conn.cursor()
cur.execute("CREATE TABLE p (id INTEGER, name TEXT)")
cur.execute("INSERT INTO p VALUES (1, 'Ada')")
row = cur.execute("SELECT * FROM p").fetchone()
print(row["name"]) # Ada
print(row["id"]) # 1
conn.close()Connections as context managers
Using with conn:with conn: automatically commits on success or rolls back on error.
context_manager.py
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (x INTEGER)")
with conn: # commits automatically
conn.execute("INSERT INTO t VALUES (?)", (10,))
print(conn.execute("SELECT * FROM t").fetchall()) # [(10,)]
conn.close()context_manager.py
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (x INTEGER)")
with conn: # commits automatically
conn.execute("INSERT INTO t VALUES (?)", (10,))
print(conn.execute("SELECT * FROM t").fetchall()) # [(10,)]
conn.close()Common pitfalls
- Forgetting
commit()commit()— changes vanish when the program ends. (Awith conn:with conn:block handles this.) - String-built SQL — always parameterize with
??to stay safe. - A single value tuple needs a comma —
(28,)(28,), not(28)(28). :memory::memory:databases disappear when the connection closes — fine for tests, not for persistence.
Practice Exercises
Exercise 1 – Create and insert
Exercise 2 – Parameterized filter
Exercise 3 – Insert many and fetch all
Summary
sqlite3sqlite3is a zero-setup SQL database built into Python.- Follow connect → cursor → execute → commit → fetch → close.
- Always parameterize values with
??to avoid SQL injection. - Use
sqlite3.Rowsqlite3.Rowfor dict-like rows andwith conn:with conn:for automatic commit/rollback.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
