Pandas read_sql(): SQLite Queries, Parameters and Chunked Imports

Use read_sql() to load database query results into a DataFrame. Select useful records, bind values, and check types before analysis.

SQLite table to Pandas DataFrame using read_sql()
read_sql(): Data from SQLite database table to Pandas DataFrame #B06

Create the sample student database 🔝

The notebook embeds the original 35-row student sample. It creates its own SQLite table, so no upload or existing database is required. Each database example opens and closes a separate connection. See SQLite and to_sql().
import pandas as pd
import sqlite3
import tempfile
from pathlib import Path
from io import StringIO
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_connection():
    connection = sqlite3.connect(":memory:")
    students.to_sql("student", connection, index=False, if_exists="replace")
    return connection
print(students.head())
print("Student rows:", len(students))
assert len(students) == 35

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
3   4  Krish Star   Four    60  female
4   5   John Mike   Four    60  female
Student rows: 35

Use an uploaded Excel file in Colab 🔝

To follow the original video, upload student.xlsx through the Files panel of Colab. Session files can disappear when the runtime ends; keep your source elsewhere. These original screenshots illustrate the process. Read it with pd.read_excel("student.xlsx", index_col="id"), then use reset_index() before the index=False database write below. Reading Excel requires a suitable engine such as openpyxl. The embedded CSV provides the same student records for portable practice. See read_excel().
Upload a sample file through the Colab Files panel
Files in Colab runtime storage
Read student Excel data into a DataFrame in Colab
Create a SQLite database in Colab

List tables and inspect database records 🔝

List tables through sqlite_master, then check representative records. Closing a sqlite3 connection needs an explicit close: its context manager alone manages transactions rather than closure.
connection = student_connection()
try:
    tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name", connection)
    print(tables)
    records = connection.execute("SELECT id, name, mark FROM student ORDER BY id LIMIT 3").fetchall()
    print(records)
    assert tables["name"].tolist() == ["student"]
finally:
    connection.close()

Expected output

      name
0  student
[(1, 'John Deo', 75), (2, 'Max Ruin', 85), (3, 'Arnold', 55)]

Read all student rows with an ID index 🔝

read_sql() requires SQL and a connection. With a sqlite3 connection, pass a SELECT query rather than just a table name. index_col sets the DataFrame row labels. ORDER BY gives deterministic record order.
connection = student_connection()
try:
    result = pd.read_sql("SELECT * FROM student ORDER BY id", connection, index_col="id")
    print(result.head())
    print("Rows:", len(result))
    assert result.equals(students.set_index("id"))
finally:
    connection.close()

Expected output

          name  class  mark  gender
id                                 
1     John Deo   Four    75  female
2     Max Ruin  Three    85    male
3       Arnold  Three    55    male
4   Krish Star   Four    60  female
5    John Mike   Four    60  female
Rows: 35

Filter the Five class with a query 🔝

A fixed query may include a literal filter. The original class Five example is retained. For changing values or external input, use bound parameters in the next examples.
connection = student_connection()
try:
    query = "SELECT * FROM student WHERE class='Five' ORDER BY id"
    result = pd.read_sql(query, connection, index_col="id")
    print(result)
    assert result.index.tolist() == [8, 18]
finally:
    connection.close()

Expected output

      name class  mark gender
id                           
8   Asruid  Five    85   male
18   Honny  Five    75   male

Bind one parameter 🔝

SQLite accepts named placeholders with dictionary values. Pass data values separately rather than concatenating input into SQL. Placeholder styles depend on the database driver. Values cannot substitute table or column names; choose identifiers from a fixed allowed list.
connection = student_connection()
try:
    query = "SELECT * FROM student WHERE class=:my_class ORDER BY id"
    result = pd.read_sql(query, connection, index_col="id", params={"my_class": "Five"})
    print(result)
    assert result.index.tolist() == [8, 18]
    suspicious = "Five' OR 1=1 --"
    empty = pd.read_sql(query, connection, params={"my_class": suspicious})
    assert empty.empty
    print("Literal unmatched input rows:", len(empty))
