0Pricing
Pandas & NumPy Academy · Lesson

Connecting to a Database with SQLAlchemy

Create a SQLAlchemy engine for SQLite and PostgreSQL, and pass it to pd.read_sql to load a table into a DataFrame.

Connecting to a Database with SQLAlchemy is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Connect Pandas to Databases?

Most production data lives in relational databases — PostgreSQL, MySQL, SQLite, or SQL Server — not CSV files. Connecting Pandas directly to a database lets you query data into a DataFrame without exporting to CSV first, push cleaned DataFrames back into tables, and combine Python's analytical power with the database's indexing and joining capabilities. The bridge between Pandas and databases is SQLAlchemy, Python's standard database abstraction library.

Installing SQLAlchemy

SQLAlchemy is a Python SQL toolkit and ORM. For Pandas integration you only need the Core layer — not the ORM. Install with pip install sqlalchemy. You also need the specific database driver: psycopg2 for PostgreSQL, pymysql for MySQL, or sqlite3 (built into Python) for SQLite. SQLAlchemy acts as an abstraction layer: the same Pandas code works with any supported database by changing only the connection string.

# Install dependencies
# pip install sqlalchemy psycopg2-binary  # for PostgreSQL
# pip install sqlalchemy pymysql           # for MySQL
# sqlite3 is built into Python

import sqlalchemy as sa
import pandas as pd

print('SQLAlchemy version:', sa.__version__)

Creating a Connection Engine

The first step is creating a SQLAlchemy engine using a connection URL that encodes the database type, credentials, host, port, and database name. The engine is a factory for database connections — it does not open a connection until you actually need one. Pass the engine to Pandas' pd.read_sql() and df.to_sql() functions. Never hardcode credentials; read them from environment variables or a secrets manager.

import sqlalchemy as sa
import os

# SQLite (file-based, no server needed)
sqlite_engine = sa.create_engine('sqlite:///mydata.db')

# PostgreSQL
# pg_url = 'postgresql://user:pass@localhost:5432/mydb'
# pg_engine = sa.create_engine(pg_url)

# From environment variable (safer)
# pg_engine = sa.create_engine(os.environ['DATABASE_URL'])

print(sqlite_engine)
print(type(sqlite_engine))

Reading a Table with pd.read_sql_table()

pd.read_sql_table('table_name', con=engine) reads an entire database table into a DataFrame. It automatically infers column dtypes from the database schema — integers stay as integers, timestamps as datetime, etc. This is more precise than CSV inference. You can also limit columns with the columns argument and filter rows with schema for non-default database schemas. Be cautious with very large tables: this loads everything into RAM.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///sales.db')

# Read a full table
df = pd.read_sql_table('orders', con=engine)
print(df.shape)
print(df.dtypes)
print(df.head())

Running Queries with pd.read_sql_query()

pd.read_sql_query('SELECT ...', con=engine) executes an arbitrary SQL SELECT statement and returns the results as a DataFrame. This is the most flexible approach: you can filter, join, and aggregate in SQL before loading into Pandas, loading only the rows and columns you need. Write the query as a plain Python string. Never concatenate user input into queries — use parameterised queries to prevent SQL injection.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///sales.db')

query = '''
    SELECT customer_id, SUM(amount) AS total_spent,
           COUNT(*) AS num_orders
    FROM orders
    WHERE status = 'completed'
    GROUP BY customer_id
    ORDER BY total_spent DESC
    LIMIT 100
'''

top_customers = pd.read_sql_query(query, con=engine)
print(top_customers.head())

Parameterised Queries for Safety

Never build SQL queries by string concatenation with user-provided values — this opens SQL injection vulnerabilities. Instead, use parameterised queries: pass parameters as a dictionary with named placeholders. SQLAlchemy handles escaping. The syntax for placeholders is :name in SQLAlchemy text queries or %(name)s for psycopg2-style queries. Always use parameterisation even for internal scripts to build good habits.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///sales.db')

# Safe: parameterised query
params = {'status': 'completed', 'min_amount': 500.0}
query = sa.text(
    'SELECT * FROM orders WHERE status = :status AND amount > :min_amount'
)

with engine.connect() as conn:
    df = pd.read_sql_query(query, con=conn, params=params)
print(f'Loaded {len(df)} rows')

Handling Large Query Results in Chunks

For large query results, use chunksize in pd.read_sql_query() to receive an iterator of DataFrames rather than loading everything at once. This mirrors the behaviour of pd.read_csv(chunksize=...) but fetches rows from the database in batches. Combine this with a running accumulator pattern to aggregate results from million-row queries without exhausting RAM.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('postgresql://user:pass@host/db')

