Skip to content

Build a semantic model

A semantic model turns SQL tables and other source data into a reusable domain layer you can query, validate, and eventually use with reasoners. This overview gives you a high-level picture of semantic modeling in PyRel, and links to task guides that show you how to do it.

A semantic model is a shared, reusable way to define what your data means in your domain. In PyRel, it is more than renaming tables and columns. It lets you model the things you care about and validate them with logic and checks you can run repeatedly.

In PyRel, you build a semantic model using the following building blocks:

  • Concepts: The types of entities in your domain, like “Person”, “Company”, or “Product”.
  • Properties: Single-valued attributes of entities, like a person’s name or age.
  • Relationships: Links between entities, like “Person works at Company”.
  • Definitions: The base facts and logic that express domain knowledge, like “Alice is a Customer” or “Customers who spent more than $100 in the last month are VIPs”.
  • Requirements: Reusable checks that enforce data validation and business rules.

Do any of these sound familiar?

  • You keep rewriting the same joins, filters, and “business rules” across notebooks and scripts.
  • Different people (or dashboards) answer the “same” question differently because the logic is not centralized.
  • Your data sources change, and you want to update mappings and rules once instead of chasing every downstream query.
  • You need guardrails that catch bad data and broken assumptions early, before they turn into silent errors.

Semantic modeling solves these problems by letting you encode your domain knowledge in a shared layer that sits on top of your data sources.

Once you encode a concept, relationship, or definition in the model, every query can rely on it. That makes results more consistent, makes refactors cheaper, and makes it easier to onboard new teammates.

How semantic models relate to SQL and source data

Section titled “How semantic models relate to SQL and source data”

Think of a semantic model as a layer that sits on top of your source data. It maps raw data into domain concepts and relationships, and then lets you build derived logic and requirements on top of that. Queries from notebooks, scripts, and applications can then use the model as a single source of truth for what things mean and how they relate.

Here’s a quick picture of how PyRel fits together: where your data comes from, what the semantic model does, and what runs queries against it:

Data SourcesQuery clientsPyRel semantic modelSQL tablesIn-memory dataNotebooksScriptsApplicationsSemantic schema(Concepts, properties,relationships)Definitions(Facts + derived logic)Requirements(Reusable checks) map to domain meaning query

Once you map sources into the model, every client can query the same shared meaning.

PyRel is a declarative Python DSL for building and querying semantic models. This means you describe what is true for your model, not how to compute it.

In other words you:

  • Declare what’s important in your domain.
  • Let PyRel use those declarations to compute query results.

Here is a simple PyRel model that declares a Person concept, defines some facts about people, and then queries those facts:

from relationalai.semantics import Integer, Model, String
# Create a new Model instance.
m = Model("PeopleModel")
# Declare a Person concept with name and age properties.
Person = m.Concept("Person")
Person.name = m.Property(f"{Person} is named {String:name}")
Person.age = m.Property(f"{Person} is {Integer:age} years old")
# Define some Person entities.
# In real models, you would usually load this data from a SQL table.
m.define(Person.new(name="Alice", age=10), Person.new(name="Bob", age=30))
# Declare an Adult concept as a subtype of Person.
Adult = m.Concept("Adult", extends=[Person])
# Define Adults as people with age >= 18.
m.define(Adult(Person)).where(Person.age >= 18)
# Select names and ages of Adults.
q = m.select(Adult.name, Adult.age)
# Inspect the query results.
q.inspect()

If you know SQL, you can think of a model as a sort of on-demand view that you build with Python code instead of a SQL query:

  • Concepts and relationships make up the schema of your domain, similar to tables and columns.
  • Definitions are like layered views that build on top of that schema using logic, except that they may contain advanced reasoning that is unavailable in SQL.
  • When you query the model, PyRel evaluates the definitions it needs to answer that query. Data flows from your source tables, through the schema and logic in the model, into a table of results.

Concretely, when you select(Adult.name, Adult.age):

  • You’re asking for a table of adults, with a name and age column.
  • PyRel evaluates the Adult definition to figure out which Person entities count.
  • It pulls Person.name and Person.age for those people from your sources, and returns the matching rows.

All of this happens without you writing a single loop or hand-coding the filtering and join logic yourself.

Once you have stable declarations, definitions, and requirements, your model becomes a reusable domain layer. You can query it from notebooks, scripts, and applications, and evolve it as your understanding and requirements change, without breaking downstream clients.

When your schema and requirements stop changing frequently, you can apply advanced reasoning on top of the same modeled meaning. Different reasoning styles help with different kinds of decisions:

  • Rules-based reasoning: Capture complex business logic an decision trees to answer questions like “Which customers are eligible for this promotion?” or “What steps in this workflow are most likely to fail?”
  • Graph reasoning: Answer questions about relationships and connections in your data, like “Who are the top influencers in this network?” or “How can I segment my customers based on their interactions?”

Learn more in our rules-based reasoning guides.

When your schema, derived logic, and requirements live in the model, exploration stays consistent across cells and across people. That means you spend less time re-implementing joins and rules, and more time refining what you want to ask.

Scripts and scheduled jobs make model-backed queries repeatable. Because the model centralizes business logic, your automation reuses the same definitions and requirements that notebooks and applications rely on. That keeps CI, pipelines, and production exports consistent as the model evolves. When you need a durable output, export results into a table by chaining Fragment.into and Fragment.exec.

Applications benefit most when the model becomes the shared source of truth for product logic. Reuse the same model-backed queries (or the exported tables) so behavior stays aligned with your domain definitions across services, endpoints, and teams.