taxis.dim_zones
┌─────────┬────────────────┬───────────┐
│ zone_id │ name │ borough │
├─────────┼────────────────┼───────────┤
│ 100 │ Midtown │ Manhattan │
│ 101 │ Downtown │ Brooklyn │
│ 102 │ Astoria │ Queens │
│ 103 │ Yankee Stadium │ Bronx │
└─────────┴────────────────┴───────────┘
taxis.fct_trips
┌─────────┬──────────────┬───────┬─────────────┬─────────────────────┐
│ trip_id │ trip_zone_id │ fare │ tolls_cents │ dropoff_at │
├─────────┼──────────────┼───────┼─────────────┼─────────────────────┤
│ 1001 │ 100 │ 42.50 │ 0 │ 2025-06-01 08:00:00 │
│ 1002 │ 100 │ 38.00 │ 0 │ 2025-07-15 14:45:00 │
│ 1003 │ 101 │ 18.50 │ 0 │ 2025-08-20 17:30:00 │
│ 1004 │ 101 │ 24.00 │ 0 │ 2025-10-05 16:20:00 │
│ 1005 │ 100 │ 52.00 │ 0 │ 2025-11-12 11:30:00 │
│ 1006 │ 102 │ 70.00 │ 694 │ 2025-11-15 14:30:00 │
│ 1007 │ 102 │ 45.50 │ 694 │ 2026-01-21 10:00:00 │
│ 1008 │ 101 │ 21.50 │ 0 │ 2026-01-30 09:45:00 │
│ 1009 │ 103 │ 58.00 │ 300 │ 2026-01-10 12:00:00 │
│ 1010 │ 103 │ 61.50 │ 0 │ 2026-01-28 09:30:00 │
└─────────┴──────────────┴───────┴─────────────┴─────────────────────┘
taxis.agg_zones_obt
┌─────────┬──────────────┬─────────────────────┐
│ zone_id │ last_trip_id │ trips │
├─────────┼──────────────┼─────────────────────┤
│ 100 │ 1005 │ [1001, 1002, 1005] │
│ 101 │ 1008 │ [1003, 1004, 1008] │
│ 102 │ 1007 │ [1006, 1007] │
│ 103 │ 1010 │ [1009, 1010] │
└─────────┴──────────────┴─────────────────────┘
FeatureQL Formula language for business logic
AI writes your business logic as small, composable formulas.
The compiler checks that it is consistent. You check that it is right.
Define once, use in analytics, ML, and production systems.
Runs FeatureQL queries in your browser. Nothing to install.
or
pip install featuremeshIncludes local registry and serving backend. No account required.
Why use FeatureQL?
No more business logic expressed in different languages across queries, models, and services.
FeatureQL unifies calculations with a unique name, semantic type, and explicit dependencies.
Declarative
Specify what, not how. No boilerplate. Logic is decoupled from storage or optimization.
Pure functions
Deterministic transformations, no side effects.
Composable
Similar to spreadsheets where cells reference other cells, features reference other features.
Entity-centric
Think in terms of customers and orders, not joins and tables.
Semantic types
CUSTOMER_ID ≠ ORDER_ID, even if both are BIGINT. Dollars can't be added to cents.
Universal execution
Transpiles to multiple SQL dialects (batch) or DataFusion (serving).
FeatureQL abstracts SQL
FeatureQL applies to clean tables with keys (gold layer).
Data engineers define core features once.
Everyone else composes on top.
Zone fare total
Sum trip fares per zone from the fact table
RETURN
zone_id,
zone_name,
zone_fare_total := zone_id.RELATED(
SUM(trip_fare)
GROUP BY (trip_zone_id)
),
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
;WITH trips_agg AS (
SELECT
trip_zone_id,
SUM(fare) AS zone_fare_total
FROM taxis.fct_trips
GROUP BY (trip_zone_id)
)
SELECT
z.zone_id,
z.name AS zone_name,
t.zone_fare_total
FROM taxis.dim_zones z
LEFT JOIN trips_agg t
ON z.zone_id = t.trip_zone_id
;| zone_id | zone_name | zone_fare_total |
|---|---|---|
| 100 | Midtown | 132.50 |
| 101 | Downtown | 64.00 |
| 102 | Astoria | 115.50 |
| 103 | Yankee Stadium | 119.50 |
Zone fare from array
Compute the same zone fare total from an array of trip ids
DEFINE
zone_trips_details := EXTEND(
ZIP(zone_trips AS trip_id)
WITH trip_fare AS trip_fare
VIA trip_id BIND TO trip_id
)
RETURN
zone_id,
zone_name,
zone_fare_total_arr := ARRAY_SUM(zone_trips_details[trip_fare]),
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
;WITH trips_unnested AS (
SELECT
obt.zone_id,
UNNEST(obt.trips) AS trip_id
FROM taxis.agg_zones_obt obt
),
trips_with_fare AS (
SELECT
u.zone_id,
t.fare
FROM trips_unnested u
LEFT JOIN taxis.fct_trips t
ON u.trip_id = t.trip_id
)
SELECT
z.zone_id,
z.name AS zone_name,
SUM(f.fare) AS zone_fare_total_arr
FROM taxis.dim_zones z
LEFT JOIN trips_with_fare f
ON z.zone_id = f.zone_id
GROUP BY z.zone_id, z.name
;| zone_id | zone_name | zone_fare_total_arr |
|---|---|---|
| 100 | Midtown | 132.50 |
| 101 | Downtown | 64.00 |
| 102 | Astoria | 115.50 |
| 103 | Yankee Stadium | 119.50 |
Priority zone
Flag zones above $100 in fares with a trip in the last 7 days
RETURN
zone_id,
zone_name,
zone_fare_total,
recency := DATE_DIFF(
TIMESTAMP '2026-02-01',
last_trip_id.RELATED(trip_dropoff_at),
'day'
),
flag_priority_zone :=
recency <= 7
AND zone_fare_total > CAST(100 AS _@CURRENCY.USD),
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
;WITH zone_fare_total AS (
SELECT
trip_zone_id,
SUM(fare) AS zone_fare_total
FROM taxis.fct_trips
GROUP BY (trip_zone_id)
)
SELECT
z.zone_id,
z.name AS zone_name,
fare.zone_fare_total,
DATE_DIFF('day', CAST(t.dropoff_at AS DATE), DATE '2026-02-01') AS recency,
DATE_DIFF('day', CAST(t.dropoff_at AS DATE), DATE '2026-02-01') <= 7
AND fare.zone_fare_total > 100.00 AS flag_priority_zone
FROM taxis.dim_zones z
LEFT JOIN taxis.agg_zones_obt obt
ON z.zone_id = obt.zone_id
LEFT JOIN taxis.fct_trips t
ON obt.last_trip_id = t.trip_id
LEFT JOIN zone_fare_total fare
ON z.zone_id = fare.trip_zone_id
;| zone_id | zone_name | zone_fare_total | recency | flag_priority_zone |
|---|---|---|---|---|
| 100 | Midtown | 132.50 | 81 | false |
| 101 | Downtown | 64.00 | 2 | false |
| 102 | Astoria | 115.50 | 11 | false |
| 103 | Yankee Stadium | 119.50 | 4 | true |
Priority A/B experiment
Compare a 7-day window with a 14-day one without copying the rule
DEFINE
threshold := INPUT(BIGINT),
flag_priority_zone_macro := MACRO(
recency <= threshold
AND zone_fare_total > CAST(100 AS _@CURRENCY.USD)
USING threshold
),
flag_priority_zone_control := flag_priority_zone_macro(7),
flag_priority_zone_test := flag_priority_zone_macro(14),
expose_to_test := SAMPLE_BOOL(
CAST(KEY_TO_SCALAR(zone_id) AS VARCHAR) || 'SALT',
0.5e0
),
RETURN
zone_id,
zone_name,
recency,
expose_to_test,
flag_priority_zone_control,
flag_priority_zone := IF(
expose_to_test,
flag_priority_zone_test,
flag_priority_zone_control
),
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
;WITH zone_fare_total AS (
SELECT
trip_zone_id,
SUM(fare) AS zone_fare_total
FROM taxis.fct_trips
GROUP BY (trip_zone_id)
),
zone_recency AS (
SELECT
obt.zone_id,
DATE_DIFF('day', CAST(t.dropoff_at AS DATE), DATE '2026-02-01') AS recency
FROM taxis.agg_zones_obt obt
LEFT JOIN taxis.fct_trips t
ON obt.last_trip_id = t.trip_id
),
zone_exposure AS (
SELECT
zone_id,
CAST(
CAST(
('0x' || SUBSTRING(MD5(zone_id::VARCHAR || 'SALT'), 1, 15)) AS BIGINT
) AS DOUBLE
) / POW(2, 60) < 0.5 AS expose_to_test
FROM taxis.dim_zones
)
SELECT
z.zone_id,
z.name AS zone_name,
r.recency,
e.expose_to_test,
r.recency <= 7
AND fare.zone_fare_total > 100.00 AS flag_priority_zone_control,
CASE
WHEN e.expose_to_test
THEN (r.recency <= 14 AND fare.zone_fare_total > 100.00)
ELSE (r.recency <= 7 AND fare.zone_fare_total > 100.00)
END AS flag_priority_zone
FROM taxis.dim_zones z
LEFT JOIN zone_recency r
ON z.zone_id = r.zone_id
LEFT JOIN zone_fare_total fare
ON z.zone_id = fare.trip_zone_id
LEFT JOIN zone_exposure e
ON z.zone_id = e.zone_id
;| zone_id | zone_name | recency | expose_to_test | flag_priority_zone_control | flag_priority_zone |
|---|---|---|---|---|---|
| 100 | Midtown | 81 | false | false | false |
| 101 | Downtown | 2 | false | false | false |
| 102 | Astoria | 11 | true | false | true |
| 103 | Yankee Stadium | 4 | true | true | true |
FeatureQL extends SQL
Mix both in one query: SQL around FeatureQL for reporting, SQL inside FeatureQL for sources
SELECT
COALESCE(borough, 'All boroughs') AS borough,
COUNT(*) AS zones,
SUM(fare_total) AS fare_total
FROM FEATUREQL(
RETURN
borough := tables.dim_zones[borough],
fare_total := zone_fare_total,
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
)
GROUP BY ROLLUP (borough)
ORDER BY GROUPING(borough), borough
;DEFINE
tables.zone_activity := EXTERNAL_COLUMNS(
zone_id BIGINT#zones BIND TO zone_id,
trips_2026 BIGINT
FROM SQL(
SELECT
trip_zone_id AS zone_id,
COUNT(*) AS trips_2026
FROM taxis.fct_trips
WHERE dropoff_at >= TIMESTAMP '2026-01-01'
GROUP BY trip_zone_id
)
),
zone_trips_2026 := COALESCE(tables.zone_activity[trips_2026], 0),
RETURN
zone_id,
zone_name,
zone_fare_total,
zone_trips_2026,
is_active_2026 := zone_trips_2026 > 0,
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
;| borough | zones | fare_total |
|---|---|---|
| Bronx | 1 | 119.50 |
| Brooklyn | 1 | 64.00 |
| Manhattan | 1 | 132.50 |
| Queens | 1 | 115.50 |
| All boroughs | 4 | 431.50 |
| zone_id | zone_name | zone_fare_total | zone_trips_2026 | is_active_2026 |
|---|---|---|---|---|
| 100 | Midtown | 132.50 | 0 | false |
| 101 | Downtown | 64.00 | 1 | true |
| 102 | Astoria | 115.50 | 1 | true |
| 103 | Yankee Stadium | 119.50 | 2 | true |
Additional safety
SQL runs these and returns a wrong result.
FeatureQL rejects them before execution.
Entities
trip_id and zone_id are both integers. SQL joins them and returns a NULL name for every trip. FeatureQL refuses a trip key looking up a zone feature.
RETURN
trip_id,
trip_zone_name := trip_id.RELATED(zone_name),
FROM fm.taxis
FOR
trip_id := @BIND_KEYSET(fct_trips_keyset),
;SELECT
t.trip_id,
z.name AS trip_zone_name
FROM taxis.fct_trips t
LEFT JOIN taxis.dim_zones z
ON t.trip_id = z.zone_id
;UE/RELATED-ENTITY-MISMATCH
RELATED() VIA ... requires the same entity family on both sides when entity annotations are present; field 'FM.TAXIS.TRIP_ID' has entity family 'FM.TAXIS.TRIPS' on the base side but entity family 'FM.TAXIS.ZONES' on the bound side when defining feature TRIP_ZONE_NAME.Units
Fares are dollars and tolls are cents. SQL adds them and returns 764.00 for trip 1006. The real revenue is 76.94. FeatureQL refuses the addition.
RETURN
trip_id,
trip_revenue := trip_fare + trip_tolls,
FROM fm.taxis
FOR
trip_id := @BIND_KEYSET(fct_trips_keyset),
;SELECT
trip_id,
fare,
tolls_cents,
fare + tolls_cents AS trip_revenue
FROM taxis.fct_trips
WHERE tolls_cents > 0
;UE/UNIT-LABEL-MISMATCH
Unit labels must match for ADD(); got USD and USD cent. Annotate or convert one operand so both share the same unit when defining feature TRIP_REVENUE.Additivity
SQL averages the four zone averages and returns 45.75. The real city average is 43.15. FeatureQL refuses to average a non-additive average.
DEFINE
zone_avg_fare := zone_id.RELATED(
AVG(trip_fare)
GROUP BY (trip_zone_id)
),
RETURN
city_avg_fare := AVG(zone_avg_fare),
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
;WITH zone_avg AS (
SELECT
trip_zone_id,
AVG(fare) AS zone_avg_fare
FROM taxis.fct_trips
GROUP BY (trip_zone_id)
)
SELECT CAST(AVG(zone_avg_fare) AS DECIMAL(10, 2)) AS city_avg_fare
FROM zone_avg
;UE/SUMMARIZABILITY-NOT-ADDITIVE
Summing function AVG() cannot consume NOT ADDITIVE feature ZONE_AVG_FARE when defining CITY_AVG_FARE. Recompute AVG() from detail, divide SUM(total) by SUM(count), or use UNSAFE AVG(ZONE_AVG_FARE) when the override is intentional.Multiplicity
Exploding the trip array repeats each zone total once per trip. SQL returns 10 trips and 1059.50 in fares. The real city total is 431.50. FeatureQL refuses the sum.
DEFINE
zone_trip := UNNEST(ZIP(zone_trips AS trip_id)),
RETURN
city_trips := COUNT(DISTINCT zone_trip[trip_id]),
city_fare_total := SUM(zone_fare_total),
FROM fm.taxis
FOR
zone_id := @BIND_KEYSET(dim_zones_keyset),
trip_id := @BIND_KEYSET(fct_trips_keyset),
;WITH zone_fare_total AS (
SELECT
trip_zone_id,
SUM(fare) AS zone_fare_total
FROM taxis.fct_trips
GROUP BY (trip_zone_id)
),
zone_trip AS (
SELECT
obt.zone_id,
UNNEST(obt.trips) AS trip_id
FROM taxis.agg_zones_obt obt
)
SELECT
COUNT(DISTINCT zt.trip_id) AS city_trips,
SUM(f.zone_fare_total) AS city_fare_total
FROM zone_trip zt
LEFT JOIN zone_fare_total f
ON zt.zone_id = f.trip_zone_id
;UE/STATIC-ANALYSIS-AGGREGATE-COARSER-GRAIN-THAN-SPINE
Multiplicity-sensitive aggregate CITY_FARE_TOTAL must state how feature FM.TAXIS.ZONE_FARE_TOTAL contributes over a finer spine. FM.TAXIS.ZONE_FARE_TOTAL has grain FM.TAXIS.ZONES and the spine has grain FM.TAXIS.TRIPS, FM.TAXIS.ZONES plus occurrence grain ZONE_TRIP. Use PER FM.TAXIS.ZONE_ID to count each fm.taxis.zone once, or PER ROW to weight by mixed-spine multiplicity.FeatureMesh: registry, governance, serving
FeatureQL is the language. FeatureMesh is the platform that stores features, validates changes against their dependents, and serves them at request latency.
A semantic layer for analytics
By defining features on top of analytics tables, FeatureMesh becomes a semantic layer for your data analytics and BI.
Define your business metrics once in FeatureQL and serve it to every BI tool without drift.
A feature platform for production
By defining features on top of operational data sources, FeatureMesh becomes a feature platform for production APIs and online ML inference.
Low latency serving without discrepancy between training and serving.
An executable memory for AI Agents
An agent works out a calculation once and persists it in the registry. Every later call reuses that definition instead of re-deriving it, so the answer is the same every time.
LLMs read, write, and compose features the same way your team does.
Try FeatureQL
Give your LLM the reference docs.featuremesh.com/llms.txt before asking it to write or review FeatureQL.
or