How queries work, and their errors
Understanding the query pipeline makes every error message obvious — and most of them preventable through curation.
From widget to SQL
No user or AI writes SQL directly against your warehouse. Every request — a chart configuration, a table, a question to Axi — goes through the same pipeline:
- Semantic resolution — the requested fields are resolved against the connection's semantic models: dimensions, measures, metrics, and their sources (columns or expressions).
- Join-path resolution — if fields span several models, the engine walks the declared keys to find the join path. Exactly one path must exist; zero or several is an error, not a guess.
- Guardrail checks — the plan is validated: no fan-out that would double-count a measure, grains present where required, filter values matching field types.
- SQL compilation — the validated plan compiles to the warehouse's SQL dialect and executes server-side, with credentials fetched from Secret Manager. Results return to the widget; the SQL itself is visible via the SQL button in the chart and card editors.
Two properties follow: queries are read-only by construction, and a wrong result is treated as worse than no result — when correctness can't be guaranteed, the query is refused with an explanation.
Error reference
What users see (in plain language, with the culprit field named), what it means technically, and how to fix it durably:
| Message (abridged) | Technical cause | Durable fix |
|---|---|---|
| This total would be counted multiple times | Reverse-join fan-out would inflate an additive measure | Rethink the reverse join, or use a measure safe under the traversal |
| These tables aren't connected | No join path between the models | Declare the missing key |
| More than one way to connect these tables | Ambiguous join path | Have the widget specify the relationship; consider hiding a redundant key |
| This field can't be connected to your metric's data | Dimension unreachable from the metric's model | Choose a related field, or add the key that makes it reachable |
| To combine metrics from different sources, group by fields they all share | Multi-fact query with non-shared dimensions | Group only by dimensions common to all facts |
| This chart combines too many sources | Too many fact models in one query | Split into separate widgets |
| No date field to chart over time | Model has no time dimension | Add/declare a time dimension; set a primary time axis |
| This time field needs a grouping level | Grain required for a time dimension | Pick a grain (day, month, year…) |
| A running total / previous-period comparison needs a date grouping | Cumulative or offset metric without a time grouping | Add a time field to the widget |
| Comes from nested data that can't be summarized this way | Reverse traversal into an array | Flatten the array into a virtual model |
| This metric/field/table no longer exists | Semantic-layer element renamed or deleted after the widget was built | Restore or re-point; check for orphaned elements after schema refreshes |
| A filter has the wrong number of values / wrong type | Filter arity or type mismatch | Correct the filter |
| This custom formula couldn't be understood | Ad-hoc expression failed to parse | Fix the formula; promote recurring ones to curated metrics |
Reading errors as curation signals
Recurring errors are a map of what to curate next:
- Frequent "tables aren't connected" → missing keys.
- Frequent "counted multiple times" → a reverse join enabled somewhere it shouldn't be, or a measure that needs a semi-additive window.
- Frequent formula errors from the same team → a metric wants to exist.
The usage dashboard shows AI-run error rates per member — a spike is usually one of the patterns above, not a platform issue.