Polars SQL Context: Running SQL Queries Directly on DataFrames

3D isometric illustration of classic SQL query blocks passing through a glowing SQL Context dome to seamlessly run on a high-speed Polars data engine.

We learned how to use DuckDB with Polars . But did you know Polars has its own SQL engine built-in?

You don’t need to install anything extra. You can register your DataFrames as “tables” and run queries instantly.

⚡ Quick Fix: Polars Built-In SQL Engine with SQLContext

pl.SQLContext() registers DataFrames as tables and runs standard SQL against them — no DuckDB or external database required. .execute() returns a lazy frame; call .collect() to materialize results.

import polars as pl

ctx = pl.SQLContext()
ctx.register("customers", customers)
ctx.register("orders", orders)

result = ctx.execute("""
    SELECT c.name, SUM(o.amount) as total_spent
    FROM customers c
    JOIN orders o ON c.id = o.customer_id
    WHERE c.country = 'US'
    GROUP BY c.name
""").collect()

The full walkthrough below covers context setup, multi-table joins, and when to reach for SQLContext over the Polars expression API.

Step 1: The Setup

import polars as pl

# Load some data
customers = pl.DataFrame({
    "id": [1, 2, 3],
    "name": ["Alice", "Bob", "Charlie"],
    "country": ["US", "UK", "US"]
})

orders = pl.DataFrame({
    "id": [101, 102, 103],
    "customer_id": [1, 2, 1],
    "amount": [100, 200, 50]
})

Step 2: The SQL Context

We create a SQLContext and register our frames.

# 1. Create Context
ctx = pl.SQLContext()

# 2. Register tables
ctx.register("customers", customers)
ctx.register("orders", orders)

# 3. Write SQL!
query = """
    SELECT 
        c.name, 
        c.country, 
        SUM(o.amount) as total_spent
    FROM customers c
    JOIN orders o ON c.id = o.customer_id
    WHERE c.country = 'US'
    GROUP BY c.name, c.country
"""

# 4. Execute (Returns a Polars DataFrame)
result = ctx.execute(query).collect()

print(result)

Output:

shape: (1, 3)
┌───────┬─────────┬─────────────┐
│ name  ┆ country ┆ total_spent │
│ ---   ┆ ---     ┆ ---         │
│ str   ┆ str     ┆ i64         │
╞═══════╪═════════╪═════════════╡
│ Alice ┆ US      ┆ 150         │
└───────┴─────────┴─────────────┘

This allows you to reuse legacy SQL queries while getting the performance benefits of the Polars engine.

Polars SQLContext Runs Standard SQL Directly Against Your DataFrames

SQLContext executes queries lazily — .execute() builds a query plan and .collect() triggers it, which means the Polars optimizer applies predicate pushdown and projection pruning to your SQL the same way it does to expression-based queries. Register all tables before calling .execute(); referencing an unregistered name raises a TableNotFound error at execution time, not at registration time, so the failure surfaces later than expected if you build the context incrementally. For teams migrating from a SQL-heavy codebase, SQLContext lets you port existing queries without rewriting them as Polars expressions — run both side by side and replace SQL blocks with expressions only where profiling shows a measurable gain.


Key Takeaways

  • Polars has a built-in SQL engine, allowing users to run queries on DataFrames without needing external installations.
  • Using pl.SQLContext(), you can register DataFrames as tables and execute standard SQL queries directly.
  • The SQLContext queries execute lazily, optimising performance through predicate pushdown and projection pruning.
  • Teams can use SQLContext to maintain legacy SQL queries, transitioning gradually to Polars expressions for efficiency.

Python Pro Hub readers also found these helpful:

Similar Posts

Leave a Reply