MATIH Platform is in active MVP development. Documentation reflects current implementation status.
10. Data Catalog & Governance
Semantic Layer
Model Relationships

Model Relationships

Relationships define how semantic models are connected to each other through join conditions. When a metric query references dimensions or metrics from related models, the Semantic Layer automatically generates the appropriate JOIN clauses in the compiled SQL. All relationship endpoints are served at the base path /api/v1/relationships.


Relationship Types

TypeDescriptionExample
ONE_TO_ONEOne record on the left matches exactly one on the rightUser to UserProfile
ONE_TO_MANYOne record on the left matches many on the rightCustomer to Orders
MANY_TO_ONEMany records on the left match one on the rightOrders to Customer
MANY_TO_MANYMany-to-many through a bridge tableProducts to Categories

Join Types

TypeDescription
INNEROnly matching rows from both models
LEFTAll rows from the left model, matching from the right
RIGHTAll rows from the right model, matching from the left
FULLAll rows from both models
CROSSCartesian product of both models

Endpoints

MethodPathDescription
GET/api/v1/relationshipsList all relationships for the tenant
GET/api/v1/relationships/:relationshipIdGet a relationship by ID
GET/api/v1/relationships/by-model/:modelIdGet relationships for a specific model
POST/api/v1/relationshipsCreate a new relationship
PUT/api/v1/relationships/:relationshipIdUpdate a relationship
DELETE/api/v1/relationships/:relationshipIdDelete a relationship

Relationship Structure

A relationship connects a source model to a target model through join keys.

FieldTypeRequiredDescription
nameStringYesUnique relationship name
sourceModelIdUUIDYesSource (left) model ID
targetModelIdUUIDYesTarget (right) model ID
relationshipTypeEnumYesONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE, MANY_TO_MANY
joinTypeEnumYesINNER, LEFT, RIGHT, FULL, CROSS
sourceKeyStringYesJoin column on the source model
targetKeyStringYesJoin column on the target model
conditionStringNoAdditional join condition (SQL expression)
descriptionStringNoHuman-readable description

Example: Create Relationship

Request:

{
  "name": "orders_to_customers",
  "sourceModelId": "550e8400-e29b-41d4-a716-446655440000",
  "targetModelId": "660e8400-e29b-41d4-a716-446655440001",
  "relationshipType": "MANY_TO_ONE",
  "joinType": "LEFT",
  "sourceKey": "customer_id",
  "targetKey": "id",
  "description": "Links orders to their customer records"
}

Response:

{
  "id": "770e8400-e29b-41d4-a716-446655440002",
  "name": "orders_to_customers",
  "sourceModelId": "550e8400-e29b-41d4-a716-446655440000",
  "targetModelId": "660e8400-e29b-41d4-a716-446655440001",
  "relationshipType": "MANY_TO_ONE",
  "joinType": "LEFT",
  "sourceKey": "customer_id",
  "targetKey": "id",
  "description": "Links orders to their customer records",
  "createdAt": "2026-02-12T10:30:00Z",
  "updatedAt": "2026-02-12T10:30:00Z"
}

How Relationships Are Used

When a metric query references dimensions from a related model, the query compiler automatically traverses the relationship graph and adds JOIN clauses.

StepDescription
1User requests metrics from Model A with dimensions from Model B
2Compiler looks up relationships between Model A and Model B
3If a direct relationship exists, a single JOIN is added
4If only an indirect path exists (A to C to B), multiple JOINs are added
5The join type and keys from the relationship definition are used

Querying Across Models

A metric query that spans multiple models:

{
  "modelId": "550e8400-e29b-41d4-a716-446655440000",
  "metrics": ["total_revenue"],
  "dimensions": ["customer_name", "customer_region"],
  "filters": [
    {
      "field": "customer_region",
      "operator": "=",
      "value": "US"
    }
  ]
}

If customer_name and customer_region are dimensions on a related model, the compiler generates:

SELECT c.name AS customer_name, c.region AS customer_region,
       SUM(o.order_total) AS total_revenue
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE c.region = 'US'
GROUP BY c.name, c.region

Best Practices

  • Define relationships with the correct cardinality to ensure accurate aggregations
  • Use LEFT join type by default to avoid dropping rows when the target has no match
  • Name relationships descriptively (e.g., orders_to_customers not rel_1)
  • Add conditions for complex joins that require more than a simple key match
  • Avoid circular relationship chains that could cause infinite recursion in path traversal