Variants

VARIANT() creates a new version of a feature by replacing specific dependencies with alternative definitions. The original feature and all its other dependencies stay unchanged — only the parts you specify get swapped.

This is the core mechanism for experimentation and what-if analysis in FeatureQL. Instead of duplicating an entire computation pipeline to test one change, you replace a single dependency and let the system rebuild the rest.

Syntax

VARIANT(
    feature
    REPLACING FEATURES dep1, dep2, ...
    WITH replacement1, replacement2, ...
)
sql

The REPLACING FEATURES clause names the dependencies to swap out; WITH provides the new definitions in the same order.

Basic usage

The simplest use of VARIANT() is evaluating a feature with different hardcoded inputs. Here, OPERATION computes POW(EXPR, POWER), and two variants evaluate it with different values:

FeatureQL
WITH
    EXPR := INPUT(BIGINT),
    POWER := INPUT(BIGINT),
    POW(EXPR, POWER) AS OPERATION,
SELECT
    RESULT1 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 1, 2),
    RESULT2 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 3, 4),
;
Result
RESULT1 BIGINTRESULT2 BIGINT
181

You can also include the original feature alongside its variants. This is useful for comparing a baseline against alternatives:

FeatureQL
WITH
    EXPR := INPUT(BIGINT),
    POWER := INPUT(BIGINT),
    POW(EXPR, POWER) AS OPERATION,
SELECT
    RESULT1 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 1, 2),
    RESULT2 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 3, 4),
    OPERATION,
FOR CROSS
    EXPR := BIND_VALUE(5),
    POWER := BIND_VALUE(6),
;
Result
RESULT1 BIGINTRESULT2 BIGINTOPERATION BIGINT
18115625

Replacing with expressions

Replacements aren't limited to literals — you can use any feature or expression. Here, EXPR2 is a named feature used as a replacement value, and it can appear in multiple variants:

FeatureQL
WITH
    EXPR := INPUT(BIGINT),
    POWER := INPUT(BIGINT),
    POW(EXPR, POWER) AS OPERATION,
SELECT
    EXPR2 := 2,
    RESULT1 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 2, 2),
    RESULT2 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 2, EXPR2),
    RESULT3 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH EXPR2, EXPR2)
;
Result
EXPR2 BIGINTRESULT1 BIGINTRESULT2 BIGINTRESULT3 BIGINT
2444

Replacing formulas, not just inputs

VARIANT() replaces dependencies, not the feature itself. To swap the formula of a feature, create an alias and replace the alias's dependency:

FeatureQL
WITH
    EXPR := INPUT(BIGINT),
    POWER := INPUT(BIGINT),
    POW(EXPR, POWER) AS OPERATION_BASE,
    EXPR + POWER AS OPERATION_REPLACEMENT,
    OPERATION_ALIAS := OPERATION_BASE,
SELECT
    EXPR,
    POWER,
    RESULT1 := VARIANT(OPERATION_BASE REPLACING FEATURES OPERATION_BASE WITH OPERATION_REPLACEMENT),  -- That does not work as replaces only dependencies
    RESULT2 := VARIANT(OPERATION_ALIAS REPLACING FEATURES OPERATION_BASE WITH OPERATION_REPLACEMENT),  -- Creating an alias works
FOR CROSS
    EXPR := BIND_VALUE(5),
    POWER := BIND_VALUE(6),
Result
EXPR BIGINTPOWER BIGINTRESULT1 BIGINTRESULT2 BIGINT
561562511

Notice that RESULT1 returns the original value (15625 = 5^6) because replacing OPERATION_BASE within itself has no effect — it's not a dependency of itself. RESULT2 works because OPERATION_ALIAS depends on OPERATION_BASE, so the replacement takes effect.

Binding through variants

You can't use BIND_VALUES() directly inside a VARIANT() replacement. Instead, bind to a separate input feature and use that feature as the replacement:

FeatureQL
WITH
    EXPR := INPUT(BIGINT),
    POWER := INPUT(BIGINT),
    POW(EXPR, POWER) AS OPERATION,
    BINDED_FEATURE := INPUT(BIGINT),
    BIND_VALUES(ARRAY[1,2,3]) AS BINDED_FEATURE,
