Calculated Insights: the metric you compute once and retrieve everywhere
What a Calculated Insight is: an aggregation defined by dimensions and measures — not an arbitrary SELECT — pre-computed once and served everywhere as a queryable Calculated Insight Object. Batch vs streaming, the real limits of each, the freshness that makes the result trustworthy or silently wrong, and the separate SQL dialect you author it in.
A Calculated Insight is the answer to a question you'd otherwise ask over and over, computed once and stored so every consumer retrieves the same number instead of re-deriving it. This is principle 7 of the Data 360 (formerly Data Cloud) thesis made concrete — compute once, retrieve many. Lifetime value per customer, an engagement score, orders per person: define it as a CI, and the segment, the activation, and the agent all read one result rather than three slightly different ones.
This page is the reference for what a CI is — not how to consume one (that's consuming query results) and not the query dialect you read it back with (that's Data Cloud SQL). Authoring a Calculated Insight is its own surface, with its own SQL dialect and its own shape. The shape is the whole point: a CI is not a query you alias into a table, it is an aggregation with a declared grain.
A CI is dimensions and measures, not arbitrary SELECT
The mental model that misleads is "a Calculated Insight is a saved query." It isn't. A CI is an aggregation defined by two things, and only two:
- Dimensions — the fields you group by. The dimensions are the grain: one row of the result per unique combination of dimension values. Group by buyer and you get one row per buyer; group by buyer and month and you get one row per buyer per month. Choosing the dimensions is choosing what each row of the insight means.
- Measures — the aggregations you compute at that grain.
COUNT,SUM,COUNT(DISTINCT …),MIN/MAXover the rows that fall into each group.
There is no arbitrary SELECT *, no row-level passthrough, no "just give me the records." A CI is GROUP BY by construction, and the grain is a decision you make on purpose — because every consumer inherits it.
-- A Calculated Insight IS dimensions + measures. The grain is the decision.
-- Dimension: one row per buyer. Measures: their order count and lifetime value.
-- Group by the wrong dimension and every consumer inherits the wrong number.
SELECT
o.ssot__BuyerId__c AS buyer, -- dimension: the grain
COUNT(DISTINCT o.ssot__Id__c) AS order_count, -- measure
SUM(o.ssot__GrandTotalAmount__c) AS lifetime_value -- measure
FROM ssot__SalesOrder__dlm o
GROUP BY o.ssot__BuyerId__c;Get the grain wrong — group by the wrong key, or SUM where you needed COUNT(DISTINCT …) — and the number is wrong everywhere it's retrieved, consistently and silently. That's the double edge of compute-once-retrieve-many: one correct definition propagates correctness to every consumer, and one wrong definition propagates the error just as faithfully.
A note on traversal: the example above aggregates a single object, which is how a CI illustrates cleanest. Real CIs often span objects — a buyer's orders tied back to the resolved person. Those joins work only where the model already has them, and a source object never joins directly to UnifiedIndividual__dlm; the path runs through the IndividualIdentityLink__dlm bridge that identity resolution maintains. Model the relationship first, then the CI can traverse it (see Data Cloud SQL on modeled joins).
Batch and streaming: two engines, different limits
A CI runs in one of two modes, and they are genuinely different tools — not a fast version and a slow version of the same thing.
Batch — scheduled, full expressive power
A batch CI recomputes on a schedule, with a cadence you set as fine as every hour or as coarse as once every 24 hours. In exchange for not being real-time, it gives you the full expressive surface: relational, multi-object aggregation over long time horizons, the complete history of the data, the aggregates you'd expect. Lifetime value, all-time order count, an engagement score over months — these are batch metrics, because they need history and they don't need to be sub-minute fresh. Batch is the default and the one you'll reach for most.
Streaming — continuous and low-latency, with real constraints
A streaming CI updates continuously as engagement events arrive, with latency in seconds to minutes rather than the refresh interval. That speed is bought with hard limits, and reaching for streaming because "real-time is better" without knowing them is the classic mistake:
- It cannot join Unified Profiles. A streaming insight evaluates the incoming event stream; it does not reach into the resolved profile. If your metric needs a unified-individual attribute, streaming can't express it.
- It operates only within a defined time window — no lifetime metrics. Every streaming CI declares a window, from 5 minutes up to 24 hours, and computes only within it. "Clicks in the last 30 minutes" is a streaming metric; "clicks all-time" is not possible on streaming, by design.
- It sees only incoming events, not full history. Streaming aggregates the events flowing in during the window. It has no view of what happened before the stream started or outside the window.
- It supports only
SUMandCOUNT, used with aWINDOWfunction. The aggregation surface is deliberately narrow:SUMandCOUNTover the window, expressed withWINDOW.START/WINDOW.ENDand aWINDOW(…)clause. The rich multi-object aggregation that batch allows is not on the table. - It aggregates roughly every 5 minutes, up to the 24-hour window ceiling.
Freshness: a CI is exactly as fresh as its last run
Between runs, a CI serves the last result it computed. That is the entire value of pre-computation — and its entire risk. A batch CI refreshed daily and feeding a real-time decision is serving a number that can be up to a day old, and nothing in the consumer says so. The segment reads it, the agent retrieves it, and both treat a stale aggregate as current truth.
A stale insight isn't a missing answer — it's a confident wrong one, which is worse. An empty result makes a consumer hesitate; a plausible-but-old number doesn't. Freshness is a property of the CI's cadence, not of the moment you read it, so the cadence has to match the freshest decision the CI feeds.
A CI is a queryable object: the Calculated Insight Object
Once defined, a Calculated Insight is exposed as a Calculated Insight Object (CIO) — a queryable table of its dimensions and measures. You SELECT from it the same way you query any DMO, in the ordinary Data Cloud SQL dialect, and you get the pre-computed rows back:
-- Reading a CI back is ordinary Data Cloud SQL over the CIO.
-- You retrieve the pre-computed result; you do not recompute it.
SELECT
ci.buyer,
ci.lifetime_value
FROM Customer_Lifetime_Value__cio ci
WHERE ci.lifetime_value > 1000
ORDER BY ci.lifetime_value DESC;This is the read side, and it follows every rule the dialect page lays out — namespaced identifiers, aliasing, the same clause behavior. The result is already computed; the query just fetches it.
Two SQL dialects, not one
The single most important thing to keep straight: creating a Calculated Insight uses a different SQL dialect than the one you read it back with. Salesforce's own documentation states it plainly — calculated insight creation uses a different SQL dialect than the Query Guide describes. The authoring dialect has its own rules (and, for streaming, the WINDOW syntax above); the Query Editor and Query API dialect is the ANSI-leaning surface documented on the Data Cloud SQL page.
Don't conflate them. A function that works when you author a CI may not exist when you query the CIO, and vice versa. The boundary is clean once you name it: you author a CI in one dialect, you retrieve it in another, and the dialect page makes the same point from the query side.
Who consumes a CI — they retrieve, they don't recompute
The reason a CI is worth defining is what happens downstream. Segments, activations, and agents all retrieve the CIO's result; none of them recomputes the aggregation. That's the contract:
- Segments filter on a CI's measures — "lifetime value over 1,000" — without re-running the aggregation per refresh.
- Activations carry a CI's values into the target system as attributes.
- Agents — Agentforce or an external LLM — ground their answers on the CI's number, retrieving it exactly as you defined it.
Every consumer inherits the CI's grain, its freshness, and its correctness, unchanged. A CI defined at the right grain and refreshed on the right cadence is a number the whole org can trust; a wrong one is a number the whole org trusts anyway — including the agent, which will answer confidently from it. How each consumer reads a CI is its own page: see consuming query results.
Related
- Query & Insights gotchas — where the SQL and insight instinct misleads, the production version
- Data Cloud SQL — the dialect you read a CIO back with (and why authoring a CI is a different one)
- The Query API — running queries programmatically, including against a CIO
- Consuming query results — how segments, activations, and agents retrieve a CI
- Query & Insights Style Guide — the conventions that keep these definitions readable
- Data 360 principles — principle 7 (compute once, retrieve many) and why it's the discipline here
Reference: