Semantic modeling
Semantic modeling lets FeatureQL check business meaning that ordinary SQL types cannot express. Declare meaning where data enters the model, and the compiler carries it through every derived feature.
The five semantic questions
FeatureQL keeps five properties separate:
| Property | Question | Typical syntax |
|---|---|---|
| Datatype | What shape of value is this? | DECIMAL(18,2) |
| Entity | What business object does this key identify? | BIGINT#ORDERS |
| Unit | What does this number measure? | BIGINT@CURRENCY.USD_CENT |
| Multiplicity | How often does this value occur on the current rows, and how should this aggregate count it? | PER ORDER_ID, PER ROW |
| Additivity | May this value be combined across the axes being removed? | NOT ADDITIVE, NOT ADDITIVE ACROSS (...) |
These properties complement one another; none replaces another. A value can have the correct unit but still be repeated across rows, or contribute once per entity but still be unsafe to add across time.
Datatype, entity, unit, and additivity are declared. Multiplicity is different: the compiler works out how often each value occurs from the query itself, and you only write PER to say how an aggregate should count a value that repeats.
The compiler applies one policy to every aggregate. When it can prove the rollup is safe, it stays quiet. When it can prove the rollup is wrong, it rejects the query and names a repair. When it cannot tell, it runs the query and warns.
Declare meaning once
This example combines all five properties:
DEFINE
orders := ENTITY(),
days := ENTITY(),
items := ENTITY(),
order_day_key := INPUT(ROW<order_id BIGINT#orders, day_id DATE#days>),
order_id := INPUT(BIGINT#orders),
facts := INLINE_COLUMNS(
order_day_key ROW(order_id BIGINT#orders, day_id DATE#days) BIND TO order_day_key,
order_total BIGINT@CURRENCY.USD_CENT DETERMINED BY (ORDER_ID),
daily_stock BIGINT@ITEMS NOT ADDITIVE ACROSS (ORDER_DAY_KEY[DAY_ID])
FROM JSON([
{
"order_day_key": {"order_id": 10, "day_id": "2026-01-01"},
"order_total": 3000,
"daily_stock": 10
},
{
"order_day_key": {"order_id": 10, "day_id": "2026-01-02"},
"order_total": 3000,
"daily_stock": 12
},
{
"order_day_key": {"order_id": 20, "day_id": "2026-01-03"},
"order_total": 2000,
"daily_stock": 5
}
])
),
day_id := order_day_key[day_id],
order_total := facts[order_total],
daily_stock := facts[daily_stock]
RETURN
booked_revenue := SUM(order_total) PER order_id,
latest_stock := MAX_BY(daily_stock, day_id)
FOR
(order_day_key, order_id) := BIND_COLUMNS(
order_day_key,
order_id
FROM JSON([
{"order_day_key": {"order_id": 10, "day_id": "2026-01-01"}, "order_id": 10},
{"order_day_key": {"order_id": 10, "day_id": "2026-01-02"}, "order_id": 10},
{"order_day_key": {"order_id": 20, "day_id": "2026-01-03"}, "order_id": 20}
])
)
;| BOOKED_REVENUE BIGINT | LATEST_STOCK BIGINT |
|---|---|
| 5000 | 5 |
Notice what each declaration contributes:
BIGINT#ORDERSandDATE#DAYSidentify business keys.@CURRENCY.USD_CENTand@ITEMSdescribe what the numbers measure.DETERMINED BY (ORDER_ID)records that an order total may repeat below order grain.PER ORDER_IDcontributes each order total once.NOT ADDITIVE ACROSS (order_day_key[day_id])prevents inventory snapshots from being added across the day field of the composite key.day_id := ORDER_DAY_KEY[day_id]names that same axis, andMAX_BY(daily_stock, day_id)explicitly chooses the latest snapshot.
The generated SQL contains the operations needed for PER and MAX_BY(). Entity, unit, and additivity annotations are compile-time contracts.
Put semantics at the boundary
Declare entity keys with INPUT(). Put business data columns, units, and metric contracts in EXTERNAL_COLUMNS (or INLINE_COLUMNS for examples). Extraction and formulas then inherit what the compiler can prove.
ORDERS := ENTITY(),
ORDER_ID := INPUT(BIGINT#ORDERS),
ORDER_FACTS := EXTERNAL_COLUMNS(
order_id BIGINT#ORDERS BIND TO ORDER_ID,
order_total DECIMAL(18,2)@CURRENCY.USD DETERMINED BY (order_id),
FROM TABLE(sales.order_facts)
),
Do not repeatedly relabel derived features. If a derived contract cannot be inferred, inspect the formula or add meaning at its true source.
Choose the focused guide
- Entities — identify business objects, keys, and relationships
- Units and measures — track dimensional meaning and conversions
- Aggregation multiplicity — choose one contribution per key or per row
- Metric additivity — control legal rollups of summarized values, including what V1 deliberately does not check
- Composability and purity — keep a persisted feature the same function when reused
For ordinary datatypes and casts, see Types .