Running SQL on DataFrames

Polars · Advanced · 10 min

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 (pass eager=True for a DataFrame).
  • df.sql("SELECT ... FROM self") queries one frame, which is called self.
  • 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 SQLContext with explicit table names in real code
  • Errors surface at collect time in Polars 2

Watch out for

  • Expecting pl.sql to validate column names immediately: in Polars 2 it fails only when collected.

Check yourself

  1. What does SQLContext.execute(query) return by default?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pola.rs/user-guide/sql/intro/

Builds on

Unlocks

Nothing builds on this yet.