ZetCode

Python sqlite3.Cursor.execute Method

Last modified April 15, 2025

This comprehensive guide explores Python's sqlite3.Cursor.execute method, the primary way to execute SQL statements in SQLite databases. We'll cover basic usage, parameter binding, transaction handling, and practical examples.

Basic Definitions

The sqlite3.Cursor.execute method executes a single SQL statement. It takes an SQL query as its first parameter and optional parameters for binding values to the query.

Key characteristics: it returns the cursor object for chaining, supports parameterized queries for security, and can execute any valid SQL statement. The method is the workhorse for all database operations.

Basic SQL Execution

Here's the simplest usage of execute to create a table and insert data. The example uses context managers for automatic resource cleanup.

basic_execute.py
import sqlite3

with sqlite3.connect('example.db') as conn:
    with conn.cursor() as 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))
        
        # Commit is automatic with context manager

This example shows the basic workflow: connect to database, create cursor, execute SQL statements. The context managers ensure proper resource cleanup.

The ? placeholders in the INSERT statement demonstrate parameterized queries, which prevent SQL injection and handle proper value escaping.

Parameter Binding Styles

The execute method supports different parameter binding styles. This example shows all three supported formats.

parameter_styles.py
import sqlite3

with sqlite3.connect('example.db') as conn:
    with conn.cursor() as cursor:
        # Question mark style (positional)
        cursor.execute("INSERT INTO products VALUES (?, ?, ?)", 
                      (2, 'Mouse', 19.99))
        
        # Named style
        cursor.execute("INSERT INTO products VALUES (:id, :name, :price)",
                      {'id': 3, 'name': 'Keyboard', 'price': 49.99})
        
        # Numbered style
        cursor.execute("INSERT INTO products VALUES (?1, ?2, ?3)",
                      (4, 'Monitor', 199.99))
        
        conn.commit()

SQLite supports three parameter binding styles: question mark (positional), named, and numbered. Each has advantages depending on the use case.

Named parameters are especially useful with complex queries containing many parameters, as they make the code more readable and maintainable.

Executing Multiple Statements

The executescript method allows executing multiple SQL statements at once. This is useful for schema initialization or bulk operations.

executescript.py
import sqlite3

sql_script = """
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS customers (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE
);
CREATE TABLE IF NOT EXISTS orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER,
    amount REAL,
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);
COMMIT;
"""

with sqlite3.connect('shop.db') as conn:
    with conn.cursor() as cursor:
        cursor.executescript(sql_script)

This example creates a complete database schema with two related tables in a single transaction. The executescript method parses and executes the entire script.

Note that executescript commits any pending transaction before executing and commits again after execution. It's ideal for setup scripts.

Executing Many Statements

The executemany method efficiently executes the same SQL statement multiple times with different parameters. This is much faster than individual executes for bulk inserts.

executemany.py
import sqlite3

products = [
    (5, 'Headphones', 79.99),
    (6, 'Webcam', 59.99),
    (7, 'Microphone', 89.99),
    (8, 'Speaker', 129.99)
]

with sqlite3.connect('example.db') as conn:
    with conn.cursor() as cursor:
        cursor.executemany("INSERT INTO products VALUES (?, ?, ?)", products)
        print(f"Inserted {cursor.rowcount} rows")

This example inserts multiple rows in a single efficient operation. The executemany method is optimized for this pattern.

The rowcount property returns the number of rows affected by the last operation. For executemany, it shows total affected rows.

Fetching Results

After executing a SELECT statement, you can fetch results using various methods. This example demonstrates the different fetching approaches.

fetching_results.py
import sqlite3

with sqlite3.connect('example.db') as conn:
    with conn.cursor() as cursor:
        # Execute SELECT
        cursor.execute("SELECT * FROM products WHERE price > ?", (50,))
        
        # Fetch one row
        print("First product over $50:", cursor.fetchone())
        
        # Fetch next 2 rows
        print("Next two products:", cursor.fetchmany(2))
        
        # Fetch all remaining rows
        print("All remaining products:", cursor.fetchall())

The example shows three fetching methods: fetchone for single rows, fetchmany for a specified number of rows, and fetchall for all remaining rows.

The cursor maintains state about the result set, so subsequent fetch calls continue where the previous one left off. This allows efficient processing of large result sets.

Using Row Factories

Row factories allow customizing how rows are returned from queries. This example shows how to access columns by name instead of position.

row_factory.py
import sqlite3

with sqlite3.connect('example.db') as conn:
    # Set row factory for named access
    conn.row_factory = sqlite3.Row
    
    with conn.cursor() as cursor:
        cursor.execute("SELECT name, price FROM products WHERE id = ?", (1,))
        product = cursor.fetchone()
        
        # Access columns by name
        print(f"{product['name']} costs ${product['price']:.2f}")
        
        # Can still access by index
        print(f"Same product: {product[0]} costs ${product[1]:.2f}")

The sqlite3.Row factory provides both index-based and name-based access to columns. This makes code more readable and resilient to schema changes.

Row objects also support other useful features like dictionary-style key access, keys method, and proper string representation for debugging.

Error Handling

Proper error handling is crucial for robust database applications. This example demonstrates handling common SQLite errors during execution.

error_handling.py
import sqlite3

try:
    with sqlite3.connect('example.db') as conn:
        with conn.cursor() as cursor:
            # This will fail (duplicate primary key)
            cursor.execute("INSERT INTO products VALUES (1, 'Tablet', 299.99)")
            conn.commit()
except sqlite3.IntegrityError as e:
    print(f"Integrity error: {e}")
except sqlite3.DatabaseError as e:
    print(f"Database error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

The example shows how to handle specific SQLite errors like IntegrityError for constraint violations. More general errors can be caught with DatabaseError.

Always handle database exceptions appropriately - at minimum log them, and often implement retry logic or user notification for recoverable errors.

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.