From join tables into Relationships

Introduction

You have Customer→Order relationships working. Now you need to connect Orders to Products so you can find what customers bought.

In this lesson, you will learn why many-to-many relationships require join tables in relational databases and how graphs handle them with direct relationships.

Join, junction, bridge, pivot

Join tables, junction tables, bridge tables, pivot tables are all the same thing. They are used to connect two tables in a many-to-many relationship.

Understanding many-to-many relationships

Many-to-many relationships occur when multiple entities on one side can connect to multiple entities on the other side.

In relational databases, you cannot directly connect two tables in a many-to-many relationship. You must create an intermediate table (join table, junction table, or bridge table) to store the associations.

You’ll encounter this pattern everywhere:

  • Products and Categories - products listed as "Electronics", "On Sale", "Featured"

  • Articles and Tags - articles tagged as "Technology", "News", "Featured"

  • Recipes and Dietary Tags - dishes classified as "Vegetarian", "Quick Meals", "Budget-Friendly"

  • Orders and Products - an order containing multiple products, products appearing in multiple orders

These tables are only there to make the problem fit the underlying technology.

Northwind’s join tables

Let’s take a look at how this pattern can be applied in the Northwind dataset.

Northwind contains two join tables that demonstrate the pattern:

  • A product can be in multiple categories, multiple categories can contain the same product

  • An Order can contain multiple products, a product can appear in multiple orders

In a graph, both of these join tables become direct relationships.

Products in Categories

