pola-rs/polars · error

'join_where' requires at least one predicate

Error message

'join_where' requires at least one predicate

What it means

polars raises this while converting a join_where (non-equi join) plan from DSL to IR: for left/right joins the engine must fold every predicate into a single AND-ed condition attached to the join node, and it does that with reduce().expect(...), which panics when the predicate list is empty (join.rs:544-554). It means join_where was called with zero predicates — the Python wrapper (LazyFrame.join_where at frame.py:6635) does not check emptiness because predicates are variadic, so the empty list reaches the Rust side unchecked. Note the asymmetry: an inner join_where with zero predicates silently degenerates to a cross join (inner lowers to cross + per-predicate filters), so this panic is specific to how='left'/'right'. In Python it surfaces as polars.exceptions.PanicException at plan-resolution time (collect/explain), not at the call site.

Source

Thrown at crates/polars-plan/src/plans/conversion/dsl_to_ir/join.rs:557

            last_node = ctxt.lp_arena.add(ir);
        }
    } else {
        // For left and right joins, we cannot lower to cross + filters
        // as null outputs for missing rows would not be preserved.
        // We attach the join predicates/conditions to the joins itself
        // and restore the original `how` join type.
        let node = resolved
            .iter()
            .map(|e| e.node())
            .reduce(|left, right| {
                ctxt.expr_arena.add(AExpr::BinaryExpr {
                    left,
                    op: Operator::And,
                    right,
                })
            })
            .expect("'join_where' requires at least one predicate");
        let predicate = ExprIR::from_node(node, ctxt.expr_arena);

        let IR::Join { options, .. } = ctxt.lp_arena.get(join_node) else {
            unreachable!()
        };
        let mut new_options = (**options).clone();
        new_options.args.how = how;
        new_options.options = JoinTypeOptionsIR::CrossAndFilter { predicate };

        let IR::Join { options, .. } = ctxt.lp_arena.get_mut(join_node) else {
            unreachable!()
        };
        *options = Arc::new(new_options);
    }

    ctxt.conversion_optimizer
        .optimize_exprs(ctxt.expr_arena, ctxt.lp_arena, last_node, false)
        .context("'join_where' failed")?;

View on GitHub (pinned to 68506541d2)

Solutions

  1. Pass at least one boolean predicate over columns of both frames, e.g. pl.col('left_a') > pl.col('right_b').
  2. If predicates are built dynamically, guard the call — raise, skip, or substitute a default predicate when the list is empty.
  3. If you actually want all row combinations, use lf.join(other, how='cross') instead of join_where.
  4. If how='left'/'right' was set by mistake, drop it — plain join_where defaults to an inner non-equi join and needs a condition regardless.

Example fix

# before — panics with "'join_where' requires at least one predicate" when preds is empty
result = lf.join_where(other, *preds)

# after — fail fast with a clear Python-side error
if not preds:
    msg = 'join_where produced no predicates'
    raise ValueError(msg)
result = lf.join_where(other, *preds)
Defensive patterns

Strategy: validation

Validate before calling

import polars as pl

def join_where_safe(lf: pl.LazyFrame, other: pl.LazyFrame, predicates: list[pl.Expr]) -> pl.LazyFrame:
    if not predicates:
        msg = "'join_where' requires at least one predicate"
        raise ValueError(msg)
    return lf.join_where(other, *predicates)

Try / catch

from polars.exceptions import PanicException, InvalidOperationError

try:
    out = lf.join_where(other, pl.col("a") > pl.col("b")).collect()
except (PanicException, InvalidOperationError) as exc:
    # plan-time failure: audit predicate list and dtypes, then re-issue
    ...

Prevention

When it happens

Trigger: Calling lf.join_where(other) with no predicate expressions; splatting a possibly-empty list, lf.join_where(other, *preds) where preds == []; passing a single empty iterable as the 'predicate' (parse_into_list_of_expressions flattens it to zero exprs); any of the above combined with how='left' or how='right' (the inner path never reaches this expect).

Common situations: Config- or user-driven predicate lists that filter down to empty before the call; refactors that move conditions out of join_where but keep the call site; intending an unconditional/cartesian join and reaching for join_where instead of join(how='cross'); tests with placeholder predicate lists that ship empty.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@68506541d2 (2026-08-23). Data as JSON: /api/errors/1bd8eef33242889d. Report an issue: GitHub.