Python sqlite3.Connection.cursor Method
Last modified April 15, 2025
This comprehensive guide explores Python's sqlite3.Connection.cursor
method, the primary way to execute SQL statements in SQLite databases. We'll cover
basic usage, execution methods, and practical examples with proper resource
management.
Basic Definitions
The cursor method creates a cursor object associated with the
database connection. Cursors are used to execute SQL commands and fetch results.
Key characteristics: each cursor maintains its own state, can execute multiple statements, and manages result sets. Cursors are lightweight but should be properly closed when no longer needed.
Basic Cursor Usage
Here's the simplest usage of cursor to create a cursor and execute
basic SQL statements.
import sqlite3
with sqlite3.connect('example.db') as conn:
cursor = conn.cursor()
# Create table
cursor.execute('''CREATE TABLE IF NOT EXISTS products
(id INTEGER PRIMARY KEY, name TEXT, price REAL)''')
# Insert data
cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)",
('Laptop', 999.99))
# Query data
cursor.execute("SELECT * FROM products")
print(cursor.fetchall())
This example shows the basic workflow: create cursor, execute SQL statements, and
fetch results. The with statement ensures proper resource cleanup.
The cursor is automatically closed when the connection context exits. Parameterized queries (with ? placeholders) are used for safe value insertion.
Executing Multiple Statements
A single cursor can execute multiple SQL statements sequentially. This example demonstrates batch operations.
import sqlite3
with sqlite3.connect('inventory.db') as conn:
cursor = conn.cursor()
# Execute multiple statements
cursor.executescript('''
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category_id INTEGER,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
INSERT INTO categories (name) VALUES ('Electronics');
INSERT INTO categories (name) VALUES ('Clothing');
''')
# Verify inserts
cursor.execute("SELECT * FROM categories")
print("Categories:", cursor.fetchall())
The executescript method executes multiple SQL statements at once.
This is useful for schema setup or batch operations. Each statement must be
properly terminated with a semicolon.
Note that executescript commits any pending transaction before
execution. Use it carefully in production code.
Parameterized Queries with Cursor
Cursors support various parameter substitution methods for safe SQL construction. This example shows different parameter styles.
import sqlite3
with sqlite3.connect('sales.db') as conn:
cursor = conn.cursor()
# qmark style (default)
cursor.execute("INSERT INTO orders VALUES (?, ?, ?)",
(1, '2023-01-15', 149.99))
# named style
cursor.execute("INSERT INTO orders VALUES (:id, :date, :amount)",
{'id': 2, 'date': '2023-01-16', 'amount': 299.99})
# format style (not recommended for security)
cursor.execute("INSERT INTO orders VALUES (%s, %s, %s)" %
(3, "'2023-01-17'", 199.99))
# Verify inserts
cursor.execute("SELECT * FROM orders")
print("Orders:", cursor.fetchall())
This example demonstrates three parameter styles: qmark (?), named (:name), and format (%s). The qmark and named styles are recommended for security.
Always prefer parameterized queries over string formatting to prevent SQL injection attacks. The format style is shown for comparison but should be avoided.
Cursor with Custom Row Factory
Cursors can return rows in different formats using row factories. This example shows how to customize row output.
import sqlite3
from collections import namedtuple
with sqlite3.connect('employees.db') as conn:
# Set row factory for the connection
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS employees
(id INTEGER PRIMARY KEY, name TEXT, department TEXT)''')
cursor.execute("INSERT INTO employees (name, department) VALUES (?, ?)",
('Alice', 'Engineering'))
# Fetch as dictionary-like Row object
cursor.execute("SELECT * FROM employees")
row = cursor.fetchone()
print("Row as dict:", row['name'], row['department'])
# Alternative: namedtuple factory
def namedtuple_factory(cursor, row):
fields = [col[0] for col in cursor.description]
Employee = namedtuple('Employee', fields)
return Employee(*row)
cursor.row_factory = namedtuple_factory
cursor.execute("SELECT * FROM employees")
employee = cursor.fetchone()
print("Row as namedtuple:", employee.name, employee.department)
This example shows two row factory approaches: SQLite's built-in Row
factory and a custom namedtuple factory. Both allow named access to
columns.
Row factories can be set at connection or cursor level. They greatly improve code readability when working with result sets.
Cursor with Transactions
Cursors play a key role in transaction management. This example demonstrates manual transaction control.
import sqlite3
with sqlite3.connect('bank.db', isolation_level=None) as conn:
cursor = conn.cursor()
try:
# Begin transaction explicitly
cursor.execute("BEGIN")
# Transfer funds
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
# Simulate error
# raise ValueError("Simulated error")
# Commit if successful
cursor.execute("COMMIT")
print("Transaction completed successfully")
except Exception as e:
# Rollback on error
cursor.execute("ROLLBACK")
print("Transaction failed:", e)
This example shows manual transaction control with BEGIN, COMMIT, and ROLLBACK. The isolation_level=None setting allows explicit transaction management.
Proper transaction handling is crucial for data integrity. The with
statement ensures the cursor is closed even if an error occurs.
Cursor with Context Manager
Cursors can be used as context managers for automatic cleanup. This example shows the recommended pattern.
import sqlite3
from contextlib import closing
with sqlite3.connect('library.db') as conn:
with closing(conn.cursor()) as cursor:
cursor.execute('''CREATE TABLE IF NOT EXISTS books
(id INTEGER PRIMARY KEY, title TEXT, author TEXT)''')
# Batch insert with executemany
books = [
(1, 'Python Basics', 'John Doe'),
(2, 'Advanced SQL', 'Jane Smith'),
(3, 'Web Development', 'Alice Johnson')
]
cursor.executemany("INSERT INTO books VALUES (?, ?, ?)", books)
# Iterate through results
cursor.execute("SELECT * FROM books")
for row in cursor:
print(row)
This example uses contextlib.closing to ensure the cursor is properly
closed. The executemany method efficiently inserts multiple rows.
Cursor iteration (for row in cursor) is memory-efficient for large
result sets. The double with statements manage both connection and
cursor resources.
Cursor with Custom Types
Cursors can handle custom Python types through adapters and converters. This example demonstrates type customization.
import sqlite3
import json
from datetime import datetime
# Custom type adapter
def adapt_dict(d):
return json.dumps(d)
# Custom type converter
def convert_dict(s):
return json.loads(s)
# Register the adapter and converter
sqlite3.register_adapter(dict, adapt_dict)
sqlite3.register_converter("JSON", convert_dict)
with sqlite3.connect('data.db', detect_types=sqlite3.PARSE_DECLTYPES) as conn:
cursor = conn.cursor()
# Create table with custom type
cursor.execute('''CREATE TABLE IF NOT EXISTS events
(id INTEGER PRIMARY KEY,
timestamp TIMESTAMP,
data JSON)''')
# Insert custom types
event = {
'type': 'login',
'user': 'alice',
'ip': '192.168.1.1'
}
cursor.execute("INSERT INTO events (timestamp, data) VALUES (?, ?)",
(datetime.now(), event))
# Retrieve custom types
cursor.execute("SELECT * FROM events")
row = cursor.fetchone()
print("Timestamp type:", type(row[1])) # datetime
print("Data type:", type(row[2])) # dict
This example registers custom adapters and converters for Python dictionaries and
datetime objects. The PARSE_DECLTYPES flag enables type detection.
Custom type handling allows seamless storage and retrieval of complex Python objects while maintaining database compatibility.
Best Practices
- Always close cursors: Use context managers or explicit close()
- Prefer parameterized queries: Avoid SQL injection vulnerabilities
- Manage transactions properly: Commit or rollback as needed
- Use appropriate fetch methods: fetchone(), fetchmany(), or fetchall()
- Consider cursor arraysize: Optimize for batch operations
Source References
Author
List all Python tutorials.