
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.