mermaid
%%{init: {
  "theme": "base",
  "themeVariables": {
    "primaryColor": "#eef6f9",
    "primaryBorderColor": "#c7e0ec",
    "lineColor": "#94a3b8",
    "fontFamily": "Public Sans, Arial, Helvetica, sans-serif"
  }
}}%%
erDiagram
    PRODUCTS {
        INT productID
        STRING productName
        FLOAT unitPrice
    }
    CATEGORIES {
        INT categoryID
        STRING categoryName
    }
    PRODUCT_CATEGORIES {
        INT id
        INT productID
        INT categoryID
    }

    PRODUCTS ||--o{ PRODUCT_CATEGORIES : ""
    CATEGORIES ||--o{ PRODUCT_CATEGORIES : ""

The product_categories table only exists to facilitate the many-to-many relationship between Products and Categories.

The table contains three columns:

  • id - The unique identifier for the row

  • productID - The ID of the product in the products table

  • categoryID - The ID of the category in the categories table

In the Northwind dataset used for this import, each product belongs to only one category (1:1 relationship). However, in a real-world scenario, a product would typically be listed in multiple categories (many-to-many).

Product IN_CATEGORY as graph relationship

This relationship can be represented as a direct relationship in a graph.

Any one relationship connects two nodes, so there is no concept of many-to-many relationship, or the need for a join table.

mermaid
%%{init: {
  "theme": "base",
  "themeVariables": {
    "primaryColor": "#eef6f9",
    "primaryBorderColor": "#c7e0ec",
    "lineColor": "#94a3b8",
    "fontFamily": "Public Sans, Arial, Helvetica, sans-serif"
  }
}}%%
graph LR
    Product((Product)):::forest -->|IN_CATEGORY| Category((Category)):::earth

    classDef forest fill:#edf6e8,stroke:#b7df9c,stroke-width:1.25px,color:#2f5d1e
    classDef earth fill:#f4ebe3,stroke:#dcc4a2,stroke-width:1.25px,color:#5c3a1e
    linkStyle default stroke:#94a3b8,stroke-width:1.25px

Orders Contain Products

The order_details table is also used to facilitate the many-to-many relationship between Orders and Products, but has additional columns that hold information about the connection.

Alongside the foreign keys, the table also contains:

  • quantity - The number of units ordered

  • unitPrice - The price per unit

  • discount - The discount applied to the order

This data gives the table validity as an entity because it describes how the product was ordered.

mermaid
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#eef6f9", "primaryBorderColor": "#c7e0ec", "lineColor": "#94a3b8", "fontFamily": "Public Sans, Arial, Helvetica, sans-serif"}}}%%
erDiagram
    ORDERS { INT orderID }
    PRODUCTS { INT productID }
    ORDER_DETAILS { INT orderID INT productID INT quantity FLOAT unitPrice }
    ORDERS ||--o{ ORDER_DETAILS : ""
    PRODUCTS ||--o{ ORDER_DETAILS : ""

Order CONTAINS as graph relationship

In a graph, the order_details join table becomes direct CONTAINS relationships with properties:

mermaid
%%{init: {
  "theme": "base",
  "themeVariables": {
    "primaryColor": "#eef6f9",
    "primaryBorderColor": "#c7e0ec",
    "lineColor": "#94a3b8",
    "fontFamily": "Public Sans, Arial, Helvetica, sans-serif"
  }
}}%%
graph LR
    Order((Order)):::highlight -->|"CONTAINS<br/>{quantity: 12,<br/>unitPrice: 14.00}"| Product((Product)):::forest

    classDef highlight fill:#f4f5ff,stroke:#c7d2fe,stroke-width:1.25px,color:#3730a3
    classDef forest fill:#edf6e8,stroke:#b7df9c,stroke-width:1.25px,color:#2f5d1e
    linkStyle default stroke:#94a3b8,stroke-width:1.25px

Concrete example: Order 10248

Here’s what the graph looks like for a real order with multiple products.

Each Order node can connect to multiple Product nodes. This is what makes graph queries fast - you traverse directly from order to products without any JOIN operations.

mermaid
%%{init: {
  "theme": "base",
  "themeVariables": {
    "primaryColor": "#eef6f9",
    "primaryBorderColor": "#c7e0ec",
    "lineColor": "#94a3b8",
    "fontFamily": "Public Sans, Arial, Helvetica, sans-serif"
  }
}}%%
graph TB
    O10248((Order 10248)):::highlight -->|"CONTAINS"| P1((Queso Cabrales)):::forest
    O10248 -->|"CONTAINS"| P2((Hokkien Mee)):::forest
    O10248 -->|"CONTAINS"| P3((Mozzarella)):::forest

    classDef highlight fill:#f4f5ff,stroke:#c7d2fe,stroke-width:1.25px,color:#3730a3
    classDef forest fill:#edf6e8,stroke:#b7df9c,stroke-width:1.25px,color:#2f5d1e
    linkStyle default stroke:#94a3b8,stroke-width:1.25px

Examples in the Northwind dataset

The Northwind dataset contains:

Product-Category relationships:

  • Product "Chai" → IN_CATEGORY → "Beverages"

  • Product "Chang" → IN_CATEGORY → "Beverages"

  • Product "Tofu" → IN_CATEGORY → "Produce"

Order-Product relationships:

  • Order 10248 → CONTAINS → "Queso Cabrales" (quantity: 12, unitPrice: 14.00, discount: 0)

  • Order 10248 → CONTAINS → "Singaporean Hokkien Fried Mee" (quantity: 10, unitPrice: 9.80, discount: 0)

  • Order 10248 → CONTAINS → "Mozzarella di Giovanni" (quantity: 5, unitPrice: 34.80, discount: 0)

In the next lesson, you’ll import the order_details data and create these CONTAINS relationships.

Summary

In this lesson, you learned how to transform join tables into graph relationships:

  • Many-to-many relationships - Occur when multiple entities on one side connect to multiple entities on the other

  • join tables in relational databases - Required to connect two tables in a many-to-many relationship

  • Two Northwind examples - product_categories (Product↔Category) and order_details (Order↔Product)

  • Pure junction tables - product_categories has only foreign keys and an ID

  • Rich join tables - order_details has meaningful business data (quantity, unitPrice, discount)

  • Graph transformation - join tables become direct relationships (IN_CATEGORY, CONTAINS)

  • Relationship properties - join table columns become properties on the relationships

In the next lesson, you will import the order_details data and create CONTAINS relationships.

Chatbot

How can I help you today?

Data Model

Your data model will appear here.