Execute Raw SQL
How to execute raw SQL queries safely with parameterized statements.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Overview
Even with ORMs, raw SQL is sometimes necessary for complex queries, migrations, or performance optimization. However, executing raw SQL without safeguards is a primary cause of SQL injection vulnerabilities. This recipe demonstrates how to execute raw SQL safely using parameterized queries in Python, JavaScript, and Java.
When to Use
Use this resource when:
- Writing complex analytics queries that ORMs cannot express efficiently
- Executing database migrations or administrative commands
- Optimizing performance with database-specific SQL features
Solution
Python
import psycopg2
conn = psycopg2.connect(host="localhost", database="mydb", user="user", password="pass")
cursor = conn.cursor()
# Safe parameterized query
cursor.execute("SELECT * FROM users WHERE email = %s AND active = %s", (email, True))
rows = cursor.fetchall()
# Safe insert with RETURNING
cursor.execute(
"INSERT INTO users (email, role) VALUES (%s, %s) RETURNING id",
(email, role)
)
user_id = cursor.fetchone()[0]
conn.commit()
cursor.close()
conn.close()
JavaScript
const { Pool } = require('pg');
const pool = new Pool({ /* config */ });
// Safe parameterized query
async function findUser(email) {
const result = await pool.query(
'SELECT * FROM users WHERE email = $1 AND active = $2',
[email, true]
);
return result.rows;
}
// Safe insert with RETURNING
async function createUser(email, role) {
const result = await pool.query(
'INSERT INTO users (email, role) VALUES ($1, $2) RETURNING id',
[email, role]
);
return result.rows[0].id;
}
Java
import java.sql.*;
public class RawSQL {
public void findUser(Connection conn, String email) throws SQLException {
String sql = "SELECT * FROM users WHERE email = ? AND active = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, email);
stmt.setBoolean(2, true);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
}
public int createUser(Connection conn, String email, String role) throws SQLException {
String sql = "INSERT INTO users (email, role) VALUES (?, ?) RETURNING id";
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, email);
stmt.setString(2, role);
stmt.executeUpdate();
try (ResultSet keys = stmt.getGeneratedKeys()) {
keys.next();
return keys.getInt(1);
}
}
}
}
Explanation
Parameterized queries (prepared statements) separate SQL logic from data. The database compiles the SQL template once and binds values at execution time, making injection impossible. In Python, %s is a placeholder, not a format string. In JavaScript, $1, $2 are positional parameters. In Java, ? is the JDBC placeholder. None of these concatenate user input into the SQL string.
Variants
| Technology | Approach | Notes |
|---|---|---|
| Python | SQLAlchemy text() | Raw SQL within ORM with bound parameters |
| JavaScript | knex.raw() | Query builder with raw SQL and bindings |
| Java | Jdbi | Fluent API over JDBC with parameter binding |
Best Practices
- Never concatenate user input into SQL strings; always use parameterized queries
- Use
RETURNING(PostgreSQL) orgetGeneratedKeys()(JDBC) instead of separateSELECT MAX(id) - Wrap multiple statements in transactions with proper rollback on error
- Validate and whitelist table/column names when they must be dynamic
- Log SQL execution times to detect slow queries and N+1 patterns
Common Mistakes
- Using Python f-strings, JS template literals, or Java
+concatenation for SQL - Assuming ORMs are always safe;
.query("..." + input)is still vulnerable - Sanitizing input with regex instead of using parameterized queries
- Forgetting to commit transactions, leaving data in an inconsistent state
- Using
Statementinstead ofPreparedStatementin Java
Frequently Asked Questions
Is cursor.execute(f"SELECT * FROM {table}") safe?
No. Table and column names cannot be parameterized in most drivers. If dynamic table names are required, whitelist them against a known set of valid names.
Can I use parameterized queries for IN clauses?
Most drivers do not support IN (%s) with a list. Use driver-specific extensions: ANY($1) in PostgreSQL, generate placeholders dynamically in Python/Java, or use find_in_set in MySQL.
Should I avoid raw SQL entirely and only use ORMs?
Not necessarily. ORMs excel at CRUD but struggle with complex aggregations, window functions, and database-specific optimizations. Use raw SQL for these cases, but always parameterize inputs.
Related Resources
Connect to MySQL
How to connect to MySQL databases in Python, JavaScript, and Java.
RecipeConnect to PostgreSQL
How to connect to PostgreSQL databases in Python, JavaScript, and Java.
RecipeConnect to Redis
How to connect to Redis and perform basic operations in Python, JavaScript, and Java.
RecipeEscape HTML Entities
How to escape HTML entities to prevent XSS attacks in Python, Java, and JavaScript.
RecipeSanitize User Input
How to sanitize and validate user input in Python, Java, and JavaScript to prevent injection attacks.