finally:
    connection.close()

Expected output

      name class  mark gender
id                           
8   Asruid  Five    85   male
18   Honny  Five    75   male
Literal unmatched input rows: 0

Bind multiple parameters 🔝

Retain the original class Five and minimum mark 80 query. The result contains Asruid. SQLite also supports positional question-mark placeholders.
connection = student_connection()
try:
    query = "SELECT * FROM student WHERE class=:my_class AND mark>=:my_mark ORDER BY id"
    result = pd.read_sql(query, connection, index_col="id", params={"my_class": "Five", "my_mark": 80})
    print(result)
    positional = pd.read_sql("SELECT id FROM student WHERE class=? AND mark>=? ORDER BY id",
        connection, params=("Five", 80))
    assert result.index.tolist() == positional["id"].tolist() == [8]
finally:
    connection.close()

Expected output

      name class  mark gender
id                           
8   Asruid  Five    85   male

Read from a persistent SQLite file 🔝

A real file can be reopened after its writing connection closes. This temporary-file example is portable; replace the destination with your chosen database path when keeping a database.
with tempfile.TemporaryDirectory(dir=Path.cwd()) as folder:
    database = Path(folder) / "my_data.db"
    connection = sqlite3.connect(database)
    try:
        students.to_sql("student", connection, index=False, if_exists="replace")
    finally:
        connection.close()
    connection = sqlite3.connect(database)
    try:
        result = pd.read_sql("SELECT COUNT(*) AS row_count FROM student", connection)
        print(result)
        assert result.loc[0, "row_count"] == 35
    finally:
        connection.close()

Expected output

   row_count
0         35

Parse dates and preserve nullable values 🔝

This synthetic order table illustrates SQL NULL values, leading-zero text IDs and dates. NULL can become NaN, None, NaT or pd.NA depending on the column and backend. Nullable dtypes retain missing integer values. SQL must store an ID as text to preserve its zeros.
connection = sqlite3.connect(":memory:")
try:
    connection.execute("CREATE TABLE orders (id TEXT, ordered TEXT, units INTEGER)")
    connection.executemany("INSERT INTO orders VALUES (?, ?, ?)",
        [("001", "2026-01-02", 2), ("002", "2026-01-03", None)])
    result = pd.read_sql("SELECT * FROM orders ORDER BY id", connection,
        parse_dates={"ordered": {"format": "%Y-%m-%d"}},
        dtype={"id": "string", "units": "Int64"})
    print(result)
    print(result.dtypes)
    assert result["id"].tolist() == ["001", "002"]
    assert result["units"].isna().sum() == 1
    assert result.loc[0, "ordered"] == pd.Timestamp("2026-01-02")
finally:
    connection.close()

Expected output

    id    ordered  units
0  001 2026-01-02      2
1  002 2026-01-03   <NA>
id                 string
ordered    datetime64[us]
units               Int64
dtype: object

Process chunks while the connection stays open 🔝

chunksize returns an iterator of DataFrames. Keep the connection open until iteration finishes. Aggregate incrementally instead of concatenating every chunk when your goal is just totals. Driver buffering still affects memory use.
connection = student_connection()
try:
    chunks = pd.read_sql("SELECT id, mark FROM student ORDER BY id", connection, chunksize=10)
    row_count = 0
    mark_total = 0
    sizes = []
    for chunk in chunks:
        sizes.append(len(chunk))
        row_count += len(chunk)
        mark_total += int(chunk["mark"].sum())
    print("Chunk sizes:", sizes)
    print("Rows:", row_count, "Total marks:", mark_total)
    assert sizes == [10, 10, 10, 5]
    assert row_count == 35 and mark_total == students["mark"].sum()
finally:
    connection.close()

Expected output

Chunk sizes: [10, 10, 10, 5]
Rows: 35 Total marks: 2613

Read a grouped report rather than every row 🔝

