Quickstart
Predict 90-day churn for every customer, as of July 1.
- Python
- Java
- Rust
Declare the schema and wire callbacks over your own storage. If your data is in pandas, pandas remains an application dependency:
import pandas as pd
from relativedb import Engine, ExecutionInput, RetrieverWiring, Row
customer_rows = [Row("customers", r.customer_id, {"age": float(r.age)})
for r in customers.itertuples()]
by_id = {row.id: row for row in customer_rows}
def fetch_customers(table, ids, bound):
return [by_id[row_id] for row_id in ids if row_id in by_id]
wiring = (RetrieverWiring.new_wiring()
.entities("customers", fetch_customers)
.entities("orders", fetch_orders)
.default_links(fetch_order_children)
.scanner("customers", scan_customers)
.build())
result = Engine(schema, wiring).execute(ExecutionInput(
query="PREDICT NOT EXISTS(orders.*) OVER (90 DAYS FOLLOWING) FOR EACH customers.customer_id",
anchor_time=pd.Timestamp("2026-07-01").to_pydatetime()))
Declare the schema, wire retrievers over your DAOs, execute:
RelativeDbSchema schema = RelativeDbSchema.newSchema()
.table(TableDef.newTable("customers").column("age", NUMBER)
.column("signup_date", DATETIME).primaryKey("customer_id").build())
.table(TableDef.newTable("orders").column("qty", NUMBER)
.column("order_date", DATETIME).primaryKey("order_id")
.timeColumn("order_date").build())
.link(LinkDef.link("orders", "customer_id", "customers"))
.build();
RetrieverWiring wiring = RetrieverWiring.newWiring()
.entities("customers", (table, ids, bound) -> customerDao.byIds(ids))
.entities("orders", (table, ids, bound) -> orderDao.byIds(ids, bound))
.defaultLinks((link, parent, bound, limit) ->
orderDao.recentByCustomer(parent, bound.asOf().orElse(Instant.MAX), limit))
.build();
RelativeDbEngine engine = RelativeDbEngine.newEngine(schema, wiring).build();
PredictionResult churn = engine.execute(ExecutionInput.newInput()
.query("PREDICT NOT EXISTS(orders.*) OVER (90 DAYS FOLLOWING) FOR EACH customers.customer_id")
.anchorTime(Instant.parse("2026-07-01T00:00:00Z"))
.build()).toCompletableFuture().join();
Closures implement the retriever traits directly:
let schema = Schema::new_schema()
.table(TableDef::new_table("customers")
.column("age", ValueType::Number)
.column("signup_date", ValueType::Datetime)
.primary_key("customer_id").build())
.table(TableDef::new_table("orders")
.column("qty", ValueType::Number)
.column("order_date", ValueType::Datetime)
.primary_key("order_id").time_column("order_date").build())
.link(LinkDef::link("orders", "customer_id", "customers"))
.build();
let wiring = RetrieverWiring::new_wiring()
.entities("customers", entity_lookup)
.default_links(newest_first_children)
.scanner("customers", customer_scan) // enables FOR EACH
.build();
let mut engine = Engine::new(schema, wiring);
let result = engine.execute(
ExecutionInput::query(
"PREDICT NOT EXISTS(orders.*) OVER (90 DAYS FOLLOWING) FOR EACH customers.customer_id")
.anchor_time(anchor))?;
:::info No model required By default predictions come from a transparent, model-free history baseline — the full pipeline runs with zero model artifacts. Swap in the real RT-J model via the native backend. :::
Next steps
- Learn the query language: RelQL tutorial
- Understand the pipeline: Architecture
- Connect real storage: Wire custom retrievers