Read MySQL records into Pandas with SQLAlchemy. Practise bound filters, LIKE, indexes, columns, chunks and aggregate reports with embedded student data.

import pandas as pd
from io import StringIO
from sqlalchemy import create_engine, text, inspect
from sqlalchemy.engine import URL
student_csv = 'id,name,class,mark,gender\n1,John Deo,Four,75,female\n2,Max Ruin,Three,85,male\n3,Arnold,Three,55,male\n4,Krish Star,Four,60,female\n5,John Mike,Four,60,female\n6,Alex John,Four,55,male\n7,My John Rob,Fifth,78,male\n8,Asruid,Five,85,male\n9,Tes Qry,Six,78,male\n10,Big John,Four,55,female\n11,Ronald,Six,89,female\n12,Recky,Six,94,female\n13,Kty,Seven,88,female\n14,Bigy,Seven,88,female\n15,Tade Row,Four,88,male\n16,Gimmy,Four,88,male\n17,Tumyu,Six,54,male\n18,Honny,Five,75,male\n19,Tinny,Nine,18,male\n20,Jackly,Nine,65,female\n21,Babby John,Four,69,female\n22,Reggid,Seven,55,female\n23,Herod,Eight,79,male\n24,Tiddy Now,Seven,78,male\n25,Giff Tow,Seven,88,male\n26,Crelea,Seven,79,male\n27,Big Nose,Three,81,female\n28,Rojj Base,Seven,86,female\n29,Tess Played,Seven,55,male\n30,Reppy Red,Six,79,female\n31,Marry Toeey,Four,88,male\n32,Binn Rott,Seven,90,female\n33,Kenn Rein,Six,96,female\n34,Gain Toe,Seven,69,male\n35,Rows Noump,Six,88,female\n'
students = pd.read_csv(StringIO(student_csv))
def student_engine():
engine = create_engine("sqlite:///:memory:")
with engine.begin() as connection:
students.to_sql("student", connection, index=False)
return engine
print(students.head())
print("Student rows:", len(students))
assert len(students) == 35Expected output
id name class mark gender
0 1 John Deo Four 75 female
1 2 Max Ruin Three 85 male
2 3 Arnold Three 55 male
3 4 Krish Star Four 60 female
4 5 John Mike Four 60 female
Student rows: 35url = URL.create("mysql+pymysql", username="practice_user", password="practice_password",
host="localhost", database="practice_db", query={"charset": "utf8mb4"})
print(url.render_as_string(hide_password=True))
assert url.drivername == "mysql+pymysql"Expected output
mysql+pymysql://practice_user:***@localhost/practice_db?charset=utf8mb4import os
run_mysql = False
if run_mysql:
private_url = URL.create("mysql+pymysql", username=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"], host=os.environ["MYSQL_HOST"],
database=os.environ["MYSQL_DATABASE"], query={"charset": "utf8mb4"})
engine = create_engine(private_url)
try:
with engine.connect() as connection:
result = pd.read_sql(text("SELECT * FROM student WHERE class=:class ORDER BY id"),
connection, params={"class": "Four"})
print(result)
finally:
engine.dispose()
else:
print("Live MySQL read disabled; practice examples use SQLite.")Expected output
Live MySQL read disabled; practice examples use SQLite.engine = student_engine()
try:
with engine.connect() as connection:
result = pd.read_sql(text("SELECT * FROM student WHERE class=:class ORDER BY id"),
connection, params={"class": "Four"})
print(result)
print(result[["name", "class"]].head())
assert result["id"].tolist() == [1, 4, 5, 6, 10, 15, 16, 21, 31]
finally:
engine.dispose()Expected output
id name class mark gender
0 1 John Deo Four 75 female
1 4 Krish Star Four 60 female
2 5 John Mike Four 60 female
3 6 Alex John Four 55 male
4 10 Big John Four 55 female
5 15 Tade Row Four 88 male
6 16 Gimmy Four 88 male
7 21 Babby John Four 69 female
8 31 Marry Toeey Four 88 male
name class
0 John Deo Four
1 Krish Star Four
2 John Mike Four
3 Alex John Four
4 Big John Fourengine = student_engine()
try:
with engine.connect() as connection:
query = text("SELECT id, name FROM student WHERE class=:class ORDER BY id")
result = pd.read_sql(query, connection, params={"class": "Five"})
print(result)
assert result["id"].tolist() == [8, 18]
unmatched = pd.read_sql(query, connection, params={"class": "Five' OR 1=1 --"})
assert unmatched.empty
print("Unmatched literal input rows:", len(unmatched))
finally:
engine.dispose()Expected output
id name
0 8 Asruid
1 18 Honny
Unmatched literal input rows: 0engine = student_engine()
try:
with engine.connect() as connection:
query = text("SELECT id, name, class FROM student WHERE class=:class AND id IN (:first,:second) ORDER BY id")
result = pd.read_sql(query, connection, params={"class": "Four", "first": 6, "second": 10})
print(result)
assert result["id"].tolist() == [6, 10]
finally:
engine.dispose()Expected output
id name class
0 6 Alex John Four
1 10 Big John Fourengine = student_engine()
try:
with engine.connect() as connection:
result = pd.read_sql(text("SELECT id, name FROM student WHERE name LIKE :pattern ORDER BY id"),
connection, params={"pattern": "%John%"})
print(result)
assert result["id"].tolist() == [1, 5, 6, 7, 10, 21]
finally:
engine.dispose()Expected output
id name
0 1 John Deo
1 5 John Mike
2 6 Alex John
3 7 My John Rob
4 10 Big John
5 21 Babby Johnengine = student_engine()
try:
with engine.connect() as connection:
result = pd.read_sql(text("SELECT * FROM student WHERE class=:class ORDER BY id"),
connection, params={"class": "Five"}, index_col="id")
print(result)
assert result.index.tolist() == [8, 18]
finally:
engine.dispose()Expected output
name class mark gender
id
8 Asruid Five 85 male
18 Honny Five 75 maleengine = student_engine()
try:
with engine.connect() as connection:
selected = pd.read_sql("student", connection, columns=["id", "name"])
print(selected.sort_values("id").head())
all_fields = pd.read_sql("student", connection)
print("Fields:", all_fields.columns.tolist())
for field in all_fields.columns:
print(field)
assert selected.columns.tolist() == ["id", "name"] and len(selected) == 35
finally:
engine.dispose()Expected output
id name
0 1 John Deo
1 2 Max Ruin
2 3 Arnold
3 4 Krish Star
4 5 John Mike
Fields: ['id', 'name', 'class', 'mark', 'gender']
id
name
class
mark
genderengine = student_engine()
try:
with engine.connect() as connection:
iterator = pd.read_sql(text("SELECT * FROM student ORDER BY id"), connection, chunksize=3)
first = next(iterator)
second = next(iterator)
print(first)
print("--End of first set of records--")
print(second)
total = len(first) + len(second) + sum(len(chunk) for chunk in iterator)
assert first["id"].tolist() == [1, 2, 3]
assert second["id"].tolist() == [4, 5, 6]
assert total == 35
finally:
engine.dispose()Expected output
id name class mark gender
0 1 John Deo Four 75 female
1 2 Max Ruin Three 85 male
2 3 Arnold Three 55 male
--End of first set of records--
id name class mark gender
0 4 Krish Star Four 60 female
1 5 John Mike Four 60 female
2 6 Alex John Four 55 maleengine = student_engine()
try:
with engine.connect() as connection:
overall = pd.read_sql(text("SELECT MAX(mark) AS highest, AVG(mark) AS average FROM student"), connection)
grouped = pd.read_sql(text("SELECT class, COUNT(*) AS students, MAX(mark) AS highest, AVG(mark) AS average, MIN(mark) AS lowest FROM student GROUP BY class ORDER BY class"), connection)
print(overall.round(2))
print(grouped.round(2))
assert overall.loc[0, "highest"] == students["mark"].max()
assert abs(overall.loc[0, "average"] - students["mark"].mean()) < 1e-10
assert grouped["students"].sum() == 35
finally:
engine.dispose()Expected output
highest average
0 96 74.66
class students highest average lowest
0 Eight 1 79 79.00 79
1 Fifth 1 78 78.00 78
2 Five 2 85 80.00 75
3 Four 9 88 70.89 55
4 Nine 2 65 41.50 18
5 Seven 10 90 77.60 55
6 Six 7 96 82.57 54
7 Three 3 85 73.67 55engine = student_engine()
try:
with engine.connect() as connection:
query = text("SELECT id, name, mark FROM student WHERE class=:class ORDER BY mark DESC, id ASC LIMIT 1 OFFSET :offset")
highest = pd.read_sql(query, connection, params={"class": "Five", "offset": 0})
second = pd.read_sql(query, connection, params={"class": "Five", "offset": 1})
print(highest)
print(second)
assert highest.loc[0, "id"] == 8 and second.loc[0, "id"] == 18
finally:
engine.dispose()Expected output
id name mark
0 8 Asruid 85
id name mark
0 18 Honny 75engine = create_engine("sqlite:///:memory:")
try:
with engine.begin() as connection:
connection.execute(text("CREATE TABLE student5 (id INTEGER PRIMARY KEY, name TEXT, class TEXT, mark INTEGER, gender TEXT)"))
connection.execute(text("INSERT INTO student5 VALUES (:id,:name,:class,:mark,:gender)"),
{"id": 1, "name": "John Deo", "class": "Four", "mark": 75, "gender": "female"})
result = pd.read_sql(text("SELECT * FROM student5 ORDER BY id"), connection)
print(result)
assert inspect(engine).get_pk_constraint("student5")["constrained_columns"] == ["id"]
finally:
engine.dispose()Expected output
id name class mark gender
0 1 John Deo Four 75 femaleengine = create_engine("sqlite:///:memory:")
try:
with engine.begin() as connection:
connection.execute(text("CREATE TABLE orders (id TEXT, date TEXT, units INTEGER)"))
connection.execute(text("INSERT INTO orders VALUES (:id,:date,:units)"),
[{"id": "001", "date": "2026-01-02", "units": 2},
{"id": "002", "date": "2026-01-03", "units": None}])
with engine.connect() as connection:
result = pd.read_sql(text("SELECT * FROM orders ORDER BY id"), connection,
parse_dates={"date": {"format": "%Y-%m-%d"}}, dtype={"id": "string", "units": "Int64"})
print(result)
assert result["id"].tolist() == ["001", "002"]
assert result["units"].isna().sum() == 1
finally:
engine.dispose()Expected output
id date units
0 001 2026-01-02 2
1 002 2026-01-03 <NA>| Parameter | Use |
|---|---|
sql / con | Query or table name, and a supported connection. |
params | Bind query values; placeholder syntax depends on the interface/driver. |
index_col | Columns to use as DataFrame row labels. |
coerce_float | May convert Decimal-like objects to float; assess precision requirements. |
parse_dates | Date parsing options; plural name. |
columns | Selected fields in table-name mode, not query filtering. |
chunksize | Return a DataFrame iterator; retain its connection while reading. |
dtype / dtype_backend | Query dtype mapping or supported nullable backend choices. |
Bind data values and keep identifiers controlled; read_sql() does not sanitize an entire supplied SQL statement. For large reports push field selection and aggregation into SQL. Close connections and dispose engines. Check the actual sample labels and column names rather than assuming sex and gender are interchangeable. Keep DDL separate from SELECT readers and do not assume MySQL DDL transaction behavior matches SQLite. For graphing, see charts from database data.
engine = student_engine()
try:
with engine.connect() as connection:
answer = pd.read_sql(text("SELECT id, name, mark FROM student WHERE class=:class AND mark>=:mark ORDER BY id"),
connection, params={"class": "Four", "mark": 80})
print(answer)
assert answer["id"].tolist() == [15, 16, 31]
finally:
engine.dispose()Expected output
id name mark
0 15 Tade Row 88
1 16 Gimmy 88
2 31 Marry Toeey 88Open in Google Colab View on GitHub
Run examples in order, inspect database records and try the exercise. Save a copy to keep edits.
Reference: Pandas MySQL read_sql() documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.