Let SQL select columns, filter records and aggregate when the report only needs summaries. This retains counts and totals so each average has a clear denominator. Continue with Data Analysis or groupby().
connection = student_connection()
try:
    query = """SELECT class, COUNT(*) AS students, SUM(mark) AS total_mark,
        AVG(mark) AS average_mark FROM student GROUP BY class ORDER BY class"""
    report = pd.read_sql(query, connection)
    print(report.round({"average_mark": 2}))
    assert report["students"].sum() == 35
    assert report["total_mark"].sum() == students["mark"].sum()
finally:
    connection.close()

Expected output

   class  students  total_mark  average_mark
0  Eight         1          79         79.00
1  Fifth         1          78         78.00
2   Five         2         160         80.00
3   Four         9         638         70.89
4   Nine         2          83         41.50
5  Seven        10         776         77.60
6    Six         7         578         82.57
7  Three         3         221         73.67

SQLAlchemy, connection strings and other databases 🔝

The original SQLAlchemy workflow remains useful for SQLite, MySQL and PostgreSQL. Install SQLAlchemy plus the chosen driver first. For SQLite, an engine URL is sqlite:///my_data.db. PostgreSQL with psycopg uses postgresql+psycopg:// connection settings; store credentials privately and create a URL with SQLAlchemy URL.create rather than publishing passwords. The optional example uses only an in-memory database. If SQLAlchemy is unavailable it reports the skipped branch.
try:
    from sqlalchemy import create_engine, text
except ImportError:
    print("Optional SQLAlchemy example skipped: install sqlalchemy to run it.")
else:
    engine = create_engine("sqlite:///:memory:")
    try:
        with engine.begin() as connection:
            students.to_sql("student", connection, index=False, if_exists="replace")
        with engine.connect() as connection:
            query = text("SELECT id, name, mark FROM student WHERE class=:my_class ORDER BY id")
            result = pd.read_sql(query, connection, params={"my_class": "Five"})
            print(result)
            assert result["id"].tolist() == [8, 18]
    finally:
        engine.dispose()

Expected output

Optional SQLAlchemy example skipped: install sqlalchemy to run it.

Questions and common pitfalls 🔝

  1. How do I read SQL data? Pass a query and compatible connection to read_sql().
  2. What is required? sql and con; index_col, params and other choices are optional.
  3. Can I use a connection string? Yes, with supported SQLAlchemy or ADBC setup and installed drivers.
  4. Can I use a custom query? Yes; bind changing data values with params.
  5. What does con do? It identifies the database connection used to execute the query.
  6. Multiple result sets? read_sql() returns one tabular result, or chunks of it. Read separate SELECT statements separately.
  7. Limit rows? Use the database dialect's LIMIT or equivalent, with ORDER BY when the selection must be repeatable.
  8. PostgreSQL? Use a SQLAlchemy engine and installed PostgreSQL driver; manage credentials and close connections.
  9. SQL NULL? Check dtypes and missing values explicitly.
  10. Large data? Select needed columns and aggregate in SQL, or consume chunks while the connection stays open.

With raw sqlite3, a bare table name is not a query. columns applies to table reads; put column selection in SELECT for queries. Parameter syntax is driver-specific. read_sql() forwards SQL rather than sanitizing an entire statement. coerce_float may convert Decimal values to floats; choose an explicit precision policy when exact values matter.

Exercise: high-scoring Four-class students 🔝

Read id, name and mark for class Four with marks at least 80. Bind both values, sort by id, and count the result.

Exercise solution 🔝

Filter in SQL with bound values, then check the resulting IDs.
connection = student_connection()
try:
    query = "SELECT id, name, mark FROM student WHERE class=:class AND mark>=:mark ORDER BY id"
    answer = pd.read_sql(query, connection, params={"class": "Four", "mark": 80})
    print(answer)
    print("Selected rows:", len(answer))
    assert answer["id"].tolist() == [15, 16, 31]
finally:
    connection.close()

Expected output

   id         name  mark
0  15     Tade Row    88
1  16        Gimmy    88
2  31  Marry Toeey    88
Selected rows: 3

Practice in Google Colab 🔝

Open in Google Colab View on GitHub

Run examples in order, inspect queries and records, then change bound values. Save a copy to keep edits.

Reference: Pandas read_sql documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer