A frame is a query that hasn’t run yet, in Polars’ style. You make one with db.table,
db.sql or db.read_csv, chain methods on it, and ask for the rows. The whole chain is one SQL
statement, run by the node: frame.sql shows it. The methods use Polars’ names, so Polars code
moves over with few changes. For PySpark’s names, see
pondra.spark.
WITH _2 AS (SELECT * FROM orders WHERE ("amount" > 3)),
_3 AS (SELECT *, ("amount" * CAST('0.9' AS DOUBLE)) AS "net" FROM _2),
_4 AS (SELECT "item", sum("net") AS "total", count(*) AS "n" FROM _3 GROUP BY 1)
SELECT * FROM _4 ORDER BY "total" DESC NULLS FIRST LIMIT 2
+------+--------------------+---+
| item | total | n |
+------+--------------------+---+
| cake | 12.149999999999999 | 2 |
| tea | 9.45 | 2 |
+------+--------------------+---+
A string that isn’t a plain column name is SQL, so you can mix the two: filter("amount > 5 AND item LIKE 't%'"), with_columns("qty * amount AS total"), agg("sum(amount) AS s").
order the rows; descending and nulls_last take a list for one flag per key
limit(n=5)
the first n rows. Also head
unique(subset=None, keep="any")
distinct rows, or one row per value of the subset columns
sample(n=None, fraction=None, seed=None)
n random rows, or about fraction of them. With a seed you get the same rows every time (they are picked by a hash of the seed and each row’s values, so rows with identical values are picked together)
fill_null(value)
replace nulls in every column of the value’s kind: numbers, text, booleans, dates
drop_nulls(subset=None)
drop rows with a null in the subset columns, or in any column
join two frames. how is inner, left, right, full (or outer), cross, semi or anti. The key comes once for inner, left and right; full and cross keep both. A column of the right side with the same name as one on the left gets suffix
save the frame as a view others read by name. materialized=True keeps it up to date, and options are a materialized view’s: window, size_secs, and the rest of table options. temporary=True keeps it to this connection
write_table(name, mode="create")
rows into a table: create (CREATE TABLE … AS), append (INSERT … SELECT), overwrite (a new table in its place)
write_parquet(path, partition_by=None, mode=None)
Parquet files: a file, or a folder if path ends in / or partition_by names columns
merge(source, on, source_alias="s", target_alias="t")
MERGE, built as Delta Lake’s builder is
sink_parquet, sink_csv, sink_ndjson and write_ndjson are Polars’ names for the file
writers. For the files, mode is "append" or "overwrite"; without it the folder must be empty.
For Delta and Iceberg, mode is "error" (refuse if the folder holds a table), "append",
"overwrite" or "ignore". A path on this machine is written only by a node that
pondra.local started; a URL needs an admin token and a secret.
update, delete and merge change a table, so call them on db.table(name). The merge
builder has these steps, then run():
Step
Meaning
when_matched_update(set=None, predicate=None)
update matched rows: {"amount": col("s.amount")} (s is the source, t the target)
An expression is a column or a computation on columns. col("amount") * 0.9 is one. Methods
that take an expression take a plain value or a SQL string too.
Function
Meaning
col(name)
a column: col("amount"), col("s.amount") for a column of the table called s, col("*") for all
lit(value)
a constant
sql_expr(sql)
a SQL expression as it is, with AS name if you want a name
when(condition).then(value).otherwise(value)
CASE WHEN; chain more .when(…).then(…) before .otherwise
coalesce(*exprs)
the first that isn’t null
concat_str(exprs, separator="")
join text
all()
every column, col("*")
len()
count(*)
fn.name(*args)
call any SQL function, or one of the lake’s own: pondra.fn.slug(col("title")). pondra.fn("ops.slug", x) for a function in a schema
sum(name), mean, min, max, count, n_unique, median, first, last
the aggregate of a column, by name: pondra.sum("amount")
Operators: + - * %, / (always a true division, as a float), // (floor division: whole numbers
give whole numbers, as in Polars, and floats give a float), **, == != < <= > >=, &, |, ~, abs() and -x. A Python comparison needs
parentheses around each side of & and |.
over(partition_by=None, order_by=None, descending=False) computes an expression per group of
rows, as SQL’s OVER (PARTITION BY … ORDER BY …). These methods are for it:
Method
Meaning
shift(n=1)
the value n rows before (n negative: after)
cum_sum()
the running total, in the order_by order
rank(method="average", descending=False)
rank by the column’s values. method is average, min, max, dense or ordinal