pola-rs/polars · error · ValueError

cannot use 'on' in conjunction with 'left_on' or 'right_on'

Error message

cannot use 'on' in conjunction with 'left_on' or 'right_on'

What it means

LazyFrame.join() raises ValueError when on= is combined with left_on= or right_on=. on= means 'the same key on both frames'; left_on/right_on exist precisely for differing keys, so specifying both is ambiguous.

Source

Thrown at py-polars/src/polars/lazyframe/frame.py:6426

        │ 2   ┆ 7.0 ┆ b   ┆ y     ┆ b         │
        │ 2   ┆ 7.0 ┆ b   ┆ z     ┆ d         │
        │ 3   ┆ 8.0 ┆ c   ┆ x     ┆ a         │
        │ 3   ┆ 8.0 ┆ c   ┆ y     ┆ b         │
        │ 3   ┆ 8.0 ┆ c   ┆ z     ┆ d         │
        └─────┴─────┴─────┴───────┴───────────┘
        """
        require_same_type(self, other)

        if maintain_order is None:
            maintain_order = "none"

        uses_on = on is not None
        uses_left_on = left_on is not None
        uses_right_on = right_on is not None
        uses_lr_on = uses_left_on or uses_right_on
        if uses_on and uses_lr_on:
            msg = "cannot use 'on' in conjunction with 'left_on' or 'right_on'"
            raise ValueError(msg)
        elif uses_left_on != uses_right_on:
            msg = "'left_on' requires corresponding 'right_on'"
            raise ValueError(msg)

        if how == "outer":
            how = "full"
            issue_deprecation_warning(
                "use of `how='outer'` should be replaced with `how='full'`.",
                version="0.20.29",
            )
        elif how == "outer_coalesce":  # type: ignore[comparison-overlap]
            coalesce = True
            how = "full"
            issue_deprecation_warning(
                "use of `how='outer_coalesce'` should be replaced with `how='full', coalesce=True`.",
                version="0.20.29",
            )
        elif how == "cross":

View on GitHub (pinned to df599052da)

Solutions

  1. Use on='id' alone when key names are identical
  2. Use left_on= and right_on= alone (both required) when names differ
  3. In wrappers, use sentinel logic so exactly one of the two styles is passed: kwargs.pop('on', None) before setting left_on/right_on

Example fix

# before
lf.join(other, on='id', left_on='id', right_on='user_id')

# after
lf.join(other, left_on='id', right_on='user_id')
Defensive patterns

Strategy: validation

Validate before calling

if on is not None:
    assert left_on is None and right_on is None, 'do not mix on= with left_on/right_on='
    kwargs = {'on': on}
else:
    kwargs = {'left_on': left_on, 'right_on': right_on}
lf.join(other, **kwargs, how=how)

Type guard

def uses_exclusive_join_keys(on, left_on, right_on) -> bool:
    return not (on is not None and (left_on is not None or right_on is not None))

Prevention

When it happens

Trigger: lf.join(other, on='id', left_on='id') (redundant specification); refactors that added left_on/right_on without removing an existing on=; option dicts that always set on plus per-side keys.

Common situations: Parameterized join wrappers where on has a default value that is not cleared; incremental edits to join calls.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/ff15c0c5cd77a7de. Report an issue: GitHub.