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
%%{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.
%%{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.25pxOrders 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.
%%{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:
%%{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.25pxConcrete 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.
%%{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.25pxExamples 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) andorder_details(Order↔Product) -
Pure junction tables -
product_categorieshas only foreign keys and an ID -
Rich join tables -
order_detailshas 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.