Q&A
What is the to_sql() function in pandas Python?
Overview
to_sql is the mirror of pd.read_sql - where read_sql pulls a table into a DataFrame, to_sql pushes a DataFrame into a database table. The most important decision is what to do when the table already exists, controlled by if_exists.
graph LR
A[pandas DataFrame] --> B["to_sql(name, engine)"]
B --> C{if_exists?}
C -->|fail| D["Error if table exists"]
C -->|replace| E["Drop & recreate table"]
C -->|append| F["Insert rows into existing table"]
style B fill:#1e1b4b,color:#fff
Basic Usage
import pandas as pd
from sqlalchemy import create_engine
df = pd.DataFrame({
"name": ["Ada", "Linus", "Grace"],
"score": [95, 88, 91],
"date": pd.to_datetime(["2026-01-01", "2026-01-02", "2026-01-03"]),
})
engine = create_engine("postgresql://user:pass@localhost:5432/mydb")
df.to_sql("results", con=engine, if_exists="replace", index=False)
"results"- table name to create or write to.con=engine- a SQLAlchemy engine or connection.if_exists="replace"- drop the table if it exists and recreate.index=False- don't write the DataFrame index as a database column.
The if_exists Parameter
| Value | Behavior |
|---|---|
"fail" (default) |
Raise ValueError if the table already exists |
"replace" |
Drop the table and recreate it with the new data |
"append" |
Add rows to the existing table (schema must match) |
# Append new rows without losing existing data:
df.to_sql("results", con=engine, if_exists="append", index=False)
Controlling Column Types with dtype
By default pandas infers SQL types. You can override them explicitly (useful for DECIMAL precision, VARCHAR length, etc.):
from sqlalchemy import types
df.to_sql("products", con=engine, if_exists="replace", index=False,
dtype={
"name": types.VARCHAR(100),
"price": types.NUMERIC(10, 2),
"active": types.Boolean(),
})
Large DataFrames: chunksize
For big DataFrames, insert in batches to avoid memory issues and timeouts:
df.to_sql("big_table", con=engine, if_exists="append", index=False,
chunksize=1000) # inserts 1000 rows per batch
Using SQLite (no SQLAlchemy needed for sqlite3)
to_sql also accepts a sqlite3.Connection directly:
import sqlite3
conn = sqlite3.connect("local.db")
df.to_sql("results", con=conn, if_exists="replace", index=False)
conn.close()
Common Workflow: Read, Transform, Write Back
# Pull data from DB
df = pd.read_sql("SELECT * FROM raw_data", engine)
# Transform
df["score_normalized"] = (df["score"] - df["score"].mean()) / df["score"].std()
# Write result to a new table
df.to_sql("processed_data", con=engine, if_exists="replace", index=False)
What to_sql Doesn't Do
- It doesn't create primary keys, indexes, or constraints automatically (beyond the basic table).
- For production schemas with constraints, create the table first with proper DDL, then use
if_exists="append". replacedrops and recreates - it loses any existing indexes, constraints, and permissions on that table.
Why This Matters in Interviews
Data-engineering/analytics interviews use to_sql to check the pandas–SQL bridge. Key points: requires a SQLAlchemy engine (not just a connection string); the if_exists choices (fail/replace/append) and their consequences; index=False to avoid a spurious extra column; chunksize for large data; and dtype for precise type control. Knowing that replace drops constraints and that you should pre-create the table for production schemas signals practical maturity.
Common Pitfalls
- Passing a raw connection string instead of a SQLAlchemy engine.
- Forgetting
index=False- the default writes the DataFrame index as a column (often unwanted). - Using
if_exists="replace"in production and losing indexes/constraints on the table. - Not using
chunksizeon large DataFrames - can exhaust memory or time out. - Type mismatches on
appendwhen the DataFrame and the existing table schema differ.
References
- pandas
to_sql: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html - SQLAlchemy engine creation: https://docs.sqlalchemy.org/en/14/core/engines.html