thebench.bio #1 Ecosystem for the Bio-Tech Industry
All SQL questions

Question 17 of 20 Intermediate Practical

How would you pass values from a Python script into a SQL query safely?

Short answer

Pass values as query parameters and let the database driver send them separately from the SQL text, never by formatting them into the string. Identifiers such as table names cannot be parameters, so build those from an allow-list or the driver's identifier helper.

Why

Building SQL with an f-string or the % operator works until a value contains a quote. The psycopg documentation shows a name like O'Reilly breaking such a query, and explains that the same weakness lets anyone who controls the value change the query itself, which is SQL injection. In a lab setting the input may be a sample ID typed into a dashboard or read from a vendor file, so it is not trustworthy just because it is internal.

With psycopg the correct form is cur.execute("SELECT * FROM results WHERE sample_id = %s", (sample_id,)). The %s there is a placeholder, not Python formatting, and the second argument must be a sequence even for one value. The driver also converts Python types properly, so dates and decimals arrive as dates and decimals rather than strings. pandas read_sql accepts a params argument for the same reason.

Placeholders work for values only. A column or table name chosen at run time needs psycopg's sql.Identifier, or a fixed allow-list in code, which is the defense OWASP recommends when bind variables cannot be used. Parameterization also makes queries easier to log and test, because the SQL text stays constant.

A strong answer covers

  • Placeholders with values passed separately
  • String formatting enables SQL injection and breaks on quotes
  • Internal inputs are not automatically trusted
  • Identifiers need sql.Identifier or an allow-list
  • Driver handles type conversion

Common mistakes

  • Escaping quotes by hand instead of using parameters
  • Trying to pass a table name as a bound parameter
  • Passing a single value as a bare string instead of a one-element tuple