Quickstart
Predict 90-day churn for every customer, as of July 1.
- Python
- Java
- Rust
With pandas, the schema is inferred from your frames (PKs from *_id naming,
time columns from datetime dtypes):
import pandas as pd
import relativedb
ds = relativedb.from_dataframes(
{"customers": customers, "products": products, "orders": orders},
links=[("orders", "customer_id", "customers"),
("orders", "product_id", "products")])
df = ds.predict(
"PREDICT COUNT(orders.*, 0, 90, days) = 0 FOR EACH customers.customer_id",
anchor_time=pd.Timestamp("2026-07-01"))
print(df) # entity_id, probability — one row per customer
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 COUNT(orders.*, 0, 90, days) = 0 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 COUNT(orders.*, 0, 90, days) = 0 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: PQL tutorial
- Understand the pipeline: Architecture
- Connect real storage: Wire custom retrievers