SELECT
    EXPR2 := 2,
    RESULT1 := VARIANT(OPERATION REPLACING FEATURES EXPR, POWER WITH 2, BINDED_FEATURE),
;
Result
EXPR2 BIGINTRESULT1 VARCHAR
22
24
28

This produces multiple rows because BINDED_FEATURE is bound to three values, and the variant inherits that binding.

Shared dependencies

When a variant replaces a dependency that's shared with other features, each feature gets its own version of the dependency chain. Consider features A and B that both depend on C, which depends on D:

A := FUNC(C),
B := FUNC(C),
C := FUNC(D, E)
sql

Creating VARIANT(A REPLACING FEATURES D WITH D_new) produces:

  • A_variant uses a modified C that references D_new instead of D
  • B continues using the original C with the original D

The two features no longer share the same C. For caching and lineage tracking, each feature is identified by its name plus a hash of its entire dependency tree — not just its name.

Nested variants

Variants can build on other variants. When multiple VARIANT() calls are chained, substitutions closer to the original feature take precedence:

FeatureQL
WITH
    EXPR1 := INPUT(BIGINT),
    EXPR1 := BIND_VALUES(ARRAY[1,2,3]),
    EXPR2 := 2,
    EXPR3 := 3,
    EXPR4 := 4,
    OPERATION_1 := EXPR1 + EXPR2,
    OPERATION_2 := OPERATION_1 * EXPR4,  -- OPERATION_2 = (EXPR1 + EXPR2) * EXPR4
    OPERATION_3 := POW(OPERATION_2, EXPR4),
    OPERATION_4 := EXPR1 - EXPR2,
    OPERATION_FINAL := OPERATION_3 + OPERATION_1,  -- OPERATION_FINAL := POW((EXPR1 + EXPR2) * EXPR4, EXPR4) + (EXPR1 + EXPR2),
    OPERATION_2_REPLACEMENT1 := OPERATION_1 * EXPR3,  -- OPERATION_2 = (EXPR1 + EXPR2) * EXPR3
    OPERATION_2_REPLACEMENT2 := VARIANT(OPERATION_2 REPLACING FEATURES OPERATION_1, EXPR4 WITH OPERATION_4, 5),  -- OPERATION_2 = (EXPR1 - EXPR2) * 5
SELECT
    OPERATION_ALIAS1 := VARIANT(OPERATION_FINAL REPLACING FEATURES OPERATION_2 WITH OPERATION_2_REPLACEMENT1),  -- OPERATION_ALIAS1 := POW((EXPR1 + EXPR2) * EXPR3, EXPR4) + (EXPR1 + EXPR2),
    OPERATION_ALIAS2 := VARIANT(OPERATION_FINAL REPLACING FEATURES OPERATION_2 WITH OPERATION_2_REPLACEMENT2),  -- OPERATION_ALIAS2 := POW((EXPR1 - EXPR2) * 5, EXPR4) + (EXPR1 + EXPR2),
;
Result
OPERATION_ALIAS1 BIGINTOPERATION_ALIAS2 VARCHAR
207404
50630630
6564628

This example has several layers: OPERATION_FINAL depends on OPERATION_3, which depends on OPERATION_2, which depends on OPERATION_1. The two variants replace OPERATION_2 with different alternatives — one that changes the multiplier, and one that changes both the base expression and the exponent.

When to use variants vs macros

Both VARIANT() and MACRO() enable reuse, but they serve different purposes:

VARIANT()MACRO()
PurposeCreate alternative versions of existing featuresCreate reusable function templates
MechanismReplaces dependencies in an existing DAGParameterizes a feature expression
Best forExperimentation, A/B testing, what-if analysisReusable calculations, DRY patterns
ScopeAffects the entire dependency chainSelf-contained — no side effects on other features

See Macros for details on creating reusable function templates.

Last update at: 2026/03/03 16:47:38
Last updated: 2026-03-03 16:48:19