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, ...
) 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:
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),
;| RESULT1 BIGINT | RESULT2 BIGINT |
|---|---|
| 1 | 81 |
You can also include the original feature alongside its variants. This is useful for comparing a baseline against alternatives:
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),
;| RESULT1 BIGINT | RESULT2 BIGINT | OPERATION BIGINT |
|---|---|---|
| 1 | 81 | 15625 |
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:
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)
;| EXPR2 BIGINT | RESULT1 BIGINT | RESULT2 BIGINT | RESULT3 BIGINT |
|---|---|---|---|
| 2 | 4 | 4 | 4 |
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:
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),| EXPR BIGINT | POWER BIGINT | RESULT1 BIGINT | RESULT2 BIGINT |
|---|---|---|---|
| 5 | 6 | 15625 | 11 |
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:
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),
;| EXPR2 BIGINT | RESULT1 VARCHAR |
|---|---|
| 2 | 2 |
| 2 | 4 |
| 2 | 8 |
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) Creating VARIANT(A REPLACING FEATURES D WITH D_new) produces:
A_variantuses a modifiedCthat referencesD_newinstead ofDBcontinues using the originalCwith the originalD
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:
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),
;| OPERATION_ALIAS1 BIGINT | OPERATION_ALIAS2 VARCHAR |
|---|---|
| 20740 | 4 |
| 50630 | 630 |
| 6564 | 628 |
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() | |
|---|---|---|
| Purpose | Create alternative versions of existing features | Create reusable function templates |
| Mechanism | Replaces dependencies in an existing DAG | Parameterizes a feature expression |
| Best for | Experimentation, A/B testing, what-if analysis | Reusable calculations, DRY patterns |
| Scope | Affects the entire dependency chain | Self-contained — no side effects on other features |
See Macros for details on creating reusable function templates.