Skip to main content
A static prediction fills in a column of your table for the rows where it is missing: an account’s balance, a user’s income, whether a transaction was fraud. The rows where the value is known are the examples, and features still come from every linked table. Unlike the temporal predictions on Relational data, which forecast events after a date, a static target has no time window, so the tables need no dates. Nothing is trained.

No dates needed

A database with no time columns works. There are no anchor_times, observed_through or anchor_time to choose.

Linked tables count

Features follow foreign keys, such as each account’s transactions and its branch, the same way they do for forecasts.

No leaks

The target column is removed before features are built, so no feature can read any row’s value, directly or through another table.

Numbers and 0/1 flags

A numeric column is regression. For a 0/1 column, such as a loan flag, you get the probability of a 1.
Static predictions need nori-rel 0.4.6 or later; see Installation. Local mode runs Nori in your process, with no API key. The outputs on this page come from a synthetic bank: 1,000 accounts, 7,046 transactions, no date columns, and 20% of the balances set to NULL.

Quickstart

Connect without registering any time columns, then name the column to predict and the table it belongs to:
The 800 accounts with a known balance were the examples, and the 200 with a NULL balance were scored. Against the hidden values, these predictions have an R² of 0.945 and a mean absolute error of 1,468, where predicting the mean balance for every account gives 6,530. anchor_time is the time of the read.

How it works

A static target is a column of the FOR EACH table, with no time window. Nori-Rel reads every table once and splits the rows of that table in two:
  • Examples are the rows whose value is known (not NULL, and not NaN) and that pass the query’s WHERE. The rows being scored are never examples, even when their value is known. max_context_rows (default 5,000) counts only these rows. Above it, Nori-Rel keeps the lowest primary keys, often the oldest rows, or the newest rows when you pass entity_created_at. To sample the whole table instead, see Choose the examples.
  • Scored rows are the ones FOR names. FOR EACH means every row whose value is NULL or NaN; FOR accounts.account_id IN (3, 8) means those rows, known value or not. query_filter= narrows them further.
Before features are built, the target column is removed from the tables. The features then come from the row’s other columns and its linked rows, such as the count and mean amount of its transactions and its branch’s city. No feature can read a balance: not the account’s own, and not one reached through another table, such as a branch’s average balance. Every row’s features use the data as of the read: PostgreSQL’s transaction time, or the pinned table versions on Snowflake and Databricks.

Check predictions against known values

Name rows whose value is known. They are scored and left out of the examples, so you can compare each prediction with the real value without the model having seen it:
The actual balances of accounts 1, 2 and 3 are 2,642.87, 21,436.29 and 10,351.17.

0/1 targets

A column of 0s and 1s, such as a loan flag, can be predicted as the probability of a 1. Its values alone don’t say whether you want a number or a probability, so Nori-Rel asks. It checks every stored value, not only the examples:
Pass task="regression" with output_type="mean". The mean of a 0/1 label is the probability of a 1:
On the synthetic bank, with 20% of the loan flags hidden, these scores rank the hidden flags with an AUC of 0.758.

Values fixed when the row was created

Leave entity_created_at out when the value is the row’s current state, such as a balance. Pass it when the value describes the row at the moment it was created, such as fraud on a transaction. Each row’s features then stop at that row’s own time:
Without it, an old transaction’s features would include later events, such as a chargeback, that a new transaction can’t have yet. With it, rows with a NULL or future time are left out, and the result’s anchor_time is each row’s own time.

Choose the examples

By default every row with a known value is an example, up to max_context_rows (default 5,000), taken by lowest primary key. Pass context=ContextPolicy(...) to choose them another way. Every setup below ran on the synthetic bank; each score is the mean absolute error on the 200 hidden balances: Use every row when it fits. Below, each setup is one call. On a large table the lowest keys are usually the oldest rows. A random sample covers the whole table instead, and the same seed keeps the same rows on every run:
context replaces max_context_rows; passing both raises an error.

Narrow the examples with your own SQL

candidate_sql keeps only the examples your SELECT returns, for filters the query’s WHERE can’t express, such as a join. It reads candidates(entity_id, anchor_time), the rows Nori-Rel would use, and can only narrow them:
Name tables with their schema, as above. To filter on the entity’s own columns, WHERE in the query is simpler, for example ... FOR EACH accounts.account_id WHERE accounts.branch_id = 1.

Balance a 0/1 label

ClassBalance keeps every positive and a set number of negatives per positive, so a rare label isn’t drowned out. Nori-Rel then maps the predicted probabilities back to the population’s rate. With local regression on a 0/1 column, use output_type="mean":
The balanced sample is half positives, so each predicted probability’s odds are multiplied by 0.5625 to undo that. The corrected predictions average 0.367 against a known rate of 0.360. At a 36% positive rate balancing doesn’t help the ranking: AUC is 0.744 balanced and 0.758 with the natural mix. Balance a label when positives are rare, and keep the natural mix otherwise.

See which rows were kept

details.metadata["context_policy"] reports the setup every prediction used, with or without a policy: the policy itself, the label counts before and after selection (labelled_label_counts, kept_label_counts), and, for a 0/1 label, the population and sample positive rates and any correction applied.

Configuration

Good to know

  • Nori-Rel removes only the target column. A column that copies or encodes it, such as a balance_band, would still leak it: connect a view without it.
  • "noridfs" and AgenticSearch size their features to a window of future events, which a static target doesn’t have, so they raise an error here.
  • A static prediction makes no reusable pipeline: details.pipeline is None.
  • Backtests at a past date aren’t supported yet. They need to know when each value was recorded, which the database doesn’t track.
  • To score every known row at once, as in leave-one-out, name the rows in batches with FOR ... IN (...); there is no single call for it yet.

Next steps

Relational data

Forecast what happens next from linked tables, with past dates as examples.

Explainability

SHAP values, interactions, and partial dependence.