Ravindra BagaleCourses & study guides मराठी Track your progress

24. How to Load SQL Query Results into a pandas DataFrame

24.6 Common Mistakes: Huge Tables and String-Format SQL

Why. Two classroom disasters: (1) loading an entire production table into RAM; (2) building SQL with Python f-strings from user or cell input. The second is how SQL injection starts — even in a “harmless” notebook. Fix both early.

What.

  • read_sql_table / SELECT * without filters on a huge table → slow laptop, crashed kernel, angry DBA.
  • String format / f-string SQL like f"... WHERE city = '{city}'" → unsafe if city ever comes from outside your own typed value.
  • Parameterized queries pass values separately; the driver escapes them. In pandas: use params= with ? (qmark) or %(name)s / :name depending on the backend.

How.

Safer query habit

  1. Never load a full big table “just to see”.
  2. Always add a date window or LIMIT while exploring.
  3. Do not assemble SQL with + or f-strings for values.
  4. Put filters in params= (or bind parameters your driver documents).
  5. Keep column/table names under your control (whitelist) — parameters are for values, not for inventing column names from user text.
  6. Log df.shape after every pull; if it is millions of rows by accident, stop and fix the SQL.

Worked example A — Fragile vs parameterized (SQLite ?).

import sqlite3
import pandas as pd

con = sqlite3.connect("data/blinkit_lab.db")
city = "Pune"  # in real apps this might come from a form — still use params

# Fragile habit — do not use for external input
bad = f"SELECT * FROM orders WHERE city = '{city}'"

# Safer — values travel separately from SQL text
good = "SELECT order_id, city, amount_inr FROM orders WHERE city = ?"
df = pd.read_sql_query(good, con, params=("Pune",))
print(df)

con.close()

Worked example B — Named style sketch (SQLAlchemy / many servers).

import pandas as pd
from sqlalchemy import create_engine, text

engine = create_engine("sqlite:///data/blinkit_lab.db")
sql = text(
    """
    SELECT city, status, amount_inr
    FROM orders
    WHERE status = :status AND amount_inr >= :min_amt
    """
)
df = pd.read_sql_query(
    sql,
    engine,
    params={"status": "Delivered", "min_amt": 100},
)
print(df)
engine.dispose()

Worked example C — Huge-table checklist (fictional Monday).

Shraddha almost ran pd.read_sql_table("orders", engine) on production. Checklist stopped her:

  1. Row estimate from DBA / COUNT(*) on a filtered subset.
  2. Date filter for “today IST”.
  3. Column list without receipt JSON blobs.
  4. LIMIT 1000 smoke test first.
  5. Then remove LIMIT only if the filtered shape is sane.

Ravindra Bagale's Tip

f-string SQL = shortcut temptation. params= = professional habit. Mothe table = pehle LIMIT.