SQL lets analysts and existing queries use Polars directly, and mixing SQL with expressions is often the clearest code.
Polars includes a SQL engine that translates SQL into the same lazy query plans as the expression API, so it gets the same optimizations and speed.
pl.sql("SELECT ... FROM df")finds DataFrames and LazyFrames by their Python variable names in the calling scope and returns a LazyFrame (passeager=Truefor a DataFrame).df.sql("SELECT ... FROM self")queries one frame, which is calledself.pl.SQLContext(orders=lf1, users=lf2)registers tables under explicit names;ctx.execute(query)runs a query. Use it in applications, where implicit variable lookup is too magical.
Supported SQL covers SELECT, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, joins, UNION, CTEs (WITH), window functions with OVER, CASE, and many string, math and date functions. scan_parquet sources work as tables too, so SELECT ... FROM read_parquet('file.parquet') style table functions also exist.
In Polars 2 resolution is deferred: syntax, table and column errors appear when you collect or call collect_schema(), not when you call sql(). Also note that numeric literals without exponents, like 1.5, are exact Decimal values.
SQL and expressions mix freely: run a SQL query, then keep chaining .with_columns(...) on the LazyFrame it returns.
Example
import polars as pl
orders = pl.DataFrame({"uid": [1, 1, 2, 3], "total": [10, 5, 7, 9]})
users = pl.DataFrame({"uid": [1, 2, 3], "country": ["NO", "SE", "NO"]})
ctx = pl.SQLContext(orders=orders, users=users)
q = ctx.execute("""
SELECT u.country, SUM(o.total) AS revenue, COUNT(*) AS n
FROM orders o JOIN users u ON o.uid = u.uid
GROUP BY u.country
ORDER BY revenue DESC
""")
print(type(q).__name__) # LazyFrame
out = q.with_columns(avg=pl.col("revenue") / pl.col("n")).collect()
print(out.rows()) # [('NO', 24, 3, 8.0), ('SE', 7, 1, 7.0)]Remember
- SQL compiles to the same optimized lazy plans as expressions
- Use
SQLContextwith explicit table names in real code - Errors surface at collect time in Polars 2
Watch out for
- Expecting
pl.sqlto validate column names immediately: in Polars 2 it fails only when collected.