> For the complete documentation index, see [llms.txt](https://v2.dataos.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://v2.dataos.info/concepts/foundations/activation/apis/getting-started/querying-data-products/cross-model-queries.md).

# Cross-model queries

Cross-model queries let you combine measures and dimensions from related models. They only work when the semantic graph gives the transpiler a valid and safe join path.

Mixing measures and dimensions from different models requires a join path in the transpiler. Three rules decide what works.

### Rule 1 — Directionality

A join is only traversable from the model that declares it. Joins live in `model.joins[]`.

In the b2b\_saas catalog:

* `users.joins` → `subscriptions` (one\_to\_many), `usage_events` (one\_to\_many)
* `usage_events.joins` is empty

Valid: `subscriptions.plan_type` as a dimension while measuring `users.total_users` (users declares the join).

Invalid: `users.plan_type` as a dimension while measuring `usage_events.total_events` (usage\_events declares no path to users).

Check `joins[]` on the model that owns your measure. Only declared targets are reachable.

### Rule 2 — Fan-out

Crossing a `one_to_many` edge from the measure's model and grouping by a dimension on the many side inflates the measure. A user with three subscriptions appears three times when you group by `subscriptions.plan_type`, so `users.total_users` counts each user three times.

Safe patterns:

* Filter across the join without grouping by the many-side dimension (filter `subscriptions.status = active` while counting users).
* Put the measure on the many-side model (`subscriptions.total_arr` by `subscriptions.plan_type`).

When in doubt, measure from the model that owns what you're counting. Use joins to filter, not to group.

### Rule 3 — Ambiguous paths and joinHints

When multiple paths connect two models, the transpiler picks the shortest by hop count — which may not match business intent.

```
A → B → C → X
A → D → E → X   ← correct business path
A → F → X       ← shortest, picked by default
```

Set an explicit path in [REST](/concepts/foundations/activation/apis/getting-started/querying-data-products/rest.md) or [Semantic SQL](/concepts/foundations/activation/apis/getting-started/querying-data-products/semantic-sql.md):

```json
{
  "joinHints": [["A", "D", "E", "X"]]
}
```

Or encode the path in a member name:

```json
{
  "dimensions": ["users.subscriptions.subscription_plans.tier"]
}
```

`joinHints` is supported in REST and Semantic SQL, not [GraphQL](/concepts/foundations/activation/apis/getting-started/querying-data-products/graphql.md).

### Safe patterns

```json
{
  "measures": ["users.total_users"],
  "dimensions": ["users.plan_type"]
}
```

```json
{
  "measures": ["users.active_users"],
  "filters": [
    {
      "member": "subscriptions.status",
      "operator": "equals",
      "values": ["active"]
    }
  ]
}
```

```json
{
  "measures": ["subscriptions.total_arr"],
  "dimensions": ["subscriptions.plan_type"]
}
```

Semantic SQL equivalent for the filter-across-join case:

```sql
SELECT users.industry, MEASURE(users.enterprise_users)
FROM users
CROSS JOIN subscriptions
WHERE subscriptions.status = 'active'
GROUP BY 1;
```

### Patterns to avoid

```json
{
  "measures": ["usage_events.total_events"],
  "dimensions": ["users.plan_type"]
}
```

Transpiler error: no join path.

```json
{
  "measures": ["users.total_users"],
  "dimensions": ["subscriptions.plan_type"]
}
```

No error. Silent count inflation.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://v2.dataos.info/concepts/foundations/activation/apis/getting-started/querying-data-products/cross-model-queries.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
