This page is the conceptual spine: if you remember one thing about Sidemantic, remember **automatic joins** and **automatic grouping**.

## Core Objects

- **Model**: a named dataset (usually a table; can also be a SQL subquery) with a `primary_key`.
- **Dimension**: a field you can group/filter by (categorical, numeric, boolean, time).
- **Metric (model-level)**: an aggregation defined on a single model (sum/count/avg/etc).
- **Metric (graph-level)**: a metric defined as a formula over other metrics.
- **Segment**: a named filter you can reuse in queries.
- **Relationship**: declares how two models connect (many-to-one, one-to-many, many-to-many).
- **Pre-aggregation**: a materialized rollup table the planner can route queries to.

## Query Semantics (Non-Negotiables)

<Callout type="warning">
Sidemantic SQL is *not* generic SQL.

- **No `JOIN` keyword**: joins are inferred from referenced fields and declared relationships.
- **`GROUP BY` is ignored**: grouping is derived from the selected dimensions.
- **`DISTINCT` is not supported**: dimensions are treated as distinct at the query grain.
</Callout>

## Automatic Joins

You trigger joins by referencing fields from multiple models in the same query.

<img src="/img/sidemantic/join-graph.svg" alt="Join graph diagram showing models and relationships" style={{ maxWidth: "100%", border: "1px solid var(--border)", borderRadius: "8px" }} />

Join inference relies on:
- relationship types (many-to-one vs one-to-many)
- declared keys (foreign keys + primary keys)
- choosing a join path when multiple paths exist (see [Gotchas](gotchas))

## Automatic Grouping (Why `GROUP BY` is Ignored)

Sidemantic treats metrics as **aggregations** and dimensions as the **grouping keys**.

When you write:

```sql doc-validate=lint
SELECT customers.region, orders.revenue
FROM orders
```

Sidemantic interprets that as: “aggregate `orders.revenue` grouped by `customers.region`”.

## Fan-out and Correctness

Fan-out happens when you join one row to many rows (e.g. `orders` → `order_items`).
Naive SQL `sum(orders.amount)` would double count.

Sidemantic uses **symmetric aggregates** to protect metric correctness in fan-out joins (see [Fan-out & Symmetric Aggregates](correctness-fanout)).

## Compilation Pipeline

<img src="/img/sidemantic/compilation-pipeline.svg" alt="Compilation pipeline: semantic query to join planning to SQL generation" style={{ maxWidth: "100%", border: "1px solid var(--border)", borderRadius: "8px" }} />

High-level stages:
1. **Parse** semantic SQL (models + fields).
2. **Resolve join graph** using relationships.
3. **Plan metrics** (aggregation, filters, symmetric aggregates if needed).
4. **Generate SQL** for the target engine/dialect.

## Where Engine Differences Show Up

- SQL dialect differences (date functions, casts, identifiers)
- pre-aggregation materialization and routing behavior
- server/connectivity behavior (`serve`) and adapter capabilities

## Next

- [SQL Semantics](query)
- [Relationships](relationships)
- [Parameters & Templating](parameters-templating)