window_funnel

Aggregate function for conversion funnel analysis. Searches for the longest chain of sequential conditions within a time window. Returns the maximum funnel step reached.

Signature

window_funnel(window INTERVAL, timestamp TIMESTAMP,
              cond1 BOOLEAN, cond2 BOOLEAN [, ...]) -> INTEGER

window_funnel(window INTERVAL, mode VARCHAR, timestamp TIMESTAMP,
              cond1 BOOLEAN, cond2 BOOLEAN [, ...]) -> INTEGER

Parameters:

ParameterTypeDescription
windowINTERVALMaximum time window from the first step
modeVARCHAROptional comma-separated mode string
timestampTIMESTAMPEvent timestamp
cond1..condNBOOLEANFunnel step conditions (2 to 32)

Returns: INTEGER -- the number of matched funnel steps (0 to N). A return value of 0 means the entry condition was never satisfied.

Usage

Default Mode

SELECT user_id,
  window_funnel(INTERVAL '1 hour', event_time,
    event_type = 'page_view',
    event_type = 'add_to_cart',
    event_type = 'checkout',
    event_type = 'purchase'
  ) as furthest_step
FROM events
GROUP BY user_id;

With Mode String

SELECT user_id,
  window_funnel(INTERVAL '1 hour', 'strict_increase, strict_once',
    event_time,
    event_type = 'page_view',
    event_type = 'add_to_cart',
    event_type = 'purchase'
  ) as furthest_step
FROM events
GROUP BY user_id;

Behavior

  1. Events are sorted by timestamp.
  2. For each event matching the entry condition (cond1), a forward scan begins.
  3. The scan attempts to match cond2, cond3, ..., condN in order, subject to the time window constraint: each subsequent event must occur within window of the entry event.
  4. The maximum step reached across all entry points is returned.

A single event can advance multiple funnel steps in default mode. For example, if an event satisfies both cond2 and cond3, it advances the funnel by two steps in a single pass.

Example

Given events for a user with a 1-hour window and 3-step funnel:

event_timepage_viewadd_to_cartpurchase
10:00truefalsefalse
10:20falsetruefalse
11:30falsefalsetrue

Result: 2

  • Step 1 matched at 10:00 (page_view).
  • Step 2 matched at 10:20 (add_to_cart, within 1 hour of 10:00).
  • Step 3 at 11:30 is outside the 1-hour window from the entry at 10:00.

Determinism

Events sort by (timestamp, conditions) before the scan, so results are deterministic regardless of thread count, physical row order, or the order in which DuckDB's parallel aggregation combines partial states — including same-timestamp event bursts.

Modes

Modes are independently combinable via a comma-separated string parameter. Each mode adds an additional constraint on top of the default greedy scan.

ModeDescription
strictIf the previously-matched condition fires again, the chain breaks. Prevents backwards movement. ClickHouse: 'strict' or 'strict_deduplication'.
strict_deduplicationAlias for strict (matches ClickHouse, where both strings map to the same behavior).
strict_orderEvents must satisfy conditions in exact sequential order. No events matching earlier conditions are allowed between matched steps.
strict_increaseRequires strictly increasing timestamps between consecutive matched steps. Same-timestamp events cannot advance the funnel.
strict_onceEach event can advance the funnel by at most one step, even if it satisfies multiple consecutive conditions.
allow_reentryIf the entry condition fires again after step 1, the funnel resets from that new entry point.
timestamp_dedupExtension mode. Events with the same timestamp as the previously matched step are skipped. Not present in ClickHouse.

Mode Combinations

Modes can be combined freely:

-- Require strictly increasing timestamps and one step per event
window_funnel(INTERVAL '1 hour', 'strict_increase, strict_once',
  ts, cond1, cond2, cond3)

-- Strict order with reentry
window_funnel(INTERVAL '1 hour', 'strict_order, allow_reentry',
  ts, cond1, cond2, cond3)

Errors

Invalid configuration aborts the query with a descriptive SQL error instead of silently producing wrong results:

  • Unknown mode stringwindow_funnel: unknown mode 'strict_typo'; valid modes are 'strict', 'strict_deduplication', 'strict_order', 'strict_increase', 'strict_once', 'allow_reentry', 'timestamp_dedup' (comma-separated for combinations)
  • Month-based window — month intervals are ambiguous (28-31 days); use day/hour/minute/second units (e.g. INTERVAL '30 days')
  • Negative window — the window must be non-negative

A NULL window or mode is skipped leniently (the row contributes no configuration), matching SQL aggregate conventions.

Implementation

Events are collected during the update phase and sorted by timestamp during finalize. A greedy forward scan from each entry point finds the longest chain.

OperationComplexity
UpdateO(1) amortized (event append)
CombineO(m) where m = events in other state
FinalizeO(n * k) where n = events, k = conditions
SpaceO(n) -- all collected events

At benchmark scale, window_funnel processes 100 million events in 791 ms (126 Melem/s).

See Also