Semantic Model Setup

How to create first semantical model

Building a robust analytics layer starts with a clear, reusable semantic model. This guide shows how to turn raw, replicated tables into business‑ready entities, link them together, and expose trusted metrics.

Key Concepts

Term
Definition
Entity
A logical representation of a table / dataset. Contains dimensions, measures, and relations.
Dimension
A descriptive attribute – date, company name, status, etc.
Measure
A numeric metric, usually aggregated – sales amount, deal count, …
Relation
A logical link between two entities. Always declared in both directions.
Semantic Model
A connected set of entities forming a domain graph.
💡

Access control – Only users with the Admin role can create or edit entities.

Ready‑made HubSpot Semantic Model 🔧

Connect your HubSpot account and RevOS instantly spins up a ready‑made semantic model that mirrors HubSpot’s core CRM objects (companies, contacts, deals, tickets, owners, engagements, and more) — complete with default measures and relations. Use it as‑is or extend it with your own business logic.

👉 Full details: Getting Started with the HubSpot Semantic Model.

Interface Overview

Below is a quick tour of the modeling workspace. You can place screenshots next to each subsection (file names in brackets are suggestions).

Notion image

Data Model sidebar

Use the Semantic Model icon (two connected circles) to open the modeling area from anywhere in the app.

Overlays tab

  • Shows every existing entity (overlay).
  • Click ✏️ Edit to open the JSON editor.
  • Click 🗑 Delete to remove the entity.
  • Click + Add Overlay (bottom-left) to create a new one.

Graph tab

Notion image

Interactive map of all entities and relations. Here you can see all the entites, pre-made also.

Entity Creation Workflow

  1. Know your source table. Semantic Model window does not display a catalog or dropdown. Enter the fully‑qualified table name manually (e.g. my_project.raw_hubspot.deals).
  1. Write the SQL query (sql). Add filters as needed – for example, restrict to the last 3 months.
  1. Verify column names yourself. The editor provides no autocomplete or validation. If you misspell a column, the model will still publish, but downstream queries will be empty or error‑prone.
  1. Mark the primary key. In dimensions, set "primary_key": true on one field.
  1. Define relations in the joins block (see § 5).
  1. Add measures in the measures block. Each measure must have unique name.
  1. Add dimensions in the dimensions block. Each dimension must have unique name.
  1. Save and publish the entity. Teammates can now build reports on top of the model.
Important – Each entity must have one primary key. Without it the model will not build.

Entity Code Template

Entity Code Template

{
  "sql": "<your SQL – filters optional>",
  "joins": {
    "<target_entity>": {
      "sql": "${CUBE}.field = ${<target_entity>}.field",
      "relationship": "one_to_one"
    }
  },
  "measures": {
    "<measure_name>": {
      "sql": "<expression>",
      "type": "sum" | "count" | "avg" | "min" | "max" | "number"
    }
  },
  "dimensions": {
    "<dimension_name>": {
      "sql": "${CUBE}.<column>",
      "type": "string" | "number" | "time",
      "primary_key": true
    },
   "<another_dimension_name>": {
    "sql": "${CUBE}.<column>",
    "type": "string" | "number" | "time"
	  }
  }
}

Example – minimal SELECT * with filtering and a join

Relation Types and When to Use Them

Type
Quick description
Example
How to set
one_to_one
Each A has exactly one B
Company ↔️ Company profile
"relationship": "one_to_one"
one_to_many
One A owns many B
Company → Invoices
"one_to_many"
many_to_one
Many A refer to one B
Invoice → Company
"many_to_one"
360‑degree rule – Declare each relation in both entities. If deals has many_to_one to companies, then companies must have the mirror one_to_many to deals. Otherwise the graph builder cannot find a path.

Modeling many‑to‑many

Direct many_to_many is not supported. Instead create a junction entity – a link table with pairs of keys. Both original objects connect to it with one‑to‑many / many‑to‑one relations, keeping source tables clean and avoiding duplicates.

Example (HubSpot deals ↔️ companies):

  1. Create entity company_deal_link based on the companies table.
  1. In SQL unnest the deal_ids JSON array: UNNEST(deal_ids) AS deal_id.
  1. Relations:
      • companies ↔️ company_deal_linkone_to_many / many_to_one.
      • deals ↔️ company_deal_linkone_to_many / many_to_one.

Creating Dimensions

  1. Add an object under dimensions.
  1. Fill sql – usually the column name.
  1. Choose type:
      • string – text, UUID, email, …
      • number – numeric IDs or values that shouldn’t be aggregated.
      • time – dates & timestamps. The system auto‑creates Year → Quarter → Month hierarchy.
  1. Mark the main key with "primary_key": true.

Quick checklist

  • Does the primary key make each row unique?
  • Can the value be aggregated? If yes, it’s probably a Measure, not a Dimension.

Creating Measures

  1. Add an object under measures.
  1. Fill in:
      • sql – an expression or numeric column.
      • typesum, count, avg, min, max, or number (custom formula not further aggregated).

Tips

  • Use SAFE_DIVIDE to protect against division by zero.
  • Prefix percentages with pct_; suffix totals with _cnt, _sum.
Did this answer your question?
😞
😐
🤩

Last updated on October 14, 2025