total = 0.0
count = 0

for chunk in pd.read_sql_query(
    'SELECT amount FROM orders',
    con=engine,
    chunksize=50000
):
    total += chunk['amount'].sum()
    count += len(chunk)

print(f'Mean amount: {total/count:.2f}')

Connection Context Managers

Always open database connections inside a context manager (with engine.connect() as conn:) to ensure the connection is properly closed even if an exception occurs. Forgetting to close connections leads to connection pool exhaustion in production, causing new queries to hang waiting for a free slot. SQLAlchemy's connection pool manages a fixed number of connections and recycles them automatically when context managers are used.

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine('sqlite:///sales.db')

# Using context manager — connection always closed properly
with engine.connect() as conn:
    df = pd.read_sql_query(
        'SELECT * FROM products WHERE category = "Electronics"',
        con=conn
    )
    print(f'Products loaded: {len(df)}')
# Connection is automatically returned to the pool here

Inspecting Database Schema

Before writing queries, you need to know what tables and columns exist. SQLAlchemy's Inspector lets you reflect the database schema without writing raw SQL. inspector.get_table_names() lists all tables; inspector.get_columns('table') returns column names and types. This is useful when working with unfamiliar databases and is cleaner than running PRAGMA table_info() or \d tablename manually.

import sqlalchemy as sa

engine = sa.create_engine('sqlite:///sales.db')
inspector = sa.inspect(engine)

# List all tables
tables = inspector.get_table_names()
print('Tables:', tables)

# Get columns for the 'orders' table
for col in inspector.get_columns('orders'):
    print(f'  {col["name"]}: {col["type"]}')

Closing Engines and Best Practices

In long-running scripts or web applications, call engine.dispose() when you are done to close all connections in the pool. For short scripts, Python's garbage collector handles cleanup. Best practices for database connections in data pipelines: create the engine once at the top of the script and reuse it, use connection pooling defaults (pool_size=5), enable pool_pre_ping=True to automatically reconnect if the database server restarts between queries.

import sqlalchemy as sa

# Production-grade engine creation
engine = sa.create_engine(
    'postgresql://user:pass@host:5432/mydb',
    pool_size=5,          # max 5 persistent connections
    max_overflow=10,      # allow 10 temporary extra connections
    pool_pre_ping=True,   # verify connection before use
    connect_args={'connect_timeout': 10}
)

# ... run all your queries ...

# At the end of the application/script
engine.dispose()
print('Engine disposed')

Comparing read_sql vs read_csv Speed

For data already in a database with proper indexes, pd.read_sql_query with a filtered query is often faster than exporting to CSV and reading that. The database server applies filters before sending data, reducing network transfer and parse overhead. For very wide tables, the database can also project only needed columns. However, reading from a remote database over a slow network may be slower than reading a local Parquet file — always profile both options for your specific setup.

import pandas as pd
import sqlalchemy as sa
import time

engine = sa.create_engine('sqlite:///data.db')

# Database read with server-side filter
start = time.time()
df_sql = pd.read_sql_query(
    'SELECT * FROM transactions WHERE amount > 100 AND year = 2024',
    con=engine
)
print(f'SQL read: {time.time()-start:.3f}s, {len(df_sql):,} rows')

Quick Check

Test your understanding of Data Analysis concepts from this lesson.

Lesson Recap

In this lesson you learned: sa.create_engine() creates a reusable connection factory from a URL string, pd.read_sql_query() executes arbitrary SQL and returns a DataFrame, and parameterised queries with sa.text() and params prevent SQL injection vulnerabilities. Next up we look at running more complex SQL queries from Pandas and combining SQL with Python logic.

Frequently asked questions

Is the “Connecting to a Database with SQLAlchemy” lesson free?

Yes — the full text of “Connecting to a Database with SQLAlchemy” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.

What will I learn in “Connecting to a Database with SQLAlchemy”?

Create a SQLAlchemy engine for SQLite and PostgreSQL, and pass it to pd.read_sql to load a table into a DataFrame. You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Pandas & NumPy Academy?

No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Connecting to a Database with SQLAlchemy” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Pandas & NumPy Academy lesson?

Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Connecting to a Database with SQLAlchemy
  2. Running SQL Queries from Pandas
  3. Writing DataFrames to Database Tables
  4. Pandas vs. SQL: Choosing the Right Tool
← Back to Pandas & NumPy Academy