Learn how to safely filter database results in Python using the SQL WHERE clause with parameterized queries to prevent injection attacks.
What it is
The WHERE clause in SQL restricts which rows are returned from a query. In Python, when interacting with databases via libraries like sqlite3, psycopg2, or mysql-connector, you never insert user input directly into the SQL string. Instead, you use placeholders (such as %s or ?) and pass values separately to the execute() method. This technique is called parameterization or prepared statements.
Related terms: SQL Injection, Prepared Statements, Cursor, Tuple.
Why it matters
- Security: Prevents SQL injection attacks where malicious users manipulate your query structure.
- Data Integrity: Ensures that special characters (like quotes) in data do not break the SQL syntax.
- Performance: Many database drivers cache prepared statements, speeding up repeated queries with different parameters.
- Readability: Separates logic (SQL) from data (parameters), making code easier to maintain.
Syntax or steps
- Write the SQL query with a placeholder for each variable value (e.g.,
WHERE id = %s). - Create a tuple containing the values to substitute into the placeholders.
- Pass both the query string and the tuple to the cursor's
execute()method. - Fetch the results using
fetchone()orfetchall().
Example
import sqlite3
# Connect to an in-memory database for demonstration
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
# Create table and insert sample data
cur.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
cur.executemany("INSERT INTO users VALUES (?, ?)", [
(1, "Alice"),
(2, "Bob"),
(3, "Charlie")
])
conn.commit()
# Filter results using WHERE with parameterization
user_id = 2
query = "SELECT * FROM users WHERE id = ?"
cur.execute(query, (user_id,))
# Fetch and print the result
result = cur.fetchone()
print(result) # Output: (2, 'Bob')
conn.close()
Explanation: The query uses ? as a placeholder because this example uses sqlite3. The value user_id is passed inside a tuple (user_id,). Note the trailing comma; without it, Python treats it as just parentheses around an integer, not a tuple. The database driver handles escaping and type conversion automatically.
Common mistakes
- String Formatting: Using f-strings or
%formatting to inject variables directly into SQL (e.g.,f"WHERE id = {user_id}"). This causes security vulnerabilities. - Missing Comma in Tuple: Passing
(user_id)instead of(user_id,)for single-value parameters. This raises a TypeError because it is not recognized as a sequence. - Wrong Placeholder Style: Mixing styles. Use
%sfor MySQL/PostgreSQL drivers and?for SQLite. Check your specific library documentation. - Forgetting to Commit: If modifying data (UPDATE/DELETE) within a transaction context, failing to call
conn.commit()may discard changes.
When to use it
| Approach | Use When | Risk |
|---|---|---|
| Parameterized Query | Always, especially with user input. | None (if implemented correctly). |
| String Concatenation | Never for dynamic values. Only for static column names if strictly controlled. | High risk of SQL Injection. |
Practice
Guided Exercise: Modify the example above to find all users whose name starts with "A". Use the LIKE operator and a wildcard pattern.
Challenge: Write a function get_user_by_name(name) that returns the ID of a user given their exact name. Handle the case where no user is found by returning None.
Hint for Challenge: Use cur.execute("SELECT id FROM users WHERE name = ?", (name,)) and check if cur.fetchone() is None.
Quick check
Q: Why must you pass a tuple even if there is only one parameter?
A: The execute() method expects a sequence of arguments. A single value in parentheses is just grouping, not a tuple. You need the trailing comma (value,) to create a valid tuple.
Summary
Using the WHERE clause with parameterized queries is the standard, secure way to filter database results in Python. Always separate your SQL logic from your data by using placeholders and passing values as tuples to avoid critical security